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 url = path('ibexa.content_type.view', { 'contentTypeId': content_type.id, 'contentTypeGroupId': content_type.contentTypeGroups|first.id }) %} - - - - - - {{ content_type.name }} - - {% set language_names = [] %} - {% for language in content.versionInfo.languages %} - {% set language_names = language_names|merge([language.name]) %} - {% endfor %} - {{ language_names|join(', ') }} +
+
+ + {% set url = path('ibexa.content_type.view', { 'contentTypeId': content_type.id, 'contentTypeGroupId': content_type.contentTypeGroups|first.id }) %} + + + + + + {{ content_type.name }} + + {% set language_names = [] %} + {% for language in content.versionInfo.languages %} + {% set language_names = language_names|merge([language.name]) %} + {% endfor %} + {{ language_names|join(', ') }} +
+
+ {{ render(controller('Netgen\\Bundle\\IbexaAdminUIExtraBundle\\Controller\\Content\\LastModifiedInfo', { + 'content': content, + })) }} +
+
{% endblock %} {% endembed %} - {% if location.hidden or location.invisible %} -
- {% include '@ibexadesign/ui/component/alert/alert.html.twig' with { - type: 'info', - title: 'content.hidden.message'|trans()|desc('This Content item or its Location is hidden.'), - icon_path: ibexa_icon_path('hide'), - class: 'mb-4', - } only %} -
- {% endif %} +
+ {{ render(controller('Netgen\\Bundle\\IbexaAdminUIExtraBundle\\Controller\\Content\\VisibilityInfo', { + 'contentId': content.id, + 'iconPath': ibexa_icon_path('hide'), + })) }} +
+ {{ ibexa_render_component_group( 'location-view-content-alerts', { diff --git a/bundle/Resources/views/themes/ngadmin/content/tab/locations/tab.html.twig b/bundle/Resources/views/themes/ngadmin/content/tab/locations/tab.html.twig new file mode 100644 index 0000000..c797a18 --- /dev/null +++ b/bundle/Resources/views/themes/ngadmin/content/tab/locations/tab.html.twig @@ -0,0 +1,190 @@ +{% trans_default_domain 'ibexa_locationview' %} +{% import _self as tab %} +{% form_theme form_content_location_add '@IbexaAdminUi/themes/admin/ui/form/flat_widgets.html.twig' %} +{% form_theme form_content_location_remove with ['@IbexaAdminUi/themes/admin/ui/form/flat_widgets.html.twig', '@IbexaAdminUi/themes/admin/ui/form_fields.html.twig'] %} + +
+ {{ form(form_content_location_add, {'action': path('ibexa.location.add')}) }} + {% set body_rows = [] %} + {% if locations is not empty %} + {% for location in locations %} + {% set body_row_cols = [] %} + + {% set col_raw %} + {{ form_widget(form_content_location_remove.locations[location.id], {'attr': {'disabled': not location.canDelete}}) }} + {% endset %} + {% set body_row_cols = body_row_cols|merge([{ + has_checkbox: true, + content: col_raw, + raw: true, + }]) %} + + {% set col_raw %} + {% include '@IbexaAdminUi/themes/admin/ui/location_path.html.twig' with {'locations': location.pathLocations, 'link_last_element': true} %} + {% endset %} + {% set body_row_cols = body_row_cols|merge([{ + content: col_raw, + raw: true, + }]) %} + + {% set body_row_cols = body_row_cols|merge([ + { content: '' }, + ]) %} + + {% set col_raw %} + + {% endset %} + {% set body_row_cols = body_row_cols|merge([{ + content: col_raw, + raw: true, + }]) %} + + {% set col_raw %} +
+
+ + + {{ 'toggle_widget.label.on'|trans|desc('On') }} + + + {{ 'toggle_widget.label.off'|trans|desc('Off') }} + +
+ + + + {% if location.parentLocation is defined and location.parentLocation.invisible %} + + {% endif %} +
+ {% endset %} + + {% set body_row_cols = body_row_cols|merge([{ + content: col_raw, + raw: true, + }]) %} + + {% set body_row_cols = body_row_cols|merge([ + { content: location.childCount }, + ]) %} + + {% set body_rows = body_rows|merge([{ cols: body_row_cols }]) %} + {% endfor %} + {% endif %} + + {% embed '@IbexaAdminUi/themes/admin/ui/component/table/table.html.twig' with { + headline: 'tab.locations.content_locations'|trans|desc('Content locations'), + head_cols: [ + { has_checkbox: true }, + { content: 'tab.locations.path'|trans|desc('Path'), + attr: { colspan: 2 } + }, + { content: 'tab.locations.main'|trans|desc('Main') }, + { content: 'tab.locations.visibility'|trans|desc('Visibility'), + class: 'ibexa-table__header-cell--visibility-toggler' + }, + { content: 'tab.locations.subitems'|trans|desc('Sub-items') }, + ], + body_rows, + actions: tab.table_header_tools(form_content_location_add, form_content_location_remove, can_add), + } %} + {% block between_header_and_table %} + {{ form_start(form_content_location_remove, { + 'action': path('ibexa.location.remove'), + 'attr': { 'class': 'ibexa-toggle-btn-state', 'data-toggle-button-id': '#delete-locations' } + }) }} + {% endblock %} + {% endembed %} + {{ form_end(form_content_location_remove) }} + + {% if pager is defined and pager.haveToPaginate %} +
+ + {{ 'pagination.viewing'|trans({ + '%viewing%': pager.currentPageResults|length, + '%total%': pager.nbResults}, 'ibexa_pagination')|desc('Viewing %viewing% out of %total% items')|raw }} + +
+
+ {{ pagerfanta(pager, 'ibexa', pager_options|merge({ + 'routeParams': { + '_fragment': 'ibexa-tab-location-view-locations' + } + })) }} +
+ {% endif %} + + {{ form_start(form_content_location_update_visibility, {'action': path('ibexa.location.update_visibility')}) }} + {{ form_row(form_content_location_update_visibility.location) }} + {{ form_row(form_content_location_update_visibility.hidden, { + label_attr: { hidden: true } + }) }} + {{ form_row(form_content_location_update_visibility.set) }} + {{ form_end(form_content_location_update_visibility) }} + + {{ form(form_content_location_main_update, {'action': path('ibexa.content.update_main_location')}) }} +
+ +{% include '@IbexaAdminUi/themes/admin/content/tab/locations/panel_swap.html.twig' with { + 'form': form_content_location_swap, + 'can_swap': can_swap +} %} + +{% macro table_header_tools(form_add, form_remove, can_add) %} + + + {% set modal_data_target = 'delete-content-types-modal' %} + + {% include '@IbexaAdminUi/themes/admin/ui/modal/bulk_delete_confirmation.html.twig' with { + 'id': modal_data_target, + 'message': 'tab.locations.modal.message'|trans|desc('Do you want to delete the Location? All its sub-items will be sent to Trash.'), + 'data_click': '#' ~ form_remove.remove.vars.id, + } %} +{% endmacro %} diff --git a/bundle/Resources/views/themes/ngadmin/ui/last_modified_info.html.twig b/bundle/Resources/views/themes/ngadmin/ui/last_modified_info.html.twig new file mode 100644 index 0000000..a9b4d35 --- /dev/null +++ b/bundle/Resources/views/themes/ngadmin/ui/last_modified_info.html.twig @@ -0,0 +1,12 @@ +{% trans_default_domain 'locationview' %} + +
+
+ {{ 'content.last_modified'|trans|desc('Last modified:') }} +
+
+
+ {{ last_contributor_name }} at {{ content_info.modificationDate | date('d/m/Y H:i') }} +
+
+