Skip to content

Updated Commons DAM example to work - User-agent is missing - #3028

Merged
adriendupuis merged 3 commits into
5.0from
dam_commons_updated
Jan 30, 2026
Merged

adriendupuis merged 3 commits into
5.0from
dam_commons_updated

Conversation

@vidarl

@vidarl vidarl commented Jan 30, 2026

Copy link
Copy Markdown
Contributor
Question Answer
JIRA Ticket N/A
Versions 4.6, 5.0
Edition Headless, Experience, Commerce

An User-Agent header is required, or Commons will only reply with 403 access denied now.

Also, small improvement to view template:
image

vs

image

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

<div>
<a href="{{ asset.assetMetadata.page_url }}">Image</a>
{% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author }}{% endif %}
{% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author|striptags }}{% endif %}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we trust that the URL coming from Commons cannot contain bad things. This will display the author as link:

Suggested change
{% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author|striptags }}{% endif %}
{% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author|raw }}{% endif %}

@mnocon mnocon Jan 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a 3rd party site so we can never be 100% sure - and this template is just an example, this will be easy to modify.

I'd keep it as it (striptags instead of raw), better safe than sorry

@adriendupuis adriendupuis Jan 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

https://commons.wikimedia.org/wiki/Commons:Reusing_content_outside_Wikimedia and https://commons.wikimedia.org/wiki/Commons:Credit_line#Credit_lines_required_by_license doesn't seem to specifically require a link on the author, I guess the link to the image page is enough. To strip the author link shouldn't be an legal issue. In short, I'm OK with this change.

@vidarl
vidarl requested review from adriendupuis and dabrt January 30, 2026 11:42

class WikimediaCommonsHandler implements HandlerInterface
{
private const USER_AGENT = 'Ibexa DXP Commons Dam Connector';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe you want to inject Product name somehow . Wasn't sure if that worked in code snippets so hardcoded "Ibexa DXP"...

@github-actions

Copy link
Copy Markdown

Preview of modified files: no change to preview.

@mnocon mnocon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you Vidar!

<div>
<a href="{{ asset.assetMetadata.page_url }}">Image</a>
{% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author }}{% endif %}
{% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author|striptags }}{% endif %}

@mnocon mnocon Jan 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a 3rd party site so we can never be 100% sure - and this template is just an example, this will be easy to modify.

I'd keep it as it (striptags instead of raw), better safe than sorry

@github-actions

Copy link
Copy Markdown

code_samples/ change report

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

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

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

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⫶{

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

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

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⫶ $assets[] = $this->fetchAsset($identifier);
017⫶    private const USER_AGENT = 'Ibexa DXP Commons Dam Connector';
018⫶
019⫶ public function search(Query $query, int $offset = 0, int $limit = 20): AssetSearchResult
020⫶ {
021⫶ $searchUrl = 'https://commons.wikimedia.org/w/api.php?action=query&list=search&format=json&srnamespace=6'
022⫶ . '&srsearch=' . urlencode($query->getPhrase())
023⫶ . '&sroffset=' . $offset
024⫶ . '&srlimit=' . $limit
025⫶ ;
026⫶
027⫶ $opts = [
028⫶ 'http' => [
029⫶ 'method' => 'GET',
030⫶ 'header' => [
031⫶ 'User-Agent: ' . self::USER_AGENT,
032⫶ ],
033⫶ ],
034⫶ ];
035⫶
036⫶ $jsonResponse = file_get_contents($searchUrl, false, stream_context_create($opts));
037⫶ if ($jsonResponse === false) {
038⫶ return new AssetSearchResult(0, new AssetCollection([]));
039⫶        }
040⫶
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⫶ }
041⫶        $response = json_decode($jsonResponse, true);
042⫶ if (!isset($response['query']['search'])) {
043⫶ return new AssetSearchResult(0, new AssetCollection([]));
044⫶ }
045⫶
046⫶ $assets = [];
047⫶ foreach ($response['query']['search'] as $result) {
048⫶ $identifier = str_replace('File:', '', $result['title']);
049⫶ $assets[] = $this->fetchAsset($identifier);
050⫶ }
051⫶
052⫶ return new AssetSearchResult(
053⫶ (int) ($response['query']['searchinfo']['totalhits'] ?? 0),
054⫶ new AssetCollection($assets)
055⫶ );
056⫶ }
057⫶
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⫶}
058⫶    public function fetchAsset(string $id): Asset
059⫶ {
060⫶ $metadataUrl = 'https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=extmetadata&format=json'
061⫶ . '&titles=File%3a' . urlencode($id)
062⫶ ;
063⫶
064⫶ $opts = [
065⫶ 'http' => [
066⫶ 'method' => 'GET',
067⫶ 'header' => [
068⫶ 'User-Agent: ' . self::USER_AGENT,
069⫶ ],
070⫶ ],
071⫶ ];
072⫶
073⫶ $jsonResponse = file_get_contents($metadataUrl, false, stream_context_create($opts));
074⫶ if ($jsonResponse === false) {
075⫶ throw new \RuntimeException('Couldn\'t retrieve asset metadata');
076⫶ }
077⫶
078⫶ $response = json_decode($jsonResponse, true);
079⫶ if (!isset($response['query']['pages'])) {
080⫶ throw new \RuntimeException('Couldn\'t parse asset metadata');
081⫶ }
082⫶
083⫶ $pageData = array_values($response['query']['pages'])[0] ?? null;
084⫶ if (!isset($pageData['imageinfo'][0]['extmetadata'])) {
085⫶ throw new \RuntimeException('Couldn\'t parse image asset metadata');
086⫶ }
087⫶
088⫶ $imageInfo = $pageData['imageinfo'][0]['extmetadata'];
089⫶
090⫶ return new Asset(
091⫶ new AssetIdentifier($id),
092⫶ new AssetSource('commons'),
093⫶ new AssetUri('https://commons.wikimedia.org/w/index.php?title=Special:Redirect/file/' . urlencode($id)),
094⫶ new AssetMetadata([
095⫶ 'page_url' => "https://commons.wikimedia.org/wiki/File:$id",
096⫶ 'author' => $imageInfo['Artist']['value'] ?? null,
097⫶ 'license' => $imageInfo['LicenseShortName']['value'] ?? null,
098⫶ 'license_url' => $imageInfo['LicenseUrl']['value'] ?? null,
099⫶ ])
100⫶ );
101⫶ }
102⫶}


code_samples/back_office/images/templates/themes/standard/commons_asset_view.html.twig

docs/content_management/images/add_image_asset_from_dam.md@174:```html+twig
docs/content_management/images/add_image_asset_from_dam.md@175:[[= include_file('code_samples/back_office/images/templates/themes/standard/commons_asset_view.html.twig') =]]
docs/content_management/images/add_image_asset_from_dam.md@176:```

001⫶{% extends '@ibexadesign/ui/field_type/image_asset_view.html.twig' %}
002⫶
003⫶{% block asset_preview %}
004⫶ {{ parent() }}
005⫶ <div>
006⫶ <a href="{{ asset.assetMetadata.page_url }}">Image</a>


code_samples/back_office/images/templates/themes/standard/commons_asset_view.html.twig

docs/content_management/images/add_image_asset_from_dam.md@174:```html+twig
docs/content_management/images/add_image_asset_from_dam.md@175:[[= include_file('code_samples/back_office/images/templates/themes/standard/commons_asset_view.html.twig') =]]
docs/content_management/images/add_image_asset_from_dam.md@176:```

001⫶{% extends '@ibexadesign/ui/field_type/image_asset_view.html.twig' %}
002⫶
003⫶{% block asset_preview %}
004⫶ {{ parent() }}
005⫶ <div>
006⫶ <a href="{{ asset.assetMetadata.page_url }}">Image</a>
007⫶        {% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author }}{% endif %}
007⫶        {% if asset.assetMetadata.author %} by {{ asset.assetMetadata.author|striptags }}{% endif %}
008⫶        {% if asset.assetMetadata.license and asset.assetMetadata.license_url %}
009⫶ under <a href="{{ asset.assetMetadata.license_url }}">{{ asset.assetMetadata.license }}</a>
010⫶ {% endif %}.
011⫶ </div>
012⫶{% endblock %}

008⫶        {% if asset.assetMetadata.license and asset.assetMetadata.license_url %}
009⫶ under <a href="{{ asset.assetMetadata.license_url }}">{{ asset.assetMetadata.license }}</a>
010⫶ {% endif %}.
011⫶ </div>
012⫶{% endblock %}

Download colorized diff

@adriendupuis
adriendupuis merged commit 7082cdb into 5.0 Jan 30, 2026
1 check passed
@adriendupuis
adriendupuis deleted the dam_commons_updated branch January 30, 2026 14:52
adriendupuis pushed a commit that referenced this pull request Jan 30, 2026
* Added user-agent header in requests to Commons DAM
* Fixed author line in view template for Commons DAM
* PHP & JS CS Fixes

---------

Co-authored-by: vidarl <vidarl@users.noreply.github.com>
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