Skip to content

Conversation

adriendupuis
Copy link
Contributor

@adriendupuis adriendupuis commented Apr 2, 2025

Question Answer
JIRA Ticket
Versions
Edition
  • Fix PHPStan reported signature issues: throws exceptions instead of returning null.
  • Fix other PHPStan issues.
  • Fix services configurations.
  • Add "Commons" tab to "Select from DAM">"Select Image Asset" modal:
    • Add tab service declaration (be aware that on 4.6.18 the tab is not displayed but it works if Commons is the first DAM connector. See ibexa/connector-dam#72.);
    • Document how to set up a tab for custom connector.
  • Links to PHP API Ref.
  • Fix vale "[Ibexa.Wordy] Remove 'respectively' and list each option instead."
  • Add translations for BO field preview.
  • Emphase a bit about the commons source identifier importance.

Preview: Extend DAM support by adding custom connector

Checklist

  • Text renders correctly
  • Text has been checked with vale
  • Description metadata is up to date
  • Redirects cover removed/moved pages
  • Code samples are working
  • PHP code samples have been fixed with PHP CS fixer
  • Added link to this PR in relevant JIRA ticket or code PR

Copy link

github-actions bot commented Apr 2, 2025

Preview of modified Markdown:

@adriendupuis adriendupuis changed the base branch from master to IBX-9594 April 2, 2025 15:44
PHPStan: "If condition is always true."
Parameter #1 $key of function array_key_exists expects int|string, string|null given.
Parameter #2 $transformationProperties of class Ibexa\Contracts\Connector\Dam\Variation\Transformation constructor expects array<string>, array<string, bool|float|int|string> given.
Parameter #2 $transformationProperties of class Ibexa\Contracts\Connector\Dam\Variation\Transformation constructor expects array<string>, array<string, bool|float|int|string> given.
Comment on lines +10 to +15
/** @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));
}
Copy link
Contributor Author

@adriendupuis adriendupuis Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Unsplash connector way to deal with this is "ignore" 😅🤞

@adriendupuis adriendupuis marked this pull request as ready for review April 3, 2025 12:02
@adriendupuis adriendupuis requested a review from dabrt April 3, 2025 12:02
Copy link

github-actions bot commented Apr 3, 2025

code_samples/ change report

Before (on target branch)After (in current PR)

code_samples/back_office/images/config/packages/views.yaml


code_samples/back_office/images/config/packages/views.yaml

docs/content_management/images/add_image_asset_from_dam.md@159:```yaml
docs/content_management/images/add_image_asset_from_dam.md@160:[[= include_file('code_samples/back_office/images/config/packages/views.yaml') =]]
docs/content_management/images/add_image_asset_from_dam.md@161:```
docs/content_management/images/add_image_asset_from_dam.md@182:```yaml
docs/content_management/images/add_image_asset_from_dam.md@183:[[= include_file('code_samples/back_office/images/config/packages/views.yaml') =]]
docs/content_management/images/add_image_asset_from_dam.md@184:```

001⫶parameters:

001⫶parameters:
002⫶    ibexa.<site_access>.image_asset_view_defaults:
002⫶    ibexa.site_access.config.<scope>.image_asset_view_defaults:
003⫶        full:
004⫶ commons:
005⫶ template: '@@ibexadesign/commons_asset_view.html.twig'
006⫶ match:
007⫶ SourceBasedViewMatcher: commons
008⫶ default:
009⫶ template: '@@ibexadesign/ui/field_type/image_asset_view.html.twig'
010⫶ match: []


code_samples/back_office/images/config/services.yaml

003⫶        full:
004⫶ commons:
005⫶ template: '@@ibexadesign/commons_asset_view.html.twig'
006⫶ match:
007⫶ SourceBasedViewMatcher: commons
008⫶ default:
009⫶ template: '@@ibexadesign/ui/field_type/image_asset_view.html.twig'
010⫶ match: []


code_samples/back_office/images/config/services.yaml

docs/content_management/images/add_image_asset_from_dam.md@107:```yaml
docs/content_management/images/add_image_asset_from_dam.md@108:[[= include_file('code_samples/back_office/images/config/services.yaml', 9, 14) =]]
docs/content_management/images/add_image_asset_from_dam.md@109:```
docs/content_management/images/add_image_asset_from_dam.md@109:```yaml
docs/content_management/images/add_image_asset_from_dam.md@110:[[= include_file('code_samples/back_office/images/config/services.yaml', 9, 12) =]]
docs/content_management/images/add_image_asset_from_dam.md@111:```

001⫶ App\Connector\Dam\Handler\WikimediaCommonsHandler:

001⫶ App\Connector\Dam\Handler\WikimediaCommonsHandler:
002⫶        arguments:
003⫶ $httpClient: '@http_client'
004⫶ tags:
005⫶ - { name: 'ibexa.platform.connector.dam.handler', source: 'commons' }

docs/content_management/images/add_image_asset_from_dam.md@124:```yaml
docs/content_management/images/add_image_asset_from_dam.md@125:[[= include_file('code_samples/back_office/images/config/services.yaml', 15, 20) =]]
docs/content_management/images/add_image_asset_from_dam.md@126:```

001⫶ App\Connector\Dam\Transformation\TransformationFactory:
002⫶ arguments:
003⫶ $httpClient: '@http_client'
004⫶ tags:
005⫶ - { name: 'ibexa.platform.connector.dam.transformation_factory', source: 'commons' }

docs/content_management/images/add_image_asset_from_dam.md@143:```yaml
docs/content_management/images/add_image_asset_from_dam.md@144:[[= include_file('code_samples/back_office/images/config/services.yaml', 21, 25) =]]
docs/content_management/images/add_image_asset_from_dam.md@145:```
002⫶        tags:
003⫶ - { name: 'ibexa.platform.connector.dam.handler', source: 'commons' }

docs/content_management/images/add_image_asset_from_dam.md@128:```yaml
docs/content_management/images/add_image_asset_from_dam.md@129:[[= include_file('code_samples/back_office/images/config/services.yaml', 13, 16) =]]
docs/content_management/images/add_image_asset_from_dam.md@130:```

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

docs/content_management/images/add_image_asset_from_dam.md@147:```yaml
docs/content_management/images/add_image_asset_from_dam.md@148:[[= include_file('code_samples/back_office/images/config/services.yaml', 17, 21) =]]
docs/content_management/images/add_image_asset_from_dam.md@149:```

001⫶ commons_asset_variation_generator:
002⫶ class: Ibexa\Connector\Dam\Variation\URLBasedVariationGenerator
003⫶ tags:

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

docs/content_management/images/add_image_asset_from_dam.md@165:```yaml
docs/content_management/images/add_image_asset_from_dam.md@166:[[= include_file('code_samples/back_office/images/config/services.yaml', 22, 33) =]]
docs/content_management/images/add_image_asset_from_dam.md@167:```

001⫶ commons_search_tab:
002⫶ class: Ibexa\Platform\Connector\Dam\View\Search\Tab\GenericSearchTab
003⫶ public: false
004⫶ arguments:
005⫶ $identifier: 'commons'
006⫶ $source: 'commons'
007⫶ $name: 'Wikimedia Commons'
008⫶ $searchFormType: 'Ibexa\Platform\Connector\Dam\Form\Search\GenericSearchType'
009⫶ $formFactory: '@form.factory'
010⫶ tags:
011⫶ - { name: 'ibexa.admin_ui.tab', group: 'connector-dam-search' }

docs/content_management/images/images.md@217:```yaml
docs/content_management/images/images.md@218:[[= include_file('code_samples/back_office/images/config/services.yaml', 0, 8) =]]
docs/content_management/images/images.md@219:```

001⫶services:
002⫶ # ...
003⫶ App\Controller\SvgController:
004⫶ public: true
005⫶ arguments:
006⫶ - '@ibexa.api.service.content'
007⫶ - '@ibexa.field_type.ezbinaryfile.io_service'
008⫶ - '@Ibexa\Core\Helper\TranslationHelper'


code_samples/back_office/images/src/Connector/Dam/Handler/WikimediaCommonsHandler.php


docs/content_management/images/images.md@217:```yaml
docs/content_management/images/images.md@218:[[= include_file('code_samples/back_office/images/config/services.yaml', 0, 8) =]]
docs/content_management/images/images.md@219:```

001⫶services:
002⫶ # ...
003⫶ App\Controller\SvgController:
004⫶ public: true
005⫶ arguments:
006⫶ - '@ibexa.api.service.content'
007⫶ - '@ibexa.field_type.ezbinaryfile.io_service'
008⫶ - '@Ibexa\Core\Helper\TranslationHelper'


code_samples/back_office/images/src/Connector/Dam/Handler/WikimediaCommonsHandler.php

docs/content_management/images/add_image_asset_from_dam.md@101:```php
docs/content_management/images/add_image_asset_from_dam.md@102:[[= include_file('code_samples/back_office/images/src/Connector/Dam/Handler/WikimediaCommonsHandler.php') =]]
docs/content_management/images/add_image_asset_from_dam.md@103:```
docs/content_management/images/add_image_asset_from_dam.md@103:```php
docs/content_management/images/add_image_asset_from_dam.md@104:[[= include_file('code_samples/back_office/images/src/Connector/Dam/Handler/WikimediaCommonsHandler.php') =]]
docs/content_management/images/add_image_asset_from_dam.md@105:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Connector\Dam\Handler;
004⫶
005⫶use Ibexa\Contracts\Connector\Dam\Asset;
006⫶use Ibexa\Contracts\Connector\Dam\AssetCollection;
007⫶use Ibexa\Contracts\Connector\Dam\AssetIdentifier;
008⫶use Ibexa\Contracts\Connector\Dam\AssetMetadata;
009⫶use Ibexa\Contracts\Connector\Dam\AssetSource;
010⫶use Ibexa\Contracts\Connector\Dam\AssetUri;
011⫶use Ibexa\Contracts\Connector\Dam\Handler\Handler as HandlerInterface;
012⫶use Ibexa\Contracts\Connector\Dam\Search\AssetSearchResult;
013⫶use Ibexa\Contracts\Connector\Dam\Search\Query;
014⫶
015⫶class WikimediaCommonsHandler implements HandlerInterface
016⫶{
017⫶ public function search(Query $query, int $offset = 0, int $limit = 20): AssetSearchResult
018⫶ {
019⫶ $searchUrl = 'https://commons.wikimedia.org/w/api.php?action=query&list=search&format=json&srnamespace=6'
020⫶ . '&srsearch=' . urlencode($query->getPhrase())
021⫶ . '&sroffset=' . $offset
022⫶ . '&srlimit=' . $limit
023⫶ ;
024⫶
025⫶ $jsonResponse = file_get_contents($searchUrl);
026⫶ if ($jsonResponse === false) {
027⫶ return new AssetSearchResult(0, new AssetCollection([]));
028⫶ }
029⫶
030⫶ $response = json_decode($jsonResponse, true);
031⫶ if (!isset($response['query']['search'])) {
032⫶ return new AssetSearchResult(0, new AssetCollection([]));
033⫶ }
034⫶
035⫶ $assets = [];
036⫶ foreach ($response['query']['search'] as $result) {
037⫶ $identifier = str_replace('File:', '', $result['title']);

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Connector\Dam\Handler;
004⫶
005⫶use Ibexa\Contracts\Connector\Dam\Asset;
006⫶use Ibexa\Contracts\Connector\Dam\AssetCollection;
007⫶use Ibexa\Contracts\Connector\Dam\AssetIdentifier;
008⫶use Ibexa\Contracts\Connector\Dam\AssetMetadata;
009⫶use Ibexa\Contracts\Connector\Dam\AssetSource;
010⫶use Ibexa\Contracts\Connector\Dam\AssetUri;
011⫶use Ibexa\Contracts\Connector\Dam\Handler\Handler as HandlerInterface;
012⫶use Ibexa\Contracts\Connector\Dam\Search\AssetSearchResult;
013⫶use Ibexa\Contracts\Connector\Dam\Search\Query;
014⫶
015⫶class WikimediaCommonsHandler implements HandlerInterface
016⫶{
017⫶ public function search(Query $query, int $offset = 0, int $limit = 20): AssetSearchResult
018⫶ {
019⫶ $searchUrl = 'https://commons.wikimedia.org/w/api.php?action=query&list=search&format=json&srnamespace=6'
020⫶ . '&srsearch=' . urlencode($query->getPhrase())
021⫶ . '&sroffset=' . $offset
022⫶ . '&srlimit=' . $limit
023⫶ ;
024⫶
025⫶ $jsonResponse = file_get_contents($searchUrl);
026⫶ if ($jsonResponse === false) {
027⫶ return new AssetSearchResult(0, new AssetCollection([]));
028⫶ }
029⫶
030⫶ $response = json_decode($jsonResponse, true);
031⫶ if (!isset($response['query']['search'])) {
032⫶ return new AssetSearchResult(0, new AssetCollection([]));
033⫶ }
034⫶
035⫶ $assets = [];
036⫶ foreach ($response['query']['search'] as $result) {
037⫶ $identifier = str_replace('File:', '', $result['title']);
038⫶            $asset = $this->fetchAsset($identifier);
039⫶ if ($asset) {
040⫶ $assets[] = $asset;
041⫶ }
042⫶ }
043⫶
044⫶ return new AssetSearchResult(
045⫶ (int) ($response['query']['searchinfo']['totalhits'] ?? 0),
046⫶ new AssetCollection($assets)
047⫶ );
048⫶ }
049⫶
050⫶ public function fetchAsset(string $id): ?Asset
051⫶ {
052⫶ $metadataUrl = 'https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&format=json'
053⫶ . '&titles=File%3a' . urlencode($id)
054⫶ ;
055⫶
056⫶ $jsonResponse = file_get_contents($metadataUrl);
057⫶ if ($jsonResponse === false) {
058⫶ return null;
059⫶ }
060⫶
061⫶ $response = json_decode($jsonResponse, true);
062⫶ if (!isset($response['query']['pages'])) {
063⫶ return null;
064⫶ }
065⫶
066⫶ $pageData = array_values($response['query']['pages'])[0] ?? null;
067⫶ if (!isset($pageData['imageinfo'][0]['extmetadata'])) {
068⫶ return null;
069⫶ }
070⫶
071⫶ $imageInfo = $pageData['imageinfo'][0]['extmetadata'];
072⫶
073⫶ return new Asset(
074⫶ new AssetIdentifier($id),
075⫶ new AssetSource('commons'),
076⫶ new AssetUri('https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/' . urlencode($id)),
077⫶ new AssetMetadata([
078⫶ 'page_url' => "https://commons.wikimedia.org/wiki/File:$id",
079⫶ 'author' => $imageInfo['Artist']['value'] ?? null,
080⫶ 'license' => $imageInfo['LicenseShortName']['value'] ?? null,
081⫶ 'license_url' => $imageInfo['LicenseUrl']['value'] ?? null,
082⫶ ])
083⫶ );
084⫶ }
085⫶}
038⫶            $assets[] = $this->fetchAsset($identifier);
039⫶ }
040⫶
041⫶ return new AssetSearchResult(
042⫶ (int) ($response['query']['searchinfo']['totalhits'] ?? 0),
043⫶ new AssetCollection($assets)
044⫶ );
045⫶ }
046⫶
047⫶ public function fetchAsset(string $id): Asset
048⫶ {
049⫶ $metadataUrl = 'https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&format=json'
050⫶ . '&titles=File%3a' . urlencode($id)
051⫶ ;
052⫶
053⫶ $jsonResponse = file_get_contents($metadataUrl);
054⫶ if ($jsonResponse === false) {
055⫶ throw new \RuntimeException('Couldn\'t retrieve asset metadata');
056⫶ }
057⫶
058⫶ $response = json_decode($jsonResponse, true);
059⫶ if (!isset($response['query']['pages'])) {
060⫶ throw new \RuntimeException('Couldn\'t parse asset metadata');
061⫶ }
062⫶
063⫶ $pageData = array_values($response['query']['pages'])[0] ?? null;
064⫶ if (!isset($pageData['imageinfo'][0]['extmetadata'])) {
065⫶ throw new \RuntimeException('Couldn\'t parse image asset metadata');
066⫶ }
067⫶
068⫶ $imageInfo = $pageData['imageinfo'][0]['extmetadata'];
069⫶
070⫶ return new Asset(
071⫶ new AssetIdentifier($id),
072⫶ new AssetSource('commons'),
073⫶ new AssetUri('https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/' . urlencode($id)),
074⫶ new AssetMetadata([
075⫶ 'page_url' => "https://commons.wikimedia.org/wiki/File:$id",
076⫶ 'author' => $imageInfo['Artist']['value'] ?? null,
077⫶ 'license' => $imageInfo['LicenseShortName']['value'] ?? null,
078⫶ 'license_url' => $imageInfo['LicenseUrl']['value'] ?? null,
079⫶ ])
080⫶ );
081⫶ }
082⫶}


code_samples/back_office/images/src/Connector/Dam/Transformation/WikimediaCommonsTransformationFactory.php



code_samples/back_office/images/src/Connector/Dam/Transformation/WikimediaCommonsTransformationFactory.php

docs/content_management/images/add_image_asset_from_dam.md@118:```php
docs/content_management/images/add_image_asset_from_dam.md@119:[[= include_file('code_samples/back_office/images/src/Connector/Dam/Transformation/WikimediaCommonsTransformationFactory.php') =]]
docs/content_management/images/add_image_asset_from_dam.md@120:```
docs/content_management/images/add_image_asset_from_dam.md@122:```php
docs/content_management/images/add_image_asset_from_dam.md@123:[[= include_file('code_samples/back_office/images/src/Connector/Dam/Transformation/WikimediaCommonsTransformationFactory.php') =]]
docs/content_management/images/add_image_asset_from_dam.md@124:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Connector\Dam\Transformation;
004⫶
005⫶use Ibexa\Contracts\Connector\Dam\Variation\Transformation;
006⫶use Ibexa\Contracts\Connector\Dam\Variation\TransformationFactory as TransformationFactoryInterface;
007⫶
008⫶class WikimediaCommonsTransformationFactory implements TransformationFactoryInterface
009⫶{

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Connector\Dam\Transformation;
004⫶
005⫶use Ibexa\Contracts\Connector\Dam\Variation\Transformation;
006⫶use Ibexa\Contracts\Connector\Dam\Variation\TransformationFactory as TransformationFactoryInterface;
007⫶
008⫶class WikimediaCommonsTransformationFactory implements TransformationFactoryInterface
009⫶{
010⫶    public function build(?string $transformationName = null, array $transformationParameters = []): ?Transformation
011⫶ {
012⫶ $transformations = $this->buildAll();
013⫶
014⫶ return $transformations[$transformationName] ?? null;
015⫶ }
010⫶    /** @param array<string, scalar> $transformationParameters */
011⫶ public function build(?string $transformationName = null, array $transformationParameters = []): Transformation
012⫶ {
013⫶ if (null === $transformationName) {
014⫶ return new Transformation(null, array_map('strval', $transformationParameters));
015⫶ }
016⫶
016⫶
017⫶    public function buildAll(): array
018⫶ {
019⫶ return [
020⫶ 'reference' => new Transformation('reference', []),
021⫶ 'tiny' => new Transformation('tiny', ['width' => '30']),
022⫶ 'small' => new Transformation('small', ['width' => '100']),
023⫶ 'medium' => new Transformation('medium', ['width' => '200']),
024⫶ 'large' => new Transformation('large', ['width' => '300']),
025⫶ ];
026⫶ }
027⫶}
017⫶        $transformations = $this->buildAll();
018⫶
019⫶ if (array_key_exists($transformationName, $transformations)) {
020⫶ return $transformations[$transformationName];
021⫶ }
022⫶
023⫶ throw new \InvalidArgumentException(sprintf('Unknown transformation "%s".', $transformationName));
024⫶ }
025⫶
026⫶ public function buildAll(): array
027⫶ {
028⫶ return [
029⫶ 'reference' => new Transformation('reference', []),
030⫶ 'tiny' => new Transformation('tiny', ['width' => '30']),
031⫶ 'small' => new Transformation('small', ['width' => '100']),
032⫶ 'medium' => new Transformation('medium', ['width' => '200']),
033⫶ 'large' => new Transformation('large', ['width' => '300']),
034⫶ ];
035⫶ }
036⫶}


code_samples/back_office/images/translations/ibexa_fieldtypes_preview.en.yaml



code_samples/back_office/images/translations/ibexa_fieldtypes_preview.en.yaml

docs/content_management/images/add_image_asset_from_dam.md@192:```yaml
docs/content_management/images/add_image_asset_from_dam.md@193:[[= include_file('code_samples/back_office/images/translations/ibexa_fieldtypes_preview.en.yaml') =]]
docs/content_management/images/add_image_asset_from_dam.md@194:```

001⫶ezimageasset.dam_asset.page_url: Image page
002⫶ezimageasset.dam_asset.author: Image author
003⫶ezimageasset.dam_asset.license: License
004⫶ezimageasset.dam_asset.license_url: License page

Download colorized diff

Co-authored-by: Tomasz Dąbrowski <64841871+dabrt@users.noreply.github.com>
@adriendupuis adriendupuis merged commit ce26c9b into IBX-9594 Apr 9, 2025
5 of 7 checks passed
@adriendupuis adriendupuis deleted the IBX-9594-review branch April 9, 2025 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants