Skip to content

Commit cd11faa

Browse files
committed
Merge remote-tracking branch 'origin/2026.1' into 2026.x
2 parents 0e563d2 + ba1e446 commit cd11faa

13 files changed

Lines changed: 454 additions & 75 deletions

CONTRIBUTING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
## Contributing Studio Pull Requests
2+
We gladly accept community pull requests. This is the preferred way to contribute to Pimcore. There are a few necessary steps before we can accept a pull request:
3+
4+
* [Fork us!](https://help.github.com/articles/fork-a-repo/)
5+
* Select the right branch. `main`(`2026.x`) for features and improvements or latest maintenance branch for bug fixes (`2025.4`)
6+
* [Send a pull request](https://help.github.com/articles/using-pull-requests/) from your fork’s branch to our repo branch.
7+
* [Sign the CLA](https://cla-assistant.io/pimcore/pimcore) - see also below.
8+
* Ensure that Github pipelines pass (linter, tests...)
9+
10+
For details please check also the contributing docs of [pimcore/pimcore](https://github.com/pimcore/pimcore/blob/2026.x/CONTRIBUTING.md).

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,11 @@ This bundle requires the following dependencies:
3939
- [Installation and Configuration](./doc/02_Installation_and_Configuration/README.md)
4040
- [Extending](./doc/03_Extending/README.md)
4141
- [Development Details](./doc/04_Development_Details/README.md)
42+
43+
44+
## Contribute
45+
**Bug fixes:** please create a pull request including a step by step description to reproduce the problem
46+
**Contribute features:** contact the core-team via issue before you start developing
47+
**Security vulnerabilities:** please see our [security policy](https://github.com/pimcore/pimcore/security/policy)
48+
49+
For details, please have a look at our [contributing guide](CONTRIBUTING.md).

composer.json

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"php": "~8.4.0 || ~8.5.0",
2121
"league/csv": "^9.27",
2222
"nesbot/carbon": "^3.10.0",
23-
"pimcore/static-resolver-bundle": "^2026.1",
23+
"pimcore/static-resolver-bundle": "^2026.1.1",
2424
"pimcore/generic-data-index-bundle": "^2026.1",
2525
"pimcore/pimcore": "^2026.1",
2626
"zircote/swagger-php": "^5.0",
@@ -38,14 +38,13 @@
3838
},
3939
"require-dev": {
4040
"roave/security-advisories": "dev-latest",
41-
"codeception/codeception": "^5.3",
42-
"codeception/stub": "^4.1.4",
41+
"codeception/codeception": "^5.3.5",
42+
"codeception/stub": "^4.3.0",
4343
"codeception/module-symfony": "^3.5",
44-
"codeception/phpunit-wrapper": "^9",
4544
"codeception/module-asserts": "^2",
4645
"phpstan/phpstan": "2.1.26",
4746
"phpstan/phpstan-symfony": "^2.0.2",
48-
"phpunit/phpunit": "^10.5",
47+
"phpunit/phpunit": "^12.5.23 || ^13.1.7",
4948
"nyholm/psr7": "^1",
5049
"symfony/phpunit-bridge": "^6",
5150
"fakerphp/faker": "^1.23"

doc/03_Extending/06_Data_Objects/01_Field_Definition_Adapters.md

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ types. Each field type (input, date, relation, etc.) has an adapter that control
1111
- **Saved** — transforming incoming API request data into the format the field definition
1212
expects before it is stored.
1313
- **Read** — normalizing stored data into an API-friendly response format.
14+
- **Detail page** — providing a detail-page-specific representation that may differ from the
15+
normalized value (falls back to normalization when not implemented).
1416
- **Exported** — converting stored data into a string for grid/CSV export.
1517
- **Inherited** — resolving inherited values in the data object hierarchy.
1618
- **Previewed** — providing preview data for search results.
@@ -43,6 +45,7 @@ and can be added when your field type needs the corresponding capability.
4345
|---|---|---|
4446
| `SetterDataInterface` | **Yes** | Transform API request data for saving |
4547
| `DataNormalizerInterface` | No | Customize the API response format |
48+
| `DetailDataInterface` | No | Provide a detail-page-specific value (falls back to `DataNormalizerInterface`) |
4649
| `DataExportInterface` | No | Provide a string representation for grid/CSV export |
4750
| `DataInheritanceInterface` | No | Resolve inherited values in the object hierarchy |
4851
| `SearchPreviewDataInterface` | No | Contribute preview data to search results |
@@ -128,6 +131,47 @@ Return the API-friendly representation of the value.
128131

129132
---
130133

134+
### DetailDataInterface
135+
136+
Implement this interface when the detail page of a data object should display a different
137+
representation than the normalized value. When this interface
138+
is **not** implemented, the system automatically falls back to
139+
`DataNormalizerInterface::normalize()`.
140+
141+
**When to use:**
142+
143+
- The detail page requires a richer or more descriptive value than from `normalize()`.
144+
145+
146+
```php
147+
namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Data;
148+
149+
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Model\FieldContextData;
150+
use Pimcore\Model\DataObject\ClassDefinition\Data;
151+
use Pimcore\Model\DataObject\Concrete;
152+
153+
interface DetailDataInterface
154+
{
155+
public function getDetailData(
156+
Concrete $object,
157+
mixed $value,
158+
Data $fieldDefinition,
159+
?FieldContextData $contextData = null,
160+
): mixed;
161+
}
162+
```
163+
164+
| Parameter | Description |
165+
|---|---|
166+
| `$object` | The data object being loaded for the detail page. |
167+
| `$value` | The raw stored value from the data object getter. |
168+
| `$fieldDefinition` | The Pimcore field definition for this field. |
169+
| `$contextData` | Container context (object brick, localized field, etc.), or `null` for top-level fields. |
170+
171+
Return the detail-page-specific representation of the value.
172+
173+
---
174+
131175
### DataExportInterface
132176

133177
Implement this interface when the field type needs custom handling for grid column display
@@ -314,6 +358,7 @@ namespace App\DataObject\Data\Adapter;
314358
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataExportInterface;
315359
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataInheritanceInterface;
316360
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DataNormalizerInterface;
361+
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DetailDataInterface;
317362
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Model\FieldContextData;
318363
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SearchPreviewDataInterface;
319364
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface;
@@ -326,6 +371,7 @@ use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
326371
final readonly class CustomFieldAdapter implements
327372
SetterDataInterface,
328373
DataNormalizerInterface,
374+
DetailDataInterface,
329375
DataExportInterface,
330376
DataInheritanceInterface,
331377
SearchPreviewDataInterface
@@ -374,6 +420,19 @@ final readonly class CustomFieldAdapter implements
374420
return $value;
375421
}
376422

423+
// --- DetailDataInterface (optional) ---
424+
425+
public function getDetailData(
426+
Concrete $object,
427+
mixed $value,
428+
Data $fieldDefinition,
429+
?FieldContextData $contextData = null,
430+
): mixed {
431+
// Return a detail-page-specific representation of the value.
432+
// When this interface is not implemented, normalize() is called instead.
433+
return $value;
434+
}
435+
377436
// --- DataExportInterface (optional) ---
378437

379438
public function getExportData(
@@ -466,19 +525,19 @@ class required.
466525
| `InputQuantityValueAdapter` | `inputQuantityValue` | Setter |
467526
| `QuantityValueRangeAdapter` | `quantityValueRange` | Setter |
468527
| `UrlSlugAdapter` | `urlSlug` | Setter |
469-
| `CalculatedValueAdapter` | `calculatedValue` | Setter |
528+
| `CalculatedValueAdapter` | `calculatedValue` | Setter, Detail |
470529
| `EncryptedFieldAdapter` | `encryptedField` | Setter, Normalizer, Export |
471530
| `TableAdapter` | `table` | Setter, SearchPreview |
472531
| `StructuredTableAdapter` | `structuredTable` | Setter, Normalizer, SearchPreview |
473532
| `BlockAdapter` | `block` | Setter, Normalizer, SearchPreview |
474533
| `FieldCollectionsAdapter` | `fieldcollections` | Setter, Normalizer, SearchPreview |
475-
| `ObjectBricksAdapter` | `objectbricks` | Setter, Normalizer, Inheritance, SearchPreview |
476-
| `LocalizedFieldsAdapter` | `localizedfields` | Setter, Normalizer, Inheritance, SearchPreview |
477-
| `ClassificationStoreAdapter` | `classificationstore` | Setter, Normalizer, Inheritance, SearchPreview |
534+
| `ObjectBricksAdapter` | `objectbricks` | Setter, Normalizer, Detail, Inheritance, SearchPreview |
535+
| `LocalizedFieldsAdapter` | `localizedfields` | Setter, Normalizer, Detail, Inheritance, SearchPreview |
536+
| `ClassificationStoreAdapter` | `classificationstore` | Setter, Normalizer, Detail, Inheritance, SearchPreview |
478537

479538
**Legend:** Setter = `SetterDataInterface`, Normalizer = `DataNormalizerInterface`,
480-
Export = `DataExportInterface`, Inheritance = `DataInheritanceInterface`,
481-
SearchPreview = `SearchPreviewDataInterface`
539+
Detail = `DetailDataInterface`, Export = `DataExportInterface`,
540+
Inheritance = `DataInheritanceInterface`, SearchPreview = `SearchPreviewDataInterface`
482541

483542
:::info
484543

src/DataObject/Data/Adapter/CalculatedValueAdapter.php

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,38 @@
1313

1414
namespace Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Adapter;
1515

16+
use Pimcore\Bundle\StaticResolverBundle\Models\DataObject\DataObjectServiceResolverInterface;
17+
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\DetailDataInterface;
1618
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\Model\FieldContextData;
1719
use Pimcore\Bundle\StudioBackendBundle\DataObject\Data\SetterDataInterface;
1820
use Pimcore\Bundle\StudioBackendBundle\DataObject\Service\DataAdapterLoaderInterface;
1921
use Pimcore\Model\DataObject\ClassDefinition\Data;
22+
use Pimcore\Model\DataObject\ClassDefinition\Data\CalculatedValue as CalculatedValueDefinition;
2023
use Pimcore\Model\DataObject\Concrete;
24+
use Pimcore\Model\DataObject\Data\CalculatedValue;
25+
use Pimcore\Model\DataObject\Objectbrick\Data\AbstractData;
2126
use Pimcore\Model\UserInterface;
2227
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
2328

2429
/**
2530
* @internal
2631
*/
2732
#[AutoconfigureTag(DataAdapterLoaderInterface::ADAPTER_TAG)]
28-
final readonly class CalculatedValueAdapter implements SetterDataInterface
33+
final readonly class CalculatedValueAdapter implements SetterDataInterface, DetailDataInterface
2934
{
35+
private const string OWNER_TYPE_OBJECT = 'object';
36+
37+
private const string OWNER_TYPE_LOCALIZED_FIELD = 'localizedfield';
38+
39+
private const string OWNER_TYPE_OBJECT_BRICK = 'objectbrick';
40+
41+
private const string LOCALIZED_FIELDS_NAME = 'localizedfields';
42+
43+
public function __construct(
44+
private DataObjectServiceResolverInterface $dataObjectServiceResolver,
45+
) {
46+
}
47+
3048
public function getDataForSetter(
3149
Concrete $element,
3250
Data $fieldDefinition,
@@ -38,4 +56,72 @@ public function getDataForSetter(
3856
): null {
3957
return null;
4058
}
59+
60+
public function getDetailData(
61+
Concrete $object,
62+
mixed $value,
63+
Data $fieldDefinition,
64+
?FieldContextData $contextData = null,
65+
): ?string {
66+
if (!$fieldDefinition instanceof CalculatedValueDefinition) {
67+
return null;
68+
}
69+
70+
$calculatedValue = new CalculatedValue($fieldDefinition->getName());
71+
$this->applyContextualData($calculatedValue, $fieldDefinition, $contextData);
72+
73+
return $this->dataObjectServiceResolver->getCalculatedFieldValueForEditMode(
74+
$object,
75+
[],
76+
$calculatedValue,
77+
);
78+
}
79+
80+
private function applyContextualData(
81+
CalculatedValue $calculatedValue,
82+
CalculatedValueDefinition $fieldDefinition,
83+
?FieldContextData $contextData,
84+
): void {
85+
$contextObject = $contextData?->getContextObject();
86+
87+
if ($contextObject instanceof AbstractData) {
88+
$calculatedValue->setContextualData(
89+
self::OWNER_TYPE_OBJECT_BRICK,
90+
$contextObject->getFieldname(),
91+
$contextObject->getType(),
92+
$fieldDefinition->getName(),
93+
null,
94+
null,
95+
$fieldDefinition,
96+
);
97+
98+
return;
99+
}
100+
101+
$language = $contextData?->getLanguage();
102+
103+
if ($language !== null) {
104+
$calculatedValue->setContextualData(
105+
self::OWNER_TYPE_LOCALIZED_FIELD,
106+
self::LOCALIZED_FIELDS_NAME,
107+
null,
108+
$language,
109+
null,
110+
null,
111+
$fieldDefinition,
112+
);
113+
114+
return;
115+
}
116+
117+
$calculatedValue->setContextualData(
118+
self::OWNER_TYPE_OBJECT,
119+
$fieldDefinition->getName(),
120+
null,
121+
null,
122+
null,
123+
null,
124+
$fieldDefinition,
125+
);
126+
}
41127
}

0 commit comments

Comments
 (0)