diff --git a/bundle/Controller/Content/LastModifiedInfo.php b/bundle/Controller/Content/LastModifiedInfo.php
new file mode 100644
index 0000000..87736d2
--- /dev/null
+++ b/bundle/Controller/Content/LastModifiedInfo.php
@@ -0,0 +1,25 @@
+getVersionInfo()->getCreator();
+
+ return $this->render(
+ '@NetgenIbexaAdminUIExtra/themes/ngadmin/ui/last_modified_info.html.twig',
+ [
+ 'last_contributor_name' => $lastContributor->getName(),
+ 'content_info' => $content->getContentInfo(),
+ ],
+ );
+ }
+}
diff --git a/bundle/Controller/Content/VisibilityInfo.php b/bundle/Controller/Content/VisibilityInfo.php
new file mode 100644
index 0000000..ad90598
--- /dev/null
+++ b/bundle/Controller/Content/VisibilityInfo.php
@@ -0,0 +1,108 @@
+iconPathResolver->resolve('hide');
+
+ $content = $this->contentService->loadContent($contentId);
+
+ $extraLines = [];
+ if ($content->getContentInfo()->isHidden() === true) {
+ $extraLines[] = $this->translator->trans('content.visibility.content_hidden', [], 'locationview');
+ }
+
+ try {
+ $locations = $this->locationService->loadLocations($content->getContentInfo());
+
+ $explicitlyHiddenLocations = 0;
+ $hiddenByAncestorLocations = 0;
+ foreach ($locations as $location) {
+ if ($location->explicitlyHidden) {
+ $explicitlyHiddenLocations++;
+ }
+
+ if ($location->isInvisible() && $location->getParentLocation()?->isInvisible()) {
+ $hiddenByAncestorLocations++;
+ }
+ }
+
+ $explicitlyHiddenLocationsMessage = null;
+ $hiddenByAncestorLocationsMessage = null;
+ if (count($locations) === 1) {
+ if ($explicitlyHiddenLocations !== 0) {
+ $explicitlyHiddenLocationsMessage = $this->translator->trans('content.visibility.location_hidden', [], 'locationview');
+ }
+
+ if ($hiddenByAncestorLocations !== 0) {
+ $hiddenByAncestorLocationsMessage = $this->translator->trans('content.visibility.location_hidden_by_ancestor', [], 'locationview');
+ }
+ } else {
+ if ($explicitlyHiddenLocations !== 0) {
+ $explicitlyHiddenLocationsMessage = $this->translator->trans(
+ 'content.visibility.locations_hidden',
+ [
+ '%hidden%' => $explicitlyHiddenLocations,
+ '%total%' => count($locations),
+ ],
+ 'locationview',
+ );
+ }
+
+ if ($hiddenByAncestorLocations !== 0) {
+ $hiddenByAncestorLocationsMessage = $this->translator->trans(
+ 'content.visibility.locations_hidden_by_ancestor',
+ [
+ '%hidden%' => $hiddenByAncestorLocations,
+ '%total%' => count($locations),
+ ],
+ 'locationview',
+ );
+ }
+ }
+
+ $extraLines[] = $explicitlyHiddenLocationsMessage;
+ $extraLines[] = $hiddenByAncestorLocationsMessage;
+ $extraLines = array_filter($extraLines);
+ } catch (BadStateException $e) {
+ $extraLines[] = $this->translator->trans('content.visibility.cannot_fetch_locations', [], 'locationview');
+ }
+
+ if ($extraLines === []) {
+ return new Response('', Response::HTTP_NO_CONTENT);
+ }
+
+ return $this->render('@IbexaAdminUi/themes/admin/ui/component/alert/alert.html.twig',
+ [
+ 'type' => 'info',
+ 'title' => $this->translator->trans('content.visibility.info', [], 'locationview'),
+ 'extra_content' => implode('
', $extraLines),
+ 'icon_path' => $iconPath,
+ 'class' => 'mt-4',
+ ],
+ );
+ }
+}
diff --git a/bundle/Resources/config/routing.yaml b/bundle/Resources/config/routing.yaml
index 13126b3..38f8327 100644
--- a/bundle/Resources/config/routing.yaml
+++ b/bundle/Resources/config/routing.yaml
@@ -9,3 +9,10 @@ netgen.ibexa_admin_ui_extra.queues.list:
path: /queues/list
methods: [ 'GET' ]
controller: 'Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Queues'
+
+netgen.ibexa_admin_ui_extra.content.visibility.locations:
+ path: /content/{contentId}/locations/visibility_info
+ methods: ['GET']
+ controller: 'Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Content\VisibilityInfo'
+ requirements:
+ contentId: \d+
diff --git a/bundle/Resources/config/services/controllers.yaml b/bundle/Resources/config/services/controllers.yaml
index 04a9cec..0624ee6 100644
--- a/bundle/Resources/config/services/controllers.yaml
+++ b/bundle/Resources/config/services/controllers.yaml
@@ -7,6 +7,17 @@ services:
- '@Netgen\Bundle\IbexaAdminUIExtraBundle\Form\FormFactory'
- '@Ibexa\Core\Helper\TranslationHelper'
+ Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Content\VisibilityInfo:
+ parent: Ibexa\Contracts\AdminUi\Controller\Controller
+ arguments:
+ - '@ibexa.api.service.location'
+ - '@Symfony\Contracts\Translation\TranslatorInterface'
+ - '@ibexa.api.service.content'
+ - '@Ibexa\AdminUi\Resolver\IconPathResolver'
+
+ Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Content\LastModifiedInfo:
+ parent: Ibexa\Contracts\AdminUi\Controller\Controller
+
Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Queues:
parent: Ibexa\Contracts\AdminUi\Controller\Controller
arguments:
diff --git a/bundle/Resources/encore/ibexa.config.manager.js b/bundle/Resources/encore/ibexa.config.manager.js
index 07acadd..bdf7261 100644
--- a/bundle/Resources/encore/ibexa.config.manager.js
+++ b/bundle/Resources/encore/ibexa.config.manager.js
@@ -4,6 +4,9 @@ module.exports = (ibexaConfig, ibexaConfigManager) => {
ibexaConfigManager.add({
ibexaConfig,
entryName: 'ibexa-admin-ui-location-view-js',
- newItems: [path.resolve(__dirname, '../public/js/scripts/admin.content.always_available.js')],
+ newItems: [
+ path.resolve(__dirname, '../public/js/scripts/admin.content.always_available.js'),
+ path.resolve(__dirname, '../public/js/scripts/admin.content.location.visibility_info.js'),
+ ],
});
};
diff --git a/bundle/Resources/public/js/scripts/admin.content.location.visibility_info.js b/bundle/Resources/public/js/scripts/admin.content.location.visibility_info.js
new file mode 100644
index 0000000..8ddd795
--- /dev/null
+++ b/bundle/Resources/public/js/scripts/admin.content.location.visibility_info.js
@@ -0,0 +1,54 @@
+(function (global, doc, ibexa) {
+ "use strict";
+ const SELECTOR_VISIBILITY_ALERT_WRAPPER = '#ng-visibility-alert';
+ const handleRefreshError = () => {}
+ const handleRefreshResponse = (response) => {
+ if (response.status === 204) {
+ return { isEmpty: true };
+ }
+
+ if (!response.ok) {
+ throw new Error(response.statusText);
+ }
+
+ return response.text().then((html) => ({ isEmpty: false, html }));
+ };
+ const updateAlertContent = (wrapper, { isEmpty, html }) => {
+ wrapper.innerHTML = isEmpty ? '' : html;
+ };
+ const refreshAlert = (wrapper, url) => {
+ const request = new Request(url, {
+ method: 'GET',
+ credentials: 'same-origin',
+ headers: { 'X-Requested-With': 'XMLHttpRequest' },
+ });
+
+ fetch(request)
+ .then(handleRefreshResponse)
+ .then(updateAlertContent.bind(null, wrapper))
+ .catch(handleRefreshError);
+ };
+ const onReady = () => {
+ const wrapper = doc.querySelector(SELECTOR_VISIBILITY_ALERT_WRAPPER);
+ if (!wrapper) {
+ return;
+ }
+
+ const url = wrapper.dataset.url;
+ if (!url) {
+ return;
+ }
+
+ doc.body.addEventListener(
+ 'ibexa-content-tree-refresh',
+ refreshAlert.bind(null, wrapper, url),
+ false
+ );
+ };
+
+ if (doc.readyState === 'loading') {
+ doc.addEventListener('DOMContentLoaded', onReady);
+ } else {
+ onReady();
+ }
+})(window, window.document, window.ibexa);
diff --git a/bundle/Resources/translations/locationview.en.yaml b/bundle/Resources/translations/locationview.en.yaml
index 9f47b7b..c953888 100644
--- a/bundle/Resources/translations/locationview.en.yaml
+++ b/bundle/Resources/translations/locationview.en.yaml
@@ -3,3 +3,15 @@ content.update_always_available.success.available: "Content '%name%' marked as a
content.update_always_available.success.not_available: "Content '%name%' unmarked as always available."
content.update_always_available.toggle_widget.label.on: "On"
content.update_always_available.toggle_widget.label.off: "Off"
+
+content.last_modified: "Last modified:"
+
+content.visibility.info: "This content might not be visible"
+content.visibility.content_hidden: "Content is hidden"
+content.visibility.location_hidden: "Location is hidden"
+content.visibility.location_hidden_by_ancestor: "Location is hidden by parent or ancestor location"
+content.visibility.locations_hidden: "%hidden% of %total% location(s) are hidden"
+content.visibility.locations_hidden_by_ancestor: "%hidden% of %total% location(s) are hidden by parent or ancestor location"
+content.visibility.cannot_fetch_locations: "Can't fetch locations for this content!"
+
+content.tab.locations.visibility_toggle: "One of the ancestors is hidden"
diff --git a/bundle/Resources/views/themes/ngadmin/content/location_view.html.twig b/bundle/Resources/views/themes/ngadmin/content/location_view.html.twig
index a128681..bbba551 100644
--- a/bundle/Resources/views/themes/ngadmin/content/location_view.html.twig
+++ b/bundle/Resources/views/themes/ngadmin/content/location_view.html.twig
@@ -20,33 +20,41 @@
{% endif %}
{% endblock %}
{% block bottom %}
-
- {% set language_names = [] %}
- {% for language in content.versionInfo.languages %}
- {% set language_names = language_names|merge([language.name]) %}
- {% endfor %}
- {{ language_names|join(', ') }}
+