Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dc5e0c1
NGSTACK-991 add last modification date and user who did it to adminUI…
Oct 10, 2025
3a9ae4a
NGSTACK-991 add info about locations visibility to adminUI content fu…
Oct 10, 2025
8387fed
NGSTACK-991 update logic in 'VisibilityInfo' controller and add trans…
Oct 15, 2025
73608b0
NGSTACK-991 add 'Last modified' message to translation file and updat…
Oct 15, 2025
8405bc2
NGSTACK-991 update 'location_view' twig template to show full-width i…
Oct 15, 2025
8740dbc
NGSTACK-991 delete 'visibility_info' twig template since its not used…
Oct 15, 2025
b7f4bff
NGSTACK-991 update 'VisibilityInfo' with defining a route for it and …
Oct 15, 2025
d945d4f
NGSTACK-991 update 'location_view' twig template to wrap call to 'Vis…
Oct 15, 2025
45604da
NGSTACK-991 add JS script to dynamically load visibility info about l…
Oct 15, 2025
c69101f
NGSTACK-991 update logic in 'VisibilityInfo' controller for determini…
Oct 15, 2025
62b1764
NGSTACK-991 separate counting for locations hidden by ancestor and ex…
Oct 15, 2025
d556d34
NGSTACK-991 make the 'last modified info' be in one line and remove p…
Oct 16, 2025
5724026
NGSTACK-991 change the functionality of the label below the location'…
Oct 16, 2025
78e39ad
NGSTACK-991 make be array and collect all messages and implode them …
Oct 16, 2025
1afe8f0
NGSTACK-991 add svg icon to label below location visibility toggle
Oct 20, 2025
62b873f
NGSTACK-991 inject IconPathResolverInterface into VisibilityInfo cont…
Oct 23, 2025
8be8329
NGSTACK-991 rename variable 'extraContent' to 'extraLines' and refact…
Oct 23, 2025
875436f
NGSTACK-991 always show tooltip for some location if its parent is in…
Oct 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions bundle/Controller/Content/LastModifiedInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Content;

use Ibexa\Contracts\AdminUi\Controller\Controller;
use Ibexa\Contracts\Core\Repository\Values\Content\Content;
use Symfony\Component\HttpFoundation\Response;

class LastModifiedInfo extends Controller
{
public function __invoke(Content $content): Response
{
$lastContributor = $content->getVersionInfo()->getCreator();

return $this->render(
'@NetgenIbexaAdminUIExtra/themes/ngadmin/ui/last_modified_info.html.twig',
[
'last_contributor_name' => $lastContributor->getName(),
'content_info' => $content->getContentInfo(),
],
);
}
}
110 changes: 110 additions & 0 deletions bundle/Controller/Content/VisibilityInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

namespace Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Content;

use Ibexa\Contracts\AdminUi\Controller\Controller;
use Ibexa\Contracts\Core\Repository\ContentService;
use Ibexa\Contracts\Core\Repository\Exceptions\BadStateException;
use Ibexa\Contracts\Core\Repository\LocationService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface;

use function implode;

class VisibilityInfo extends Controller
{
public function __construct(
private readonly LocationService $locationService,
private readonly TranslatorInterface $translator,
private readonly ContentService $contentService,
) {}

public function __invoke(int $contentId, Request $request): Response
{
$iconPath = $request->attributes->get('iconPath') ?? $request->query->get('iconPath');

$content = $this->contentService->loadContent($contentId);

$extraContent = [];
if ($content->getContentInfo()->isHidden() === true) {
$extraContent[] = $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 = '';
$hiddenByAncestorLocationsMessage = '';
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',
);
}
}

if ($explicitlyHiddenLocationsMessage !== '') {
$extraContent[] = $explicitlyHiddenLocationsMessage;
}

if ($hiddenByAncestorLocationsMessage !== '') {
$extraContent[] = $hiddenByAncestorLocationsMessage;
}
} catch (BadStateException $e) {
$extraContent[] = $this->translator->trans('content.visibility.cannot_fetch_locations', [], 'locationview');
}

if ($extraContent === []) {
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('<br>', $extraContent),
'icon_path' => $iconPath,
'class' => 'mt-4',
],
);
}
}
7 changes: 7 additions & 0 deletions bundle/Resources/config/routing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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+
10 changes: 10 additions & 0 deletions bundle/Resources/config/services/controllers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ 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'

Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Content\LastModifiedInfo:
parent: Ibexa\Contracts\AdminUi\Controller\Controller

Netgen\Bundle\IbexaAdminUIExtraBundle\Controller\Queues:
parent: Ibexa\Contracts\AdminUi\Controller\Controller
arguments:
Expand Down
5 changes: 4 additions & 1 deletion bundle/Resources/encore/ibexa.config.manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
],
});
};
Original file line number Diff line number Diff line change
@@ -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);
12 changes: 12 additions & 0 deletions bundle/Resources/translations/locationview.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,41 @@
{% endif %}
{% endblock %}
{% block bottom %}
<span class="ibexa-icon-tag">
{% set url = path('ibexa.content_type.view', { 'contentTypeId': content_type.id, 'contentTypeGroupId': content_type.contentTypeGroups|first.id }) %}
<a href="{{ url }}">
<svg class="ibexa-icon ibexa-icon--small ibexa-icon--{{ content_type.identifier }}">
<use xlink:href="{{ ibexa_content_type_icon(content_type.identifier) }}"></use>
</svg>
</a>
<a href="{{ url }}">{{ content_type.name }}</a>
</span>
{% set language_names = [] %}
{% for language in content.versionInfo.languages %}
{% set language_names = language_names|merge([language.name]) %}
{% endfor %}
{{ language_names|join(', ') }}
<div style="display: flex; justify-content: space-between;">
<div style="flex-basis: 50%; max-width: 50%;">
<span class="ibexa-icon-tag">
{% set url = path('ibexa.content_type.view', { 'contentTypeId': content_type.id, 'contentTypeGroupId': content_type.contentTypeGroups|first.id }) %}
<a href="{{ url }}">
<svg class="ibexa-icon ibexa-icon--small ibexa-icon--{{ content_type.identifier }}">
<use xlink:href="{{ ibexa_content_type_icon(content_type.identifier) }}"></use>
</svg>
</a>
<a href="{{ url }}">{{ content_type.name }}</a>
</span>
{% set language_names = [] %}
{% for language in content.versionInfo.languages %}
{% set language_names = language_names|merge([language.name]) %}
{% endfor %}
{{ language_names|join(', ') }}
</div>
<div style="flex-basis: 50%; display: flex; justify-content: flex-end">
{{ render(controller('Netgen\\Bundle\\IbexaAdminUIExtraBundle\\Controller\\Content\\LastModifiedInfo', {
'content': content,
})) }}
</div>
</div>
{% endblock %}
{% endembed %}

{% if location.hidden or location.invisible %}
<div class="pb-4">
{% 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 %}
</div>
{% endif %}
<div id="ng-visibility-alert"
data-url="{{ path('netgen.ibexa_admin_ui_extra.content.visibility.locations', { contentId: content.id, iconPath: ibexa_icon_path('hide') }) }}"
>
{{ render(controller('Netgen\\Bundle\\IbexaAdminUIExtraBundle\\Controller\\Content\\VisibilityInfo', {
'contentId': content.id,
'iconPath': ibexa_icon_path('hide'),
})) }}
</div>

{{ ibexa_render_component_group(
'location-view-content-alerts',
{
Expand Down
Loading