Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4836c64
IBX-9594: Describe creating a custom DAM connector
dabrt Feb 26, 2025
36db157
Move code from md to code sample files
dabrt Feb 27, 2025
6e0c245
PHP & JS CS Fixes
dabrt Feb 27, 2025
d43bc7c
Fix formatting, template location and simplify the template
dabrt Feb 28, 2025
320fdc1
Implement reviewer comments
dabrt Mar 3, 2025
a503470
Modify template declaration
dabrt Mar 3, 2025
5e16efc
Modify Twig template declaration again
dabrt Mar 3, 2025
9c4d7eb
Fix variable
dabrt Mar 3, 2025
57c2af9
Add missing eof lines, remove obsolete script
dabrt Mar 4, 2025
af1c0ae
Merge branch 'master' into IBX-9594
dabrt Mar 5, 2025
363a917
Apply suggestions from code review
dabrt Mar 5, 2025
75a3bba
Add missing dependency to composer.json
dabrt Mar 5, 2025
377e1f0
Merge branch 'IBX-9594' of https://github.com/ibexa/documentation-dev…
dabrt Mar 5, 2025
9382168
Update namespace in php libraries
dabrt Mar 5, 2025
24faff1
Implement reviewer comments
dabrt Mar 11, 2025
d4f1423
PHP & JS CS Fixes
dabrt Mar 11, 2025
c4b841e
Merge branch 'master' into IBX-9594
adriendupuis Apr 2, 2025
ce26c9b
IBX-9594: Review of "Describe creating a custom DAM connector" (#2689)
adriendupuis Apr 9, 2025
0ccda82
Implement review comments
dabrt Apr 9, 2025
f869fd8
Apply suggestions from code review
dabrt Apr 15, 2025
2c1ad54
Apply suggestions from code review
dabrt Apr 15, 2025
3c963a0
Implement reviewer comments
dabrt Apr 15, 2025
eb2e201
One more slash fix
dabrt Apr 15, 2025
4bc5ab6
Fix a broken link
dabrt Apr 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions code_samples/back_office/images/config/packages/views.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
parameters:
ibexa.site_access.config.<scope>.image_asset_view_defaults:
full:
commons:
template: '@@ibexadesign/commons_asset_view.html.twig'
match:
SourceBasedViewMatcher: commons
default:
template: '@@ibexadesign/ui/field_type/image_asset_view.html.twig'
match: []
25 changes: 25 additions & 0 deletions code_samples/back_office/images/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,28 @@ services:
- '@ibexa.api.service.content'
- '@ibexa.field_type.ezbinaryfile.io_service'
- '@Ibexa\Core\Helper\TranslationHelper'

App\Connector\Dam\Handler\WikimediaCommonsHandler:
tags:
- { name: 'ibexa.platform.connector.dam.handler', source: 'commons' }

App\Connector\Dam\Transformation\WikimediaCommonsTransformationFactory:
tags:
- { name: 'ibexa.platform.connector.dam.transformation_factory', source: 'commons' }

commons_asset_variation_generator:
class: Ibexa\Connector\Dam\Variation\URLBasedVariationGenerator
tags:
- { name: 'ibexa.platform.connector.dam.variation_generator', source: 'commons' }

commons_search_tab:
class: Ibexa\Connector\Dam\View\Search\Tab\GenericSearchTab
public: false
arguments:
$identifier: 'commons'
$source: 'commons'
$name: 'Wikimedia Commons'
$searchFormType: 'Ibexa\Connector\Dam\Form\Search\GenericSearchType'
$formFactory: '@form.factory'
tags:
- { name: 'ibexa.admin_ui.tab', group: 'connector-dam-search' }
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php declare(strict_types=1);

namespace App\Connector\Dam\Handler;

use Ibexa\Contracts\Connector\Dam\Asset;
use Ibexa\Contracts\Connector\Dam\AssetCollection;
use Ibexa\Contracts\Connector\Dam\AssetIdentifier;
use Ibexa\Contracts\Connector\Dam\AssetMetadata;
use Ibexa\Contracts\Connector\Dam\AssetSource;
use Ibexa\Contracts\Connector\Dam\AssetUri;
use Ibexa\Contracts\Connector\Dam\Handler\Handler as HandlerInterface;
use Ibexa\Contracts\Connector\Dam\Search\AssetSearchResult;
use Ibexa\Contracts\Connector\Dam\Search\Query;

class WikimediaCommonsHandler implements HandlerInterface
{
public function search(Query $query, int $offset = 0, int $limit = 20): AssetSearchResult
{
$searchUrl = 'https://commons.wikimedia.org/w/api.php?action=query&list=search&format=json&srnamespace=6'
. '&srsearch=' . urlencode($query->getPhrase())
. '&sroffset=' . $offset
. '&srlimit=' . $limit
;

$jsonResponse = file_get_contents($searchUrl);
if ($jsonResponse === false) {
return new AssetSearchResult(0, new AssetCollection([]));
}

$response = json_decode($jsonResponse, true);
if (!isset($response['query']['search'])) {
return new AssetSearchResult(0, new AssetCollection([]));
}

$assets = [];
foreach ($response['query']['search'] as $result) {
$identifier = str_replace('File:', '', $result['title']);
$assets[] = $this->fetchAsset($identifier);
}

return new AssetSearchResult(
(int) ($response['query']['searchinfo']['totalhits'] ?? 0),
new AssetCollection($assets)
);
}

public function fetchAsset(string $id): Asset
{
$metadataUrl = 'https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&format=json'
. '&titles=File%3a' . urlencode($id)
;

$jsonResponse = file_get_contents($metadataUrl);
if ($jsonResponse === false) {
throw new \RuntimeException('Couldn\'t retrieve asset metadata');
}

$response = json_decode($jsonResponse, true);
if (!isset($response['query']['pages'])) {
throw new \RuntimeException('Couldn\'t parse asset metadata');
}

$pageData = array_values($response['query']['pages'])[0] ?? null;
if (!isset($pageData['imageinfo'][0]['extmetadata'])) {
throw new \RuntimeException('Couldn\'t parse image asset metadata');
}

$imageInfo = $pageData['imageinfo'][0]['extmetadata'];

return new Asset(
new AssetIdentifier($id),
new AssetSource('commons'),
new AssetUri('https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/' . urlencode($id)),
new AssetMetadata([
'page_url' => "https://commons.wikimedia.org/wiki/File:$id",
'author' => $imageInfo['Artist']['value'] ?? null,
'license' => $imageInfo['LicenseShortName']['value'] ?? null,
'license_url' => $imageInfo['LicenseUrl']['value'] ?? null,
])
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php declare(strict_types=1);

namespace App\Connector\Dam\Transformation;

use Ibexa\Contracts\Connector\Dam\Variation\Transformation;
use Ibexa\Contracts\Connector\Dam\Variation\TransformationFactory as TransformationFactoryInterface;

class WikimediaCommonsTransformationFactory implements TransformationFactoryInterface
{
/** @param array<string, scalar> $transformationParameters */
public function build(?string $transformationName = null, array $transformationParameters = []): Transformation
{
if (null === $transformationName) {
return new Transformation(null, array_map('strval', $transformationParameters));
}

$transformations = $this->buildAll();

if (array_key_exists($transformationName, $transformations)) {
return $transformations[$transformationName];
}

throw new \InvalidArgumentException(sprintf('Unknown transformation "%s".', $transformationName));
}

public function buildAll(): array
{
return [
'reference' => new Transformation('reference', []),
'tiny' => new Transformation('tiny', ['width' => '30']),
'small' => new Transformation('small', ['width' => '100']),
'medium' => new Transformation('medium', ['width' => '200']),
'large' => new Transformation('large', ['width' => '300']),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends '@ibexadesign/ui/field_type/image_asset_view.html.twig' %}

{% block asset_preview %}
{{ parent() }}
<div>
<a href="{{ asset.assetMetadata.page_url }}">Image</a>
{% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author }}{% endif %}
{% if asset.assetMetadata.license and asset.assetMetadata.license_url %}
under <a href="{{ asset.assetMetadata.license_url }}">{{ asset.assetMetadata.license }}</a>
{% endif %}.
</div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ezimageasset.dam_asset.page_url: Image page
ezimageasset.dam_asset.author: Image author
ezimageasset.dam_asset.license: License
ezimageasset.dam_asset.license_url: License page
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
"ibexa/solr": "5.0.x-dev",
"ibexa/version-comparison": "5.0.x-dev",
"league/oauth2-google": "^4.0",
"ibexa/core-search": "~5.0.x-dev"
"ibexa/core-search": "~5.0.x-dev",
"ibexa/connector-dam": "~5.0.x-dev"
},
"scripts": {
"fix-cs": "php-cs-fixer fix --config=.php-cs-fixer.php -v --show-progress=dots",
Expand Down
2 changes: 1 addition & 1 deletion docs/administration/back_office/add_user_setting.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ You can order the settings in the **User** menu by setting their `priority`.
`group` indicates the group that the setting is placed in.
It can be one of the built-in groups, or a custom one.

To create a custom setting group, create an `App\Setting\Group\MyGroup.php` file:
To create a custom setting group, create an `App/Setting/Group/MyGroup.php` file:

``` php
[[= include_file('code_samples/back_office/settings/src/Setting/Group/MyGroup.php') =]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ You can format date and time by using the following services:
- `@ibexa.user.settings.full_date_format.formatter`
- `@ibexa.user.settings.full_time_format.formatter`

To use them, create an `src\Service\MyService.php` file containing:
To use them, create an `src/Service/MyService.php` file containing:

``` php
<?php
Expand Down
2 changes: 1 addition & 1 deletion docs/commerce/storefront/extend_storefront.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition: commerce

## Built-in menus

With the `ibexa\storefront` package come the following built-in menus:
With the `ibexa/storefront` package come the following built-in menus:

| Item | Value | Description |
|------------|----------|---------|
Expand Down
124 changes: 122 additions & 2 deletions docs/content_management/images/add_image_asset_from_dam.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@

The configuration for each connector depends on the requirements of the specific DAM system.

You can create your own connectors, or use the provided example DAM connector for [Unsplash](https://unsplash.com/).
You can use the provided example DAM connector for [Unsplash](https://unsplash.com/), or [extend DAM support by creating a connector of your choice](#extend-dam-support-by-adding-custom-connector).

To add the Unsplash connector to your system add the `ibexa/connector-unsplash` bundle to your installation.
To add the Unsplash connector to your system, add the `ibexa/connector-unsplash` bundle to your installation.

## Add Image Asset in Page Builder [[% include 'snippets/experience_badge.md' %]] [[% include 'snippets/commerce_badge.md' %]]

Expand Down Expand Up @@ -83,3 +83,123 @@
Now, when you use the Embed block in the Page Builder, you should see a DAM Image.

For more information about block customization (defined templates, variations), see [Create custom block](4_create_a_custom_block.md).

## Extend DAM support by adding custom connector

To extend the DAM support built into [[= product_name =]], you must create a custom handler and transformation factory.

!!! note "Wikimedia Commons licensing"

Before you use Wikimedia Commons assets in a production environment, ensure that you comply with their [license requirements](https://commons.wikimedia.org/wiki/Commons:Reusing_content_outside_Wikimedia#How_to_comply_with_a_file's_license_requirements).

### Create DAM handler

This class handles searching through Wikimedia Commons for images and fetching image assets.

In `src/Connector/Dam/Handler` folder, create the `WikimediaCommonsHandler.php` file that resembles the following example,
which implements [`search()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-Handler-Handler.html#method_search) to query the server

Check failure on line 100 in docs/content_management/images/add_image_asset_from_dam.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/images/add_image_asset_from_dam.md#L100

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/content_management/images/add_image_asset_from_dam.md", "range": {"start": {"line": 100, "column": 70}}}, "severity": "ERROR"}
and [`fetchAsset()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-Handler-Handler.html#method_fetchAsset) to return asset objects:

Check failure on line 101 in docs/content_management/images/add_image_asset_from_dam.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/images/add_image_asset_from_dam.md#L101

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/content_management/images/add_image_asset_from_dam.md", "range": {"start": {"line": 101, "column": 61}}}, "severity": "ERROR"}

```php
[[= include_file('code_samples/back_office/images/src/Connector/Dam/Handler/WikimediaCommonsHandler.php') =]]
```

Then, in `config/services.yaml`, register the handler as a service:

```yaml
[[= include_file('code_samples/back_office/images/config/services.yaml', 9, 12) =]]
```

The `source` parameter passed in the tag is an identifier of this new DAM connector and is used in other places to glue elements together.

### Create transformation factory

The transformation factory maps [[= product_name =]]'s image variations to corresponding variations from Wikimedia Commons.

In `src/Connector/Dam/Transformation` folder, create the `WikimediaCommonsTransformationFactory.php` file that resembles the following example,
which implements the [`TransformationFactory` interface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-Variation-TransformationFactory.html):

Check failure on line 120 in docs/content_management/images/add_image_asset_from_dam.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/images/add_image_asset_from_dam.md#L120

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/content_management/images/add_image_asset_from_dam.md", "range": {"start": {"line": 120, "column": 97}}}, "severity": "ERROR"}

```php
[[= include_file('code_samples/back_office/images/src/Connector/Dam/Transformation/WikimediaCommonsTransformationFactory.php') =]]
```

Then register the transformation factory as a service:

```yaml
[[= include_file('code_samples/back_office/images/config/services.yaml', 13, 16) =]]
```

### Register variations generator

The variation generator applies map parameters coming from the transformation factory to build a fetch request to the DAM.
The solution uses the built-in `URLBasedVariationGenerator` class, which adds all the map elements as query parameters to the request.

For example, for an asset with the ID `Ibexa_Logo.svg`, the handler generates the Asset with [`AssetUri's URL`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Connector-Dam-AssetUri.html#method_getPath) equal to:

Check failure on line 137 in docs/content_management/images/add_image_asset_from_dam.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/images/add_image_asset_from_dam.md#L137

[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'
Raw output
{"message": "[Ibexa.VariablesGlobal] Use global variable '[[= product_name_base =]]' instead of 'Ibexa'", "location": {"path": "docs/content_management/images/add_image_asset_from_dam.md", "range": {"start": {"line": 137, "column": 152}}}, "severity": "ERROR"}

`https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/Ibexa_Logo.svg`

When the user requests a specific variation of the image, for example, "large", the variation generator modifies the URL and returns it in the following form:

`https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/Ibexa_Logo.svg&width=300`

For this to happen, register the variations generator as a service available for the custom `commons` connector:

```yaml
[[= include_file('code_samples/back_office/images/config/services.yaml', 17, 21) =]]
```

### Configure tab for "Select from DAM" modal

To enable selecting an image from the DAM system, a modal window pops up with tabs and panels that contain different search interfaces.

In this example, the search only uses the main text input.
The tab and its corresponding panel are a service created by combining existing components, like in the case of other [back office tabs](back_office_tabs.md).

The `commons_search_tab` service uses the `GenericSearchTab` class as a base, and the `GenericSearchType` form for search input.
It is linked to the `commons` DAM source and uses the identifier `commons`.

Check warning on line 159 in docs/content_management/images/add_image_asset_from_dam.md

View workflow job for this annotation

GitHub Actions / vale

[vale] docs/content_management/images/add_image_asset_from_dam.md#L159

[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.
Raw output
{"message": "[Ibexa.EOLWhitespace] Remove whitespace characters from the end of the line.", "location": {"path": "docs/content_management/images/add_image_asset_from_dam.md", "range": {"start": {"line": 159, "column": 76}}}, "severity": "WARNING"}
The DAM search tab is registered in the `connector-dam-search` [tab group](back_office_tabs.md#tab-groups) using the `ibexa.admin_ui.tab` tag.


```yaml
[[= include_file('code_samples/back_office/images/config/services.yaml', 22, 33) =]]
```

### Create Twig template

The template defines how images that come from Wikimedia Commons are displayed.

In `templates/themes/standard/`, add the `commons_asset_view.html.twig` file that resembles the following example:

```html+twig
[[= include_file('code_samples/back_office/images/templates/themes/standard/commons_asset_view.html.twig') =]]
```

Then, register the template and a fallback template in configuration files.
Replace `<scope>` with an [appropriate value](siteaccess_aware_configuration.md) that designates the SiteAccess or SiteAccess group, for example, `default` to use the template everywhere, including the back office:

```yaml
[[= include_file('code_samples/back_office/images/config/packages/views.yaml') =]]
```

### Provide back office translation

When the image asset field is displayed in the back office, a table of metadata follows.
This example uses new fields, so you need to provide translations for their labels, for example, in `translations/ibexa_fieldtypes_preview.en.yaml`:

```yaml
[[= include_file('code_samples/back_office/images/translations/ibexa_fieldtypes_preview.en.yaml') =]]
```

### Add Wikimedia Commons connection to DAM configuration

You can now configure a connection with Wikimedia Commons under the `ibexa.system.<scope>.content.dam` key using the source identifier `commons`:

```yaml
ibexa:
system:
default:
content:
dam: [ commons ]
```

Once you clear the cache, you can search for images to see whether images from the newly configured DAM are displayed correctly, including their variations.
2 changes: 1 addition & 1 deletion docs/content_management/images/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ It points to a custom controller that handles the downloading of the SVG file.
The controller's definition (that you place in the `config/services.yaml` file under `services` key) and implementation are as follows:

```yaml
[[= include_file('code_samples/back_office/images/config/services.yaml') =]]
[[= include_file('code_samples/back_office/images/config/services.yaml', 0, 8) =]]
```

```php
Expand Down
2 changes: 1 addition & 1 deletion docs/update_and_migration/from_3.3/update_from_3.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ CREATE INDEX idx_workflow_name ON ezeditorialworkflow_workflows(workflow_name);
#### Enable Commerce features

Commerce features in Experience and Content editions are disabled by default.
If you use these features, after the update enable Commerce features by going to `config\packages\ecommerce.yaml`
If you use these features, after the update enable Commerce features by going to `config/packages/ecommerce.yaml`
and setting the following:

``` yaml
Expand Down
Loading