{% else %}
diff --git a/bundle/Templating/Twig/Extension/RemoteMediaExtension.php b/bundle/Templating/Twig/Extension/RemoteMediaExtension.php
index 600a0c1d..34839846 100644
--- a/bundle/Templating/Twig/Extension/RemoteMediaExtension.php
+++ b/bundle/Templating/Twig/Extension/RemoteMediaExtension.php
@@ -57,6 +57,10 @@ public function getFunctions(): array
'ngrm_remote_resource_html_tag',
[RemoteMediaRuntime::class, 'getRemoteResourceHtmlTag'],
),
+ new TwigFunction(
+ 'ngrm_remote_resource_location_html_tag',
+ [RemoteMediaRuntime::class, 'getRemoteResourceLocationHtmlTag'],
+ ),
new TwigFunction(
'ngrm_remote_resource_variation_html_tag',
[RemoteMediaRuntime::class, 'getRemoteResourceVariationHtmlTag'],
diff --git a/bundle/Templating/Twig/Runtime/RemoteMediaRuntime.php b/bundle/Templating/Twig/Runtime/RemoteMediaRuntime.php
index 962ef9f3..8abe768c 100644
--- a/bundle/Templating/Twig/Runtime/RemoteMediaRuntime.php
+++ b/bundle/Templating/Twig/Runtime/RemoteMediaRuntime.php
@@ -19,6 +19,10 @@
use Twig\Extension\AbstractExtension;
use function count;
+use function get_debug_type;
+use function in_array;
+use function method_exists;
+use function trigger_deprecation;
final class RemoteMediaRuntime extends AbstractExtension
{
@@ -107,6 +111,36 @@ public function getRemoteResourceHtmlTag(RemoteResource $resource, array $htmlAt
return $this->provider->generateHtmlTag($resource, $htmlAttributes, $forceVideo, $useThumbnail);
}
+ public function getRemoteResourceLocationHtmlTag(RemoteResourceLocation $location, array $htmlAttributes = [], bool $forceVideo = false, bool $useThumbnail = false): string
+ {
+ // The check and the fallback below will be removed in 4.0, when generateLocationHtmlTag() is added to ProviderInterface.
+ if (method_exists($this->provider, 'generateLocationHtmlTag')) {
+ return $this->provider->generateLocationHtmlTag($location, $htmlAttributes, $forceVideo, $useThumbnail);
+ }
+
+ trigger_deprecation(
+ 'netgen/remote-media-bundle',
+ '3.1',
+ 'Not implementing the "%s::generateLocationHtmlTag()" method in "%s" is deprecated, the method will be added to the interface in 4.0.',
+ ProviderInterface::class,
+ get_debug_type($this->provider),
+ );
+
+ $type = $location->getRemoteResource()->getType();
+ $isRenderedAsImage = $type === RemoteResource::TYPE_IMAGE
+ || ($useThumbnail && in_array($type, [RemoteResource::TYPE_VIDEO, RemoteResource::TYPE_AUDIO], true));
+
+ if ($isRenderedAsImage && !($htmlAttributes['alt'] ?? null) && $location->getAltTextOverride() !== null) {
+ $htmlAttributes['alt'] = $location->getAltTextOverride();
+ }
+
+ if ($isRenderedAsImage && !($htmlAttributes['title'] ?? null) && $location->getCaptionOverride() !== null) {
+ $htmlAttributes['title'] = $location->getCaptionOverride();
+ }
+
+ return $this->provider->generateHtmlTag($location->getRemoteResource(), $htmlAttributes, $forceVideo, $useThumbnail);
+ }
+
public function getRemoteResourceVariationHtmlTag(
RemoteResourceLocation $location,
string $variationGroup,
diff --git a/composer.json b/composer.json
index 4dba01a8..2104aac5 100644
--- a/composer.json
+++ b/composer.json
@@ -35,6 +35,7 @@
"symfony/form": "^5.4 || ^6.2",
"symfony/serializer": "^5.4 || ^6.2",
"symfony/translation": "^5.4 || ^6.2",
+ "symfony/deprecation-contracts": "^2.5 || ^3.0",
"doctrine/doctrine-bundle": "^2.4",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.10"
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index f699300b..53c6bb14 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,5 +1,19 @@
# Netgen Remote Media Bundle changelog
+## 3.1.0
+
+### Added
+
+* alt text and caption overrides on `RemoteResourceLocation` (`alt_text_override` and `caption_override` columns), stored only on the location and never sent to the cloud, they take precedence over the values stored on the cloud
+* the overrides are editable in the interface (form and CKEditor 5 plugin) next to the cloud values, and configurable for named locations (`alt_text` and `caption`), see [form documentation](FORM.md#alt-text-and-caption-overrides) and [named objects](NAMED_OBJECTS.md)
+* `RemoteResourceLocation::getAltText()` and `RemoteResourceLocation::getCaption()` which return the override when it's set and the value from the cloud otherwise
+* `ngrm_remote_resource_location_html_tag` TWIG function and `ProviderInterface::generateLocationHtmlTag()` which render the resource of a location with its overrides
+* Doctrine migration `Version030100` which adds the two columns
+
+### Deprecated
+
+* not implementing `generateLocationHtmlTag()` in custom providers which don't extend `AbstractProvider` is deprecated, the method is declared with `@method` on `ProviderInterface` and will be added to the interface in 4.0, until then the TWIG runtime applies the overrides itself and calls `generateHtmlTag()` for such providers
+
## 3.0.3
### Added
diff --git a/docs/FORM.md b/docs/FORM.md
index a15fd718..385c3c9c 100644
--- a/docs/FORM.md
+++ b/docs/FORM.md
@@ -49,6 +49,23 @@ The above example will make sure that, when you select a new resource in the for
The reason why we're using `OneToOne` relation here is because the idea is that every usage of a remote resource has it's own location. Things such as cropping settings or watermark text are stored in a location, and you want to have different cropping settings for the same eg. image used on different places.
+## Alt text and caption overrides
+
+Alt text and caption entered in the interface are stored on the resource in the cloud, so changing them updates every location which uses that resource. Each location can additionally have its own alt text and caption override (`altTextOverride` and `captionOverride` fields of the form), stored only on that location and never sent to the cloud. An empty override means that the value from the cloud is used.
+
+`RemoteResourceLocation::getAltText()` and `RemoteResourceLocation::getCaption()` return the override when it's set and the value from the cloud otherwise, so use them (eg. `location.caption` in TWIG) instead of the ones on the resource when rendering a location:
+
+```twig
+{{ ngrm_remote_resource_variation_html_tag(location, 'default', 'wide') }}
+{{ ngrm_remote_resource_location_html_tag(location) }}
+
+{% if location.caption is not empty %}
+ {{ location.caption }}
+{% endif %}
+```
+
+Both TWIG functions render the overrides as `alt` and `title` attributes of images and video thumbnails, unless those attributes are explicitly provided. `ngrm_remote_resource_html_tag` receives only the resource, so it always uses the values from the cloud.
+
## Add field to your form
Now when we have the entity configured properly, it's time to add the form field:
diff --git a/docs/NAMED_OBJECTS.md b/docs/NAMED_OBJECTS.md
index 2e8e3c6d..a0b4bbb0 100644
--- a/docs/NAMED_OBJECTS.md
+++ b/docs/NAMED_OBJECTS.md
@@ -19,11 +19,13 @@ netgen_remote_media:
### Remote resource location
-If you also need a location for that resource, it's possible by using configuration for locations. Configuration consists of key, value pairs, where key is the desired name (eg. `my_resource_location`) and value is another array which accepts three keys:
+If you also need a location for that resource, it's possible by using configuration for locations. Configuration consists of key, value pairs, where key is the desired name (eg. `my_resource_location`) and value is another array which accepts the following keys:
- `resource_remote_id` - remote ID of the resource on the cloud
- `source` (optional) - text that will be used for the location source (eg. `named_my_resource_location`)
- `watermark_text` (optional) - watermark text which can be used if a watermaked variation gets built from that location
+- `alt_text` (optional) - alt text override for the location, used instead of the alt text stored on the cloud
+- `caption` (optional) - caption override for the location, used instead of the caption stored on the cloud
Example:
@@ -35,6 +37,7 @@ netgen_remote_media:
resource_remote_id: 'upload|image|folder/my_resource'
source: 'named_my_resource_location'
watermark_text: 'Netgen.io'
+ alt_text: 'Netgen logo'
```
## Usage
diff --git a/docs/UPGRADE.md b/docs/UPGRADE.md
index 298f604f..d11850bd 100644
--- a/docs/UPGRADE.md
+++ b/docs/UPGRADE.md
@@ -1,6 +1,33 @@
Netgen Remote Media Bundle upgrade instructions
===============================================
+Upgrade from 3.0.3 to 3.1.0
+---------------------------
+
+### Database
+
+* Version 3.1.0 ships the `Version030100` migration. Run it with:
+
+ ```
+ php bin/console doctrine:migrations:migrate --configuration=vendor/netgen/remote-media-bundle/migrations/doctrine.yaml
+ ```
+
+* The migration adds the nullable `alt_text_override` and `caption_override` columns to `ngrm_remote_resource_location`. Existing locations have no overrides, so they keep using the alt text and caption stored on the cloud.
+* If the columns already exist (for example, added with `doctrine:schema:update` while testing an untagged version), the migration fails. Mark it as executed instead:
+
+ ```
+ php bin/console doctrine:migrations:version 'Netgen\RemoteMedia\Migrations\Doctrine\Version030100' --add --configuration=vendor/netgen/remote-media-bundle/migrations/doctrine.yaml
+ ```
+
+* The `down()` method drops both columns together with the stored overrides.
+* If the Doctrine metadata cache is stored outside of the Symfony cache directory (for example in Redis), clear it after the upgrade with `php bin/console doctrine:cache:clear-metadata`. Otherwise Doctrine keeps using the old mapping and loading a location fails with `Typed property Netgen\RemoteMedia\API\Values\RemoteResourceLocation::$altTextOverride must not be accessed before initialization`.
+
+### Code changes
+
+* `ProviderInterface` declares the new method `generateLocationHtmlTag()` with `@method`, it will be added to the interface in 4.0. `AbstractProvider` implements it. Custom providers which don't extend `AbstractProvider` keep working, but `ngrm_remote_resource_location_html_tag` triggers a deprecation for them. Implement the method before upgrading to 4.0.
+* The overrides are used only where a location is rendered through `ngrm_remote_resource_location_html_tag`, `ngrm_remote_resource_variation_html_tag`, `location.altText` or `location.caption`. Templates which read the values from `location.remoteResource` keep showing the values from the cloud, see [form documentation](FORM.md#alt-text-and-caption-overrides).
+* Projects which build the form data of a remote media field manually (outside of the `RemoteMediaType` form, for example in import scripts) must send the current `altTextOverride` and `captionOverride` of the location. A missing key is saved as an empty override, as for the other location fields.
+
Upgrade from 3.0.1 to 3.0.2
---------------------------
diff --git a/frontend/src/components/Interactions.vue b/frontend/src/components/Interactions.vue
index 78f6c6c6..d932eac3 100644
--- a/frontend/src/components/Interactions.vue
+++ b/frontend/src/components/Interactions.vue
@@ -152,6 +152,8 @@ export default {
alternateText: item.altText,
caption: item.caption,
watermarkText: this.selectedImage.watermarkText,
+ altTextOverride: '',
+ captionOverride: '',
tags: item.tags,
size: item.size,
variations: {},
@@ -187,6 +189,8 @@ export default {
alternateText: item.altText,
caption: item.caption,
watermarkText: this.selectedImage.watermarkText,
+ altTextOverride: '',
+ captionOverride: '',
tags: item.tags,
size: item.size,
variations: {},
@@ -215,6 +219,8 @@ export default {
alternateText: '',
caption: '',
watermarkText: this.selectedImage.watermarkText,
+ altTextOverride: '',
+ captionOverride: '',
tags: [],
size: 0,
variations: {},
diff --git a/frontend/src/components/Preview.vue b/frontend/src/components/Preview.vue
index 25247317..5c870432 100644
--- a/frontend/src/components/Preview.vue
+++ b/frontend/src/components/Preview.vue
@@ -58,6 +58,36 @@
>
+
+
+ {{this.config.translations.preview_alternate_text_override}}
+
+
+
+
+
+
+
+
+ {{this.config.translations.preview_caption_override}}
+
+
+
+
+
+