diff --git a/features/personas/AddLocation.feature b/features/personas/AddLocation.feature index 34fe77f666..88422a2658 100644 --- a/features/personas/AddLocation.feature +++ b/features/personas/AddLocation.feature @@ -7,6 +7,7 @@ Feature: Verify that an Editor with Content Type limitation on content/create po And I log in as "Add" with password "Passw0rd-42" And I go to "Content structure" in "Content" tab And I navigate to content "NewArticle" of type "Article" in root + And I am using the DXP in "Expert" mode When I switch to "Locations" tab in Content structure And I add a new Location under "root/Destination" Then there exists Content view Page for "root/Destination/NewArticle" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 49a85a07b8..c7d174f2de 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1750,11 +1750,6 @@ parameters: count: 1 path: src/lib/Behat/BrowserContext/ContentViewContext.php - - - message: "#^Property Ibexa\\\\AdminUi\\\\Behat\\\\BrowserContext\\\\ContentViewContext\\:\\:\\$universalDiscoveryWidget is never read, only written\\.$#" - count: 1 - path: src/lib/Behat/BrowserContext/ContentViewContext.php - - message: "#^Method Ibexa\\\\AdminUi\\\\Behat\\\\BrowserContext\\\\LanguageContext\\:\\:deleteLanguageNamed\\(\\) has no return type specified\\.$#" count: 1 @@ -3035,21 +3030,11 @@ parameters: count: 1 path: src/lib/Behat/Page/ContentViewPage.php - - - message: "#^Property Ibexa\\\\AdminUi\\\\Behat\\\\Page\\\\ContentViewPage\\:\\:\\$contentUpdatePage is never read, only written\\.$#" - count: 1 - path: src/lib/Behat/Page/ContentViewPage.php - - message: "#^Property Ibexa\\\\AdminUi\\\\Behat\\\\Page\\\\ContentViewPage\\:\\:\\$route has no type specified\\.$#" count: 1 path: src/lib/Behat/Page/ContentViewPage.php - - - message: "#^Property Ibexa\\\\AdminUi\\\\Behat\\\\Page\\\\ContentViewPage\\:\\:\\$userUpdatePage is never read, only written\\.$#" - count: 1 - path: src/lib/Behat/Page/ContentViewPage.php - - message: "#^Method Ibexa\\\\AdminUi\\\\Behat\\\\Page\\\\DashboardPage\\:\\:editDraft\\(\\) has no return type specified\\.$#" count: 1 diff --git a/src/bundle/Controller/User/UserModeController.php b/src/bundle/Controller/User/UserModeController.php new file mode 100644 index 0000000000..45025b2591 --- /dev/null +++ b/src/bundle/Controller/User/UserModeController.php @@ -0,0 +1,82 @@ +userSettingService = $userSettingService; + } + + public function changeAction(Request $request, ?string $returnUrl): Response + { + $data = new UserModeChangeData(); + $data->setMode($this->userSettingService->getUserSetting(UserMode::IDENTIFIER)->value === UserMode::EXPERT); + + $form = $this->createForm( + UserModeChangeType::class, + $data, + [ + 'action' => $this->generateUrl( + 'ibexa.user_mode.change', + [ + self::RETURN_URL_PARAM => $returnUrl, + ] + ), + 'method' => Request::METHOD_POST, + ] + ); + + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $this->userSettingService->setUserSetting( + UserMode::IDENTIFIER, + $data->getMode() ? UserMode::EXPERT : UserMode::SMART + ); + + return $this->createRedirectToReturnUrl($request); + } + + return $this->render( + '@ibexadesign/ui/user_mode_form.html.twig', + [ + 'form' => $form->createView(), + ] + ); + } + + private function createRedirectToReturnUrl(Request $request): RedirectResponse + { + $url = $request->query->get(self::RETURN_URL_PARAM); + if (is_string($url) && $this->isSafeUrl($url, $request->getBaseUrl())) { + return new RedirectResponse($url); + } + + throw $this->createAccessDeniedException('Malformed return URL'); + } + + private function isSafeUrl(string $referer, string $baseUrl): bool + { + return str_starts_with($referer, $baseUrl); + } +} diff --git a/src/bundle/Resources/config/routing.yaml b/src/bundle/Resources/config/routing.yaml index a93f27d90c..a34e4b9757 100644 --- a/src/bundle/Resources/config/routing.yaml +++ b/src/bundle/Resources/config/routing.yaml @@ -951,3 +951,11 @@ ibexa.permission.limitation.language.content_read: methods: [GET] requirements: contentInfoId: \d+ + + +### User Mode + +ibexa.user_mode.change: + path: /user/change-mode + controller: 'Ibexa\Bundle\AdminUi\Controller\User\UserModeController::changeAction' + methods: [GET, POST] diff --git a/src/bundle/Resources/config/services/controllers.yaml b/src/bundle/Resources/config/services/controllers.yaml index b0530e6c13..ccdb984f56 100644 --- a/src/bundle/Resources/config/services/controllers.yaml +++ b/src/bundle/Resources/config/services/controllers.yaml @@ -191,6 +191,12 @@ services: tags: - controller.service_arguments + Ibexa\Bundle\AdminUi\Controller\User\UserModeController: + parent: Ibexa\Contracts\AdminUi\Controller\Controller + autowire: true + tags: + - controller.service_arguments + Ibexa\Bundle\AdminUi\Controller\UserOnTheFlyController: parent: Ibexa\Contracts\AdminUi\Controller\Controller autowire: true diff --git a/src/bundle/Resources/config/services/twig.yaml b/src/bundle/Resources/config/services/twig.yaml index 8375a60b39..c61ddaa6e5 100644 --- a/src/bundle/Resources/config/services/twig.yaml +++ b/src/bundle/Resources/config/services/twig.yaml @@ -25,3 +25,11 @@ services: - '@Ibexa\AdminUi\Limitation\Templating\LimitationBlockRenderer' tags: - { name: twig.extension } + + Ibexa\Bundle\AdminUi\Templating\Twig\UserModeExtension: + tags: + - { name: twig.extension } + + Ibexa\Bundle\AdminUi\Templating\Twig\LocationExtension: + tags: + - { name: twig.extension } diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index df33a6d51f..0b9e8bae74 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -57,6 +57,7 @@ const layout = [ path.resolve(__dirname, '../public/js/scripts/admin.anchor.navigation'), path.resolve(__dirname, '../public/js/scripts/admin.context.menu'), path.resolve(__dirname, '../public/js/scripts/admin.focus.mode.js'), + path.resolve(__dirname, '../public/js/scripts/admin.user.mode.js'), path.resolve(__dirname, '../public/js/scripts/sidebar/main.menu.js'), path.resolve(__dirname, '../public/js/scripts/admin.input.text.js'), path.resolve(__dirname, '../public/js/scripts/admin.table.js'), diff --git a/src/bundle/Resources/public/js/scripts/admin.location.view.js b/src/bundle/Resources/public/js/scripts/admin.location.view.js index d6b8ec54e3..3d54ea1f7e 100644 --- a/src/bundle/Resources/public/js/scripts/admin.location.view.js +++ b/src/bundle/Resources/public/js/scripts/admin.location.view.js @@ -4,7 +4,6 @@ const mfuContainer = doc.querySelector('#ibexa-mfu'); const token = doc.querySelector('meta[name="CSRF-Token"]').content; const siteaccess = doc.querySelector('meta[name="SiteAccess"]').content; - const sortContainer = doc.querySelector('[data-sort-field][data-sort-order]'); const emdedItemsUpdateChannel = new BroadcastChannel('ibexa-emded-item-live-update'); const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); @@ -126,8 +125,8 @@ }; listContainers.forEach((container) => { - const sortField = sortContainer.getAttribute('data-sort-field'); - const sortOrder = sortContainer.getAttribute('data-sort-order'); + const sortField = container.getAttribute('data-sort-field'); + const sortOrder = container.getAttribute('data-sort-order'); const subitemsRoot = ReactDOM.createRoot(container); const parentLocationId = parseInt(container.dataset.location, 10); const activeView = getLocationActiveView(parentLocationId); diff --git a/src/bundle/Resources/public/js/scripts/admin.user.mode.js b/src/bundle/Resources/public/js/scripts/admin.user.mode.js new file mode 100644 index 0000000000..65be41b779 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/admin.user.mode.js @@ -0,0 +1,12 @@ +(function (global, doc) { + const FORM_SELECTOR = 'form[name=user_mode_change]'; + const form = doc.querySelector(FORM_SELECTOR); + + if (form) { + form.querySelectorAll('input[type=checkbox]').forEach((input) => { + input.addEventListener('change', () => { + form.submit(); + }); + }); + } +})(window, window.document); diff --git a/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff b/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff index 0f3169a2c8..e5b86c6666 100644 --- a/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff +++ b/src/bundle/Resources/translations/ibexa_admin_ui.en.xliff @@ -26,6 +26,16 @@ Removed '%languageCode%' translation from '%name%'. key: translation.remove.success + + Expert mode + Expert mode + key: user.mode.expert + + + Smart mode + Smart mode + key: user.mode.smart + Removed version(s) from '%name%'. Removed version(s) from '%name%'. diff --git a/src/bundle/Resources/translations/ibexa_locationview.en.xliff b/src/bundle/Resources/translations/ibexa_locationview.en.xliff index 9b8eb2812e..fb26408d19 100644 --- a/src/bundle/Resources/translations/ibexa_locationview.en.xliff +++ b/src/bundle/Resources/translations/ibexa_locationview.en.xliff @@ -126,6 +126,16 @@ Location ID key: tab.author.location_id + + Modified + Modified + key: tab.author.modified + + + Published + Published + key: tab.author.published + Change section Change section @@ -266,6 +276,11 @@ Authors key: tab.name.authors + + Data + Data + key: tab.name.data + Technical Details Technical Details @@ -281,11 +296,6 @@ Policies key: tab.name.policies - - Quick Preview - Quick Preview - key: tab.name.quick_preview - Relations Relations diff --git a/src/bundle/Resources/views/themes/admin/content/tab/authors.html.twig b/src/bundle/Resources/views/themes/admin/content/tab/authors.html.twig index 79cb4fc939..221c555854 100644 --- a/src/bundle/Resources/views/themes/admin/content/tab/authors.html.twig +++ b/src/bundle/Resources/views/themes/admin/content/tab/authors.html.twig @@ -21,10 +21,21 @@ label: 'tab.author.creator'|trans()|desc('Creator'), content: creator_name, }, + { + label: 'tab.author.published'|trans|desc('Published'), + content: content_info.publishedDate | ibexa_full_datetime + }, + { + is_break: true + }, { label: 'tab.author.last_contributor'|trans()|desc('Last contributor'), content: last_contributor_name, - } + }, + { + label: 'tab.author.modified'|trans|desc('Modified'), + content: content_info.modificationDate | ibexa_full_datetime + }, ] %} {% include '@ibexadesign/ui/component/details/details.html.twig' with { diff --git a/src/bundle/Resources/views/themes/admin/content/tab/details.html.twig b/src/bundle/Resources/views/themes/admin/content/tab/details.html.twig index c70e63032e..28a7a5f82f 100644 --- a/src/bundle/Resources/views/themes/admin/content/tab/details.html.twig +++ b/src/bundle/Resources/views/themes/admin/content/tab/details.html.twig @@ -131,20 +131,20 @@ {% include '@ibexadesign/ui/component/table/table_header.html.twig' with { headline: 'tab.details.sub_items_sorting_order'|trans|desc('Sub-item sorting order'), } only %} - -
+ +
{{ form_start(form_location_update, { 'method': 'POST', 'action': path('ibexa.location.update'), 'attr': {'class': 'form-inline ibexa-form-inline justify-content-start'} }) }} - + {{ form_label(form_location_update.sort_field, 'tab.details.sub_items_listing_by.order_by'|trans|desc('Order by')) }} {{ form_widget(form_location_update.sort_field, { 'attr': {'class': 'ibexa-form-autosubmit ml-2'} }) }} - + {{ form_label(form_location_update.sort_order, 'tab.details.sub_items_listing_by.in'|trans|desc('in')) }} {{ form_widget(form_location_update.sort_order, { 'attr': {'class': 'ibexa-form-autosubmit ml-2'} }) }} - + {% do form_location_update.update.setRendered() %} {{ form_end(form_location_update) }}
diff --git a/src/bundle/Resources/views/themes/admin/content/tab/translations/tab.html.twig b/src/bundle/Resources/views/themes/admin/content/tab/translations/tab.html.twig index 5db8da0f66..ef431c63b6 100644 --- a/src/bundle/Resources/views/themes/admin/content/tab/translations/tab.html.twig +++ b/src/bundle/Resources/views/themes/admin/content/tab/translations/tab.html.twig @@ -29,9 +29,14 @@ {% set body_row_cols = body_row_cols|merge([ { content: translation.name }, - { content: translation.languageCode }, ]) %} + {% if ibexa_is_expert_mode() %} + {% set body_row_cols = body_row_cols|merge([ + { content: translation.languageCode } + ]) %} + {% endif %} + {% if main_translation_switch %} {% set col_raw %}
diff --git a/src/bundle/Resources/views/themes/admin/ui/form_fields.html.twig b/src/bundle/Resources/views/themes/admin/ui/form_fields.html.twig index 0c5aadbce5..078929f6c5 100644 --- a/src/bundle/Resources/views/themes/admin/ui/form_fields.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/form_fields.html.twig @@ -67,9 +67,9 @@ {%- endfor -%} {%- endblock -%} -{%- block search_widget -%} +{%- block search_widget -%} {% set has_search = true %} - + {{ block('form_widget_simple') }} {%- endblock -%} @@ -554,3 +554,12 @@ {%- endblock -%} + +{% block user_mode_toggle_widget %} + {% set label_on = 'user.mode.expert'|trans({}, 'ibexa_admin_ui')|desc('Expert mode') %} + {% set label_off = 'user.mode.smart'|trans({}, 'ibexa_admin_ui')|desc('Smart mode') %} + {% set small = true %} + {% set checked = ibexa_is_expert_mode() %} + + {{ block('toggle_widget') }} +{% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig b/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig index 9949032b17..2e0e02c2ab 100644 --- a/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig +++ b/src/bundle/Resources/views/themes/admin/ui/menu/user.html.twig @@ -6,9 +6,9 @@
@@ -27,8 +27,17 @@
- {{ 'user_menu.popup_menu.title'|trans({'%userName%': user.name})|desc('Logged as %userName%') }} + {% block current_user %} + {{ 'user_menu.popup_menu.title'|trans({'%userName%': user.name})|desc('Logged as %userName%') }} + {% endblock %} + + {% block user_mode_toggle %} + {{ render(controller('Ibexa\\Bundle\\AdminUi\\Controller\\User\\UserModeController::changeAction', { + returnUrl: url(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) + })) }} + {% endblock %}
+ {{ parent() }}
@@ -38,7 +47,7 @@ {% if item.name != 'root' %} {% set link_attributes = item.linkAttributes|merge({'class': (item.linkAttributes.class|default('') ~ ' ibexa-popup-menu__item-content')|trim}) %} {% set attributes = item.attributes|merge({'class': (item.attributes.class|default('') ~ ' ibexa-popup-menu__item')|trim}) %} - + {% do item.setLinkAttributes(link_attributes) %} {% do item.setAttributes(attributes) %} {% endif %} @@ -52,6 +61,6 @@ {% block linkElement %} {% import _self as knp_menu %} - + {{ block('label') }} {% endblock %} diff --git a/src/bundle/Resources/views/themes/admin/ui/user_mode_form.html.twig b/src/bundle/Resources/views/themes/admin/ui/user_mode_form.html.twig new file mode 100644 index 0000000000..b31c1edcd7 --- /dev/null +++ b/src/bundle/Resources/views/themes/admin/ui/user_mode_form.html.twig @@ -0,0 +1,7 @@ +{% trans_default_domain 'ibexa_admin_ui' %} + +{% form_theme form '@ibexadesign/ui/form_fields.html.twig' %} + +{{ form_start(form, { attr: { class: 'mt-2'}}) }} + {{ form_widget(form.mode) }} +{{ form_end(form) }} diff --git a/src/bundle/Templating/Twig/LocationExtension.php b/src/bundle/Templating/Twig/LocationExtension.php new file mode 100644 index 0000000000..d4473f6731 --- /dev/null +++ b/src/bundle/Templating/Twig/LocationExtension.php @@ -0,0 +1,42 @@ + 'LocationPath', + Location::SORT_FIELD_PUBLISHED => 'DatePublished', + Location::SORT_FIELD_MODIFIED => 'DateModified', + Location::SORT_FIELD_SECTION => 'SectionIdentifier', + Location::SORT_FIELD_DEPTH => 'LocationDepth', + Location::SORT_FIELD_PRIORITY => 'LocationPriority', + Location::SORT_FIELD_NAME => 'ContentName', + Location::SORT_FIELD_NODE_ID => 'LocationId', + Location::SORT_FIELD_CONTENTOBJECT_ID => 'ContentId', + ]; + + public function getFunctions(): array + { + return [ + new TwigFunction( + 'ibexa_location_sort_order_as_rest_value', + static fn (Location $location): string => $location->sortOrder ? 'ascending' : 'descending' + ), + new TwigFunction( + 'ibexa_location_sort_field_as_rest_sort_clause', + static fn (Location $location): string => self::SORT_FIELD_TO_SORT_CLAUSE_MAP[$location->sortField] + ), + ]; + } +} diff --git a/src/bundle/Templating/Twig/UserModeExtension.php b/src/bundle/Templating/Twig/UserModeExtension.php new file mode 100644 index 0000000000..fa793214ea --- /dev/null +++ b/src/bundle/Templating/Twig/UserModeExtension.php @@ -0,0 +1,43 @@ +userService = $userService; + } + + public function getFunctions(): array + { + return [ + new TwigFunction( + 'ibexa_is_expert_mode', + fn (): bool => $this->isModeEnabled(UserMode::EXPERT) + ), + new TwigFunction( + 'ibexa_is_smart_mode', + fn (): bool => $this->isModeEnabled(UserMode::SMART) + ), + ]; + } + + private function isModeEnabled(string $mode): bool + { + return $this->userService->getUserSetting(UserMode::IDENTIFIER)->value === $mode; + } +} diff --git a/src/lib/Behat/BrowserContext/ContentViewContext.php b/src/lib/Behat/BrowserContext/ContentViewContext.php index bedd3a578c..6a122fc9bf 100644 --- a/src/lib/Behat/BrowserContext/ContentViewContext.php +++ b/src/lib/Behat/BrowserContext/ContentViewContext.php @@ -11,33 +11,27 @@ use Behat\Behat\Context\Context; use Behat\Gherkin\Node\TableNode; use Ibexa\AdminUi\Behat\Component\DraftConflictDialog; -use Ibexa\AdminUi\Behat\Component\UniversalDiscoveryWidget; use Ibexa\AdminUi\Behat\Page\ContentViewPage; use Ibexa\Behat\Core\Behat\ArgumentParser; use PHPUnit\Framework\Assert; -class ContentViewContext implements Context +final class ContentViewContext implements Context { private $argumentParser; /** @var \Ibexa\AdminUi\Behat\Page\ContentViewPage */ private $contentViewPage; - /** @var \Ibexa\AdminUi\Behat\Component\UniversalDiscoveryWidget */ - private $universalDiscoveryWidget; - /** @var \Ibexa\AdminUi\Behat\Component\DraftConflictDialog */ private $draftConflictDialog; public function __construct( ArgumentParser $argumentParser, ContentViewPage $contentViewPage, - UniversalDiscoveryWidget $universalDiscoveryWidget, DraftConflictDialog $draftConflictDialog ) { $this->argumentParser = $argumentParser; $this->contentViewPage = $contentViewPage; - $this->universalDiscoveryWidget = $universalDiscoveryWidget; $this->draftConflictDialog = $draftConflictDialog; } @@ -50,6 +44,14 @@ public function startCreatingContent(string $contentType, string $language = nul $this->contentViewPage->startCreatingContent($contentType, $language); } + /** + * @Given I am using the DXP in :mode mode + */ + public function switchToUserMode(string $mode): void + { + $this->contentViewPage->switchToUserMode($mode); + } + /** * @Given I switch to :tab tab in Content structure */ diff --git a/src/lib/Behat/Component/UpperMenu.php b/src/lib/Behat/Component/UpperMenu.php index 86ad3e8d1c..0cbcc2b332 100644 --- a/src/lib/Behat/Component/UpperMenu.php +++ b/src/lib/Behat/Component/UpperMenu.php @@ -9,11 +9,15 @@ namespace Ibexa\AdminUi\Behat\Component; use Ibexa\Behat\Browser\Component\Component; +use Ibexa\Behat\Browser\Element\Condition\ElementNotExistsCondition; use Ibexa\Behat\Browser\Element\Criterion\ElementTextCriterion; use Ibexa\Behat\Browser\Locator\VisibleCSSLocator; +use PHPUnit\Framework\Assert; class UpperMenu extends Component { + private const USER_MODES = ['Smart', 'Expert']; + public function goToDashboard(): void { $this->getHTMLPage()->find($this->getLocator('dashboardLink'))->click(); @@ -44,6 +48,41 @@ public function chooseFromUserDropdown(string $option): void $this->getHTMLPage()->findAll($this->getLocator('userSettingsItem'))->getByCriterion(new ElementTextCriterion($option))->click(); } + public function switchToUserMode(string $newMode): void + { + $this->getHTMLPage()->find($this->getLocator('userSettingsToggle'))->click(); + + $currentMode = explode( + ' ', + $this->getHTMLPage()->find($this->getLocator('userMode'))->getText() + )[0]; + + if (strtolower($newMode) !== strtolower($currentMode)) { + $this->getHTMLPage()->find($this->getLocator('userMode'))->click(); + $this->getHTMLPage() + ->waitUntilCondition( + new ElementNotExistsCondition($this->getHTMLPage(), $this->getLocator('userSettingsPopup')) + ); + } else { + $this->getHTMLPage()->find($this->getLocator('userSettingsToggle'))->click(); + } + } + + public function getCurrentUserMode(): string + { + $this->getHTMLPage()->find($this->getLocator('userSettingsToggle'))->click(); + + $mode = explode( + ' ', + $this->getHTMLPage()->find($this->getLocator('userMode'))->getText() + )[0]; + Assert::assertContains($mode, self::USER_MODES); + + $this->getHTMLPage()->find($this->getLocator('userSettingsToggle'))->click(); + + return $mode; + } + public function verifyIsLoaded(): void { $this->getHTMLPage()->find($this->getLocator('userSettingsToggle'))->assert()->isVisible(); @@ -57,8 +96,10 @@ protected function specifyLocators(): array new VisibleCSSLocator('userSettingsToggle', '.ibexa-header-user-menu'), new VisibleCSSLocator('userNotifications', '.ibexa-header-user-menu__notifications-toggler'), new VisibleCSSLocator('userSettingsItem', '.ibexa-popup-menu__item'), + new VisibleCSSLocator('userSettingsPopup', '.ibexa-header-user-menu .ibexa-header-user-menu__popup-menu'), new VisibleCSSLocator('searchInput', '.ibexa-main-header #search_query'), new VisibleCSSLocator('searchButton', '.ibexa-main-header .ibexa-input-text-wrapper__action-btn--search'), + new VisibleCSSLocator('userMode', '[name="user_mode_change"] .ibexa-toggle__label'), ]; } } diff --git a/src/lib/Behat/Page/ContentViewPage.php b/src/lib/Behat/Page/ContentViewPage.php index c85af5443c..72ce5ec0ce 100644 --- a/src/lib/Behat/Page/ContentViewPage.php +++ b/src/lib/Behat/Page/ContentViewPage.php @@ -19,6 +19,7 @@ use Ibexa\AdminUi\Behat\Component\SubItemsList; use Ibexa\AdminUi\Behat\Component\TranslationDialog; use Ibexa\AdminUi\Behat\Component\UniversalDiscoveryWidget; +use Ibexa\AdminUi\Behat\Component\UpperMenu; use Ibexa\Behat\Browser\Element\Condition\ElementExistsCondition; use Ibexa\Behat\Browser\Element\Criterion\ElementTextCriterion; use Ibexa\Behat\Browser\Locator\VisibleCSSLocator; @@ -44,9 +45,6 @@ class ContentViewPage extends Page /** @var \Ibexa\AdminUi\Behat\Component\ContentTypePicker */ private $contentTypePicker; - /** @var ContentUpdateItemPage */ - private $contentUpdatePage; - /** @var string */ private $expectedContentType; @@ -70,9 +68,6 @@ class ContentViewPage extends Page /** @var \Ibexa\AdminUi\Behat\Component\ContentItemAdminPreview */ private $contentItemAdminPreview; - /** @var \Ibexa\AdminUi\Behat\Page\UserUpdatePage */ - private $userUpdatePage; - /** @var \Ibexa\Contracts\Core\Repository\Repository */ private $repository; @@ -85,40 +80,39 @@ class ContentViewPage extends Page /** @var \Ibexa\AdminUi\Behat\Component\IbexaDropdown */ private $ibexaDropdown; + private UpperMenu $upperMenu; + public function __construct( Session $session, Router $router, ContentActionsMenu $contentActionsMenu, SubItemsList $subItemList, ContentTypePicker $contentTypePicker, - ContentUpdateItemPage $contentUpdatePage, LanguagePicker $languagePicker, Dialog $dialog, TranslationDialog $translationDialog, Repository $repository, Breadcrumb $breadcrumb, ContentItemAdminPreview $contentItemAdminPreview, - UserUpdatePage $userUpdatePage, ArgumentParser $argumentParser, UniversalDiscoveryWidget $universalDiscoveryWidget, - IbexaDropdown $ibexaDropdown + IbexaDropdown $ibexaDropdown, + UpperMenu $upperMenu ) { parent::__construct($session, $router); - $this->contentActionsMenu = $contentActionsMenu; $this->subItemList = $subItemList; $this->contentTypePicker = $contentTypePicker; - $this->contentUpdatePage = $contentUpdatePage; $this->languagePicker = $languagePicker; $this->dialog = $dialog; $this->translationDialog = $translationDialog; $this->breadcrumb = $breadcrumb; $this->contentItemAdminPreview = $contentItemAdminPreview; - $this->userUpdatePage = $userUpdatePage; $this->repository = $repository; $this->argumentParser = $argumentParser; $this->universalDiscoveryWidget = $universalDiscoveryWidget; $this->ibexaDropdown = $ibexaDropdown; + $this->upperMenu = $upperMenu; } public function startCreatingContent(string $contentTypeName, string $language = null) @@ -188,6 +182,29 @@ public function choosePreview(string $language): void $this->verifyIsLoaded(); } + public function switchToUserMode(string $mode): void + { + $this->upperMenu->switchToUserMode($mode); + + $expertModeTab = 'Technical Details'; + + if (strtolower($mode) === 'expert') { + $this->getHTMLPage() + ->setTimeout(3) + ->findAll($this->getLocator('tab')) + ->getByCriterion(new ElementTextCriterion($expertModeTab)) + ->assert() + ->isVisible(); + } else { + $this->getHTMLPage() + ->setTimeout(3) + ->findAll($this->getLocator('tab')) + ->filterBy(new ElementTextCriterion($expertModeTab)) + ->assert() + ->isEmpty(); + } + } + public function goToSubItem(string $contentItemName): void { $this->switchToTab('Sub-items'); diff --git a/src/lib/Form/Data/User/UserModeChangeData.php b/src/lib/Form/Data/User/UserModeChangeData.php new file mode 100644 index 0000000000..cc3100cf0c --- /dev/null +++ b/src/lib/Form/Data/User/UserModeChangeData.php @@ -0,0 +1,29 @@ +mode = $data; + } + + public function getMode(): ?bool + { + return $this->mode; + } + + public function setMode(?bool $mode): void + { + $this->mode = $mode; + } +} diff --git a/src/lib/Form/Type/User/UserModeChangeType.php b/src/lib/Form/Type/User/UserModeChangeType.php new file mode 100644 index 0000000000..2d8ef67ff4 --- /dev/null +++ b/src/lib/Form/Type/User/UserModeChangeType.php @@ -0,0 +1,37 @@ +add( + 'mode', + CheckboxType::class, + [ + 'label' => null, + 'block_prefix' => 'user_mode_toggle', + ] + ); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => UserModeChangeData::class, + ]); + } +} diff --git a/src/lib/Menu/MainMenuBuilder.php b/src/lib/Menu/MainMenuBuilder.php index 21ece56c5d..d4c498b989 100644 --- a/src/lib/Menu/MainMenuBuilder.php +++ b/src/lib/Menu/MainMenuBuilder.php @@ -9,9 +9,12 @@ namespace Ibexa\AdminUi\Menu; use Ibexa\AdminUi\Menu\Event\ConfigureMenuEvent; +use Ibexa\AdminUi\Specification\UserMode\IsUserModeEnabled; +use Ibexa\AdminUi\UserSetting\UserMode; use Ibexa\Contracts\AdminUi\Menu\AbstractBuilder; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; +use Ibexa\User\UserSetting\UserSettingService; use JMS\TranslationBundle\Model\Message; use JMS\TranslationBundle\Translation\TranslationContainerInterface; use Knp\Menu\ItemInterface; @@ -141,6 +144,8 @@ class MainMenuBuilder extends AbstractBuilder implements TranslationContainerInt /** @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface */ private $tokenStorage; + private UserSettingService $userSettingService; + /** * @param \Ibexa\AdminUi\Menu\MenuItemFactory $factory * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher @@ -152,13 +157,15 @@ public function __construct( EventDispatcherInterface $eventDispatcher, ConfigResolverInterface $configResolver, PermissionResolver $permissionResolver, - TokenStorageInterface $tokenStorage + TokenStorageInterface $tokenStorage, + UserSettingService $userSettingService ) { parent::__construct($factory, $eventDispatcher); $this->configResolver = $configResolver; $this->permissionResolver = $permissionResolver; $this->tokenStorage = $tokenStorage; + $this->userSettingService = $userSettingService; } protected function getConfigureEventName(): string @@ -297,32 +304,34 @@ private function addContentMenuItems(ItemInterface $menu): void ] ); - $contentGroupSettings = $menu->addChild( - self::ITEM_CONTENT_GROUP_SETTINGS, - [ - 'extras' => [ - 'orderNumber' => 75, + if (IsUserModeEnabled::fromUserSettings($this->userSettingService)->isSatisfiedBy(UserMode::EXPERT)) { + $contentGroupSettings = $menu->addChild( + self::ITEM_CONTENT_GROUP_SETTINGS, + [ + 'extras' => [ + 'orderNumber' => 75, + ], ], - ], - ); - - if ($this->permissionResolver->hasAccess('section', 'view') !== false) { - $contentGroupSettings->addChild( - self::ITEM_ADMIN__SECTIONS, - self::ITEM_ADMIN_OPTIONS[self::ITEM_ADMIN__SECTIONS] ); - } - $contentGroupSettings->addChild( - self::ITEM_ADMIN__CONTENT_TYPES, - self::ITEM_ADMIN_OPTIONS[self::ITEM_ADMIN__CONTENT_TYPES] - ); + if ($this->permissionResolver->hasAccess('section', 'view') !== false) { + $contentGroupSettings->addChild( + self::ITEM_ADMIN__SECTIONS, + self::ITEM_ADMIN_OPTIONS[self::ITEM_ADMIN__SECTIONS] + ); + } - if ($this->permissionResolver->hasAccess('state', 'administrate')) { $contentGroupSettings->addChild( - self::ITEM_ADMIN__OBJECT_STATES, - self::ITEM_ADMIN_OPTIONS[self::ITEM_ADMIN__OBJECT_STATES] + self::ITEM_ADMIN__CONTENT_TYPES, + self::ITEM_ADMIN_OPTIONS[self::ITEM_ADMIN__CONTENT_TYPES] ); + + if ($this->permissionResolver->hasAccess('state', 'administrate')) { + $contentGroupSettings->addChild( + self::ITEM_ADMIN__OBJECT_STATES, + self::ITEM_ADMIN_OPTIONS[self::ITEM_ADMIN__OBJECT_STATES] + ); + } } if (null !== $contentStructureItem) { diff --git a/src/lib/Specification/UserMode/IsUserModeEnabled.php b/src/lib/Specification/UserMode/IsUserModeEnabled.php new file mode 100644 index 0000000000..ee0026f0e7 --- /dev/null +++ b/src/lib/Specification/UserMode/IsUserModeEnabled.php @@ -0,0 +1,36 @@ +mode = $mode; + } + + /** + * @param string $item + */ + public function isSatisfiedBy($item): bool + { + return $this->mode === $item; + } + + public static function fromUserSettings(UserSettingService $userService): self + { + return new self($userService->getUserSetting(UserMode::IDENTIFIER)->value); + } +} diff --git a/src/lib/Tab/LocationView/AuthorsTab.php b/src/lib/Tab/LocationView/AuthorsTab.php index 3178d2d998..8e701c4af3 100644 --- a/src/lib/Tab/LocationView/AuthorsTab.php +++ b/src/lib/Tab/LocationView/AuthorsTab.php @@ -9,31 +9,39 @@ namespace Ibexa\AdminUi\Tab\LocationView; use Ibexa\AdminUi\Specification\UserExists; +use Ibexa\AdminUi\Specification\UserMode\IsUserModeEnabled; +use Ibexa\AdminUi\UserSetting\UserMode; use Ibexa\Contracts\AdminUi\Tab\AbstractEventDispatchingTab; +use Ibexa\Contracts\AdminUi\Tab\ConditionalTabInterface; use Ibexa\Contracts\AdminUi\Tab\OrderedTabInterface; use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo; use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo; +use Ibexa\User\UserSetting\UserSettingService; use JMS\TranslationBundle\Annotation\Desc; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; -class AuthorsTab extends AbstractEventDispatchingTab implements OrderedTabInterface +class AuthorsTab extends AbstractEventDispatchingTab implements OrderedTabInterface, ConditionalTabInterface { public const URI_FRAGMENT = 'ibexa-tab-location-view-authors'; private UserService $userService; + private UserSettingService $userSettingService; + public function __construct( Environment $twig, TranslatorInterface $translator, UserService $userService, + UserSettingService $userSettingService, EventDispatcherInterface $eventDispatcher ) { parent::__construct($twig, $translator, $eventDispatcher); $this->userService = $userService; + $this->userSettingService = $userSettingService; } public function getIdentifier(): string @@ -82,6 +90,11 @@ public function getTemplateParameters(array $contextParameters = []): array return array_replace($contextParameters, $viewParameters); } + public function evaluate(array $parameters): bool + { + return IsUserModeEnabled::fromUserSettings($this->userSettingService)->isSatisfiedBy(UserMode::SMART); + } + /** * @param array $parameters */ diff --git a/src/lib/Tab/LocationView/ContentTab.php b/src/lib/Tab/LocationView/ContentTab.php index f422660160..100a01a5f3 100644 --- a/src/lib/Tab/LocationView/ContentTab.php +++ b/src/lib/Tab/LocationView/ContentTab.php @@ -53,8 +53,7 @@ public function getIdentifier(): string public function getName(): string { - /** @Desc("Quick Preview") */ - return $this->translator->trans('tab.name.quick_preview', [], 'ibexa_locationview'); + return $this->translator->trans(/** @Desc("Data") */ 'tab.name.data', [], 'ibexa_locationview'); } public function getOrder(): int diff --git a/src/lib/Tab/LocationView/DetailsTab.php b/src/lib/Tab/LocationView/DetailsTab.php index 500b68e17d..d2d99dd7ea 100644 --- a/src/lib/Tab/LocationView/DetailsTab.php +++ b/src/lib/Tab/LocationView/DetailsTab.php @@ -15,21 +15,25 @@ use Ibexa\AdminUi\Form\Type\Location\LocationAssignSectionType; use Ibexa\AdminUi\Form\Type\Location\LocationUpdateType; use Ibexa\AdminUi\Form\Type\ObjectState\ContentObjectStateUpdateType; +use Ibexa\AdminUi\Specification\UserMode\IsUserModeEnabled; use Ibexa\AdminUi\UI\Dataset\DatasetFactory; +use Ibexa\AdminUi\UserSetting\UserMode; use Ibexa\Contracts\AdminUi\Tab\AbstractEventDispatchingTab; +use Ibexa\Contracts\AdminUi\Tab\ConditionalTabInterface; use Ibexa\Contracts\AdminUi\Tab\OrderedTabInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\SectionService; use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo; use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo; +use Ibexa\User\UserSetting\UserSettingService; use JMS\TranslationBundle\Annotation\Desc; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; -class DetailsTab extends AbstractEventDispatchingTab implements OrderedTabInterface +class DetailsTab extends AbstractEventDispatchingTab implements OrderedTabInterface, ConditionalTabInterface { public const URI_FRAGMENT = 'ibexa-tab-location-view-details'; @@ -41,6 +45,8 @@ class DetailsTab extends AbstractEventDispatchingTab implements OrderedTabInterf private PermissionResolver $permissionResolver; + private UserSettingService $userSettingService; + public function __construct( Environment $twig, TranslatorInterface $translator, @@ -48,6 +54,7 @@ public function __construct( DatasetFactory $datasetFactory, FormFactoryInterface $formFactory, PermissionResolver $permissionResolver, + UserSettingService $userSettingService, EventDispatcherInterface $eventDispatcher ) { parent::__construct($twig, $translator, $eventDispatcher); @@ -56,6 +63,7 @@ public function __construct( $this->datasetFactory = $datasetFactory; $this->formFactory = $formFactory; $this->permissionResolver = $permissionResolver; + $this->userSettingService = $userSettingService; } public function getIdentifier(): string @@ -74,6 +82,11 @@ public function getOrder(): int return 200; } + public function evaluate(array $parameters): bool + { + return IsUserModeEnabled::fromUserSettings($this->userSettingService)->isSatisfiedBy(UserMode::EXPERT); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Tab/LocationView/LocationsTab.php b/src/lib/Tab/LocationView/LocationsTab.php index 82af05294d..8a2f1f4342 100644 --- a/src/lib/Tab/LocationView/LocationsTab.php +++ b/src/lib/Tab/LocationView/LocationsTab.php @@ -14,8 +14,11 @@ use Ibexa\AdminUi\Form\Data\Location\LocationSwapData; use Ibexa\AdminUi\Form\Data\Location\LocationUpdateVisibilityData; use Ibexa\AdminUi\Form\Factory\FormFactory; +use Ibexa\AdminUi\Specification\UserMode\IsUserModeEnabled; use Ibexa\AdminUi\UI\Value\Content\Location\Mapper; +use Ibexa\AdminUi\UserSetting\UserMode; use Ibexa\Contracts\AdminUi\Tab\AbstractEventDispatchingTab; +use Ibexa\Contracts\AdminUi\Tab\ConditionalTabInterface; use Ibexa\Contracts\AdminUi\Tab\OrderedTabInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\SearchService; @@ -24,6 +27,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Query; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Pagination\Pagerfanta\LocationSearchAdapter; +use Ibexa\User\UserSetting\UserSettingService; use JMS\TranslationBundle\Annotation\Desc; use Pagerfanta\Pagerfanta; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -33,7 +37,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; -class LocationsTab extends AbstractEventDispatchingTab implements OrderedTabInterface +class LocationsTab extends AbstractEventDispatchingTab implements OrderedTabInterface, ConditionalTabInterface { public const URI_FRAGMENT = 'ibexa-tab-location-view-locations'; private const PAGINATION_PARAM_NAME = 'locations-tab-page'; @@ -59,6 +63,8 @@ class LocationsTab extends AbstractEventDispatchingTab implements OrderedTabInte /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ private $configResolver; + private UserSettingService $userSettingService; + /** * @param \Twig\Environment $twig * @param \Symfony\Contracts\Translation\TranslatorInterface $translator @@ -81,7 +87,8 @@ public function __construct( SearchService $searchService, RequestStack $requestStack, Mapper $locationToUILocationMapper, - ConfigResolverInterface $configResolver + ConfigResolverInterface $configResolver, + UserSettingService $userSettingService ) { parent::__construct($twig, $translator, $eventDispatcher); @@ -92,6 +99,7 @@ public function __construct( $this->configResolver = $configResolver; $this->searchService = $searchService; $this->locationToUILocationMapper = $locationToUILocationMapper; + $this->userSettingService = $userSettingService; } /** @@ -119,6 +127,11 @@ public function getOrder(): int return 400; } + public function evaluate(array $parameters): bool + { + return IsUserModeEnabled::fromUserSettings($this->userSettingService)->isSatisfiedBy(UserMode::EXPERT); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Tab/LocationView/VersionsTab.php b/src/lib/Tab/LocationView/VersionsTab.php index ba3197b81f..be5764431c 100644 --- a/src/lib/Tab/LocationView/VersionsTab.php +++ b/src/lib/Tab/LocationView/VersionsTab.php @@ -12,13 +12,16 @@ use Ibexa\AdminUi\Form\Data\Version\VersionRemoveData; use Ibexa\AdminUi\Form\Factory\FormFactory; use Ibexa\AdminUi\Specification\ContentIsUser; +use Ibexa\AdminUi\Specification\UserMode\IsUserModeEnabled; use Ibexa\AdminUi\UI\Dataset\DatasetFactory; +use Ibexa\AdminUi\UserSetting\UserMode; use Ibexa\Contracts\AdminUi\Tab\AbstractEventDispatchingTab; use Ibexa\Contracts\AdminUi\Tab\ConditionalTabInterface; use Ibexa\Contracts\AdminUi\Tab\OrderedTabInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Contracts\Core\Repository\Values\Content\Location; +use Ibexa\User\UserSetting\UserSettingService; use JMS\TranslationBundle\Annotation\Desc; use Pagerfanta\Adapter\ArrayAdapter; use Pagerfanta\Pagerfanta; @@ -49,6 +52,8 @@ class VersionsTab extends AbstractEventDispatchingTab implements OrderedTabInter /** @var \Ibexa\Contracts\Core\Repository\UserService */ private $userService; + private UserSettingService $userSettingService; + /** * @param \Twig\Environment $twig * @param \Symfony\Contracts\Translation\TranslatorInterface $translator @@ -67,6 +72,7 @@ public function __construct( UrlGeneratorInterface $urlGenerator, PermissionResolver $permissionResolver, UserService $userService, + UserSettingService $userSettingService, EventDispatcherInterface $eventDispatcher ) { parent::__construct($twig, $translator, $eventDispatcher); @@ -76,6 +82,7 @@ public function __construct( $this->urlGenerator = $urlGenerator; $this->permissionResolver = $permissionResolver; $this->userService = $userService; + $this->userSettingService = $userSettingService; } /** @@ -115,7 +122,12 @@ public function getOrder(): int */ public function evaluate(array $parameters): bool { - return $this->permissionResolver->canUser('content', 'versionread', $parameters['content']); + $isExpertMode = IsUserModeEnabled::fromUserSettings($this->userSettingService)->isSatisfiedBy(UserMode::EXPERT); + if ($isExpertMode) { + return $this->permissionResolver->canUser('content', 'versionread', $parameters['content']); + } + + return false; } /** diff --git a/src/lib/UserSetting/UserMode.php b/src/lib/UserSetting/UserMode.php index b1a9faab9d..16b9b0db5c 100644 --- a/src/lib/UserSetting/UserMode.php +++ b/src/lib/UserSetting/UserMode.php @@ -19,6 +19,8 @@ final class UserMode implements ValueDefinitionInterface, FormMapperInterface, TranslationContainerInterface { + public const IDENTIFIER = 'user_mode'; + public const EXPERT = '0'; public const SMART = '1'; diff --git a/tests/bundle/Templating/Twig/LocationExtensionTest.php b/tests/bundle/Templating/Twig/LocationExtensionTest.php new file mode 100644 index 0000000000..c1baee526f --- /dev/null +++ b/tests/bundle/Templating/Twig/LocationExtensionTest.php @@ -0,0 +1,61 @@ +createMock(Location::class); + $location->method('__get')->with('sortField')->willReturn($field); + + return $location; + } + + /** + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Location&\PHPUnit\Framework\MockObject\MockObject + */ + public function createLocationWithSortOrder(int $order): Location + { + $location = $this->createMock(Location::class); + $location->method('__get')->with('sortOrder')->willReturn($order); + + return $location; + } +} diff --git a/tests/bundle/Templating/Twig/UserModeExtensionTest.php b/tests/bundle/Templating/Twig/UserModeExtensionTest.php new file mode 100644 index 0000000000..f5a676c438 --- /dev/null +++ b/tests/bundle/Templating/Twig/UserModeExtensionTest.php @@ -0,0 +1,36 @@ +createMock(UserSetting::class); + $userSetting->method('__get')->with('value')->willReturn(UserMode::SMART); + + $userSettingService = $this->createMock(UserSettingService::class); + $userSettingService->method('getUserSetting')->with(UserMode::IDENTIFIER)->willReturn($userSetting); + + return [ + new UserModeExtension($userSettingService), + ]; + } + + protected function getFixturesDir(): string + { + return __DIR__ . '/_fixtures/user_mode/'; + } +} diff --git a/tests/bundle/Templating/Twig/_fixtures/location/ibexa_location_sort_field_as_rest_sort_clause.test b/tests/bundle/Templating/Twig/_fixtures/location/ibexa_location_sort_field_as_rest_sort_clause.test new file mode 100644 index 0000000000..911c2c0d00 --- /dev/null +++ b/tests/bundle/Templating/Twig/_fixtures/location/ibexa_location_sort_field_as_rest_sort_clause.test @@ -0,0 +1,22 @@ +--TEST-- +"ibexa_location_sort_field_as_rest_sort_clause" function +--TEMPLATE-- +{% for location in locations %} +{{ ibexa_location_sort_field_as_rest_sort_clause(location) }} +{% endfor %} +--DATA-- +use \Ibexa\Contracts\Core\Repository\Values\Content\Location; + +return [ + 'locations' => $this->getLocationWithAllPossibleSortFields(), +]; +--EXPECT-- +LocationPath +DatePublished +DateModified +SectionIdentifier +LocationDepth +LocationPriority +ContentName +LocationId +ContentId diff --git a/tests/bundle/Templating/Twig/_fixtures/location/ibexa_location_sort_order_as_rest_value.test b/tests/bundle/Templating/Twig/_fixtures/location/ibexa_location_sort_order_as_rest_value.test new file mode 100644 index 0000000000..389719d545 --- /dev/null +++ b/tests/bundle/Templating/Twig/_fixtures/location/ibexa_location_sort_order_as_rest_value.test @@ -0,0 +1,15 @@ +--TEST-- +"ibexa_location_sort_order_as_rest_value" function +--TEMPLATE-- +{{ ibexa_location_sort_order_as_rest_value(location_asc) }} +{{ ibexa_location_sort_order_as_rest_value(location_desc) }} +--DATA-- +use \Ibexa\Contracts\Core\Repository\Values\Content\Location; + +return [ + 'location_asc' => $this->createLocationWithSortOrder(Location::SORT_ORDER_ASC), + 'location_desc' => $this->createLocationWithSortOrder(Location::SORT_ORDER_DESC), +]; +--EXPECT-- +ascending +descending diff --git a/tests/bundle/Templating/Twig/_fixtures/user_mode/ibexa_is_expert_mode.test b/tests/bundle/Templating/Twig/_fixtures/user_mode/ibexa_is_expert_mode.test new file mode 100644 index 0000000000..d8c57d4996 --- /dev/null +++ b/tests/bundle/Templating/Twig/_fixtures/user_mode/ibexa_is_expert_mode.test @@ -0,0 +1,8 @@ +--TEST-- +"ibexa_is_expert_mode" function +--TEMPLATE-- +{{ ibexa_is_expert_mode() ? 'YES' : 'NO' }} +--DATA-- +return []; +--EXPECT-- +NO diff --git a/tests/bundle/Templating/Twig/_fixtures/user_mode/ibexa_is_smart_mode.test b/tests/bundle/Templating/Twig/_fixtures/user_mode/ibexa_is_smart_mode.test new file mode 100644 index 0000000000..ad11d8e57a --- /dev/null +++ b/tests/bundle/Templating/Twig/_fixtures/user_mode/ibexa_is_smart_mode.test @@ -0,0 +1,8 @@ +--TEST-- +"ibexa_is_smart_mode" function +--TEMPLATE-- +{{ ibexa_is_smart_mode() ? 'YES' : 'NO' }} +--DATA-- +return []; +--EXPECT-- +YES diff --git a/tests/lib/Menu/MainMenuBuilerTest.php b/tests/lib/Menu/MainMenuBuilerTest.php index 0590b765b7..1316e7b447 100644 --- a/tests/lib/Menu/MainMenuBuilerTest.php +++ b/tests/lib/Menu/MainMenuBuilerTest.php @@ -10,9 +10,12 @@ use Ibexa\AdminUi\Menu\MainMenuBuilder; use Ibexa\AdminUi\Menu\MenuItemFactory; +use Ibexa\AdminUi\UserSetting\UserMode; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\MVC\Symfony\Security\UserInterface; +use Ibexa\User\UserSetting\UserSetting; +use Ibexa\User\UserSetting\UserSettingService; use Knp\Menu\FactoryInterface; use Knp\Menu\MenuItem; use PHPUnit\Framework\TestCase; @@ -37,6 +40,9 @@ class MainMenuBuilerTest extends TestCase /** @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface */ private $tokenStorage; + /** @var \Ibexa\User\UserSetting\UserSettingService&\PHPUnit\Framework\MockObject\MockObject */ + private UserSettingService $userSettingService; + protected function setUp(): void { $knpFactory = $this->createMock(FactoryInterface::class); @@ -151,11 +157,17 @@ protected function setUp(): void $token = new TestBrowserToken([], $this->createMock(UserInterface::class)); $this->tokenStorage->method('getToken')->willReturn($token); + + $userSetting = $this->createMock(UserSetting::class); + $userSetting->method('__get')->with('value')->willReturn(UserMode::EXPERT); + + $this->userSettingService = $this->createMock(UserSettingService::class); + $this->userSettingService->method('getUserSetting')->with(UserMode::IDENTIFIER)->willReturn($userSetting); } protected function tearDown(): void { - unset($this->factory, $this->eventDispatcher, $this->configResolver, $this->permissionResolver, $this->tokenStorage); + unset($this->factory, $this->eventDispatcher, $this->configResolver, $this->permissionResolver, $this->tokenStorage, $this->userSettingService); } public function testCreateMenuForUserWithAdministratePolicy() @@ -171,7 +183,8 @@ public function testCreateMenuForUserWithAdministratePolicy() $this->eventDispatcher, $this->configResolver, $this->permissionResolver, - $this->tokenStorage + $this->tokenStorage, + $this->userSettingService ); $menu = $menuBuilder->createStructure([]); @@ -193,7 +206,8 @@ public function testCreateMenuForUserWithoutAdministratePolicy() $this->eventDispatcher, $this->configResolver, $this->permissionResolver, - $this->tokenStorage + $this->tokenStorage, + $this->userSettingService ); $menu = $menuBuilder->createStructure([]); diff --git a/tests/lib/Specification/UserMode/IsUserModeEnabledTest.php b/tests/lib/Specification/UserMode/IsUserModeEnabledTest.php new file mode 100644 index 0000000000..4a82811bb2 --- /dev/null +++ b/tests/lib/Specification/UserMode/IsUserModeEnabledTest.php @@ -0,0 +1,57 @@ +isSatisfiedBy($value) + ); + } + + /** + * @dataProvider dataProviderForIsSatisfiedBy + */ + public function testFromUserSetting(string $userMode, string $value, bool $expectedResult): void + { + $userSetting = $this->createMock(UserSetting::class); + $userSetting->method('__get')->with('value')->willReturn($userMode); + + $userSettingService = $this->createMock(UserSettingService::class); + $userSettingService->method('getUserSetting')->with(UserMode::IDENTIFIER)->willReturn($userSetting); + + self::assertEquals( + $expectedResult, + IsUserModeEnabled::fromUserSettings($userSettingService)->isSatisfiedBy($value) + ); + } + + /** + * @return iterable + */ + public function dataProviderForIsSatisfiedBy(): iterable + { + yield [UserMode::SMART, UserMode::SMART, true]; + yield [UserMode::SMART, UserMode::EXPERT, false]; + yield [UserMode::EXPERT, UserMode::SMART, false]; + yield [UserMode::EXPERT, UserMode::EXPERT, true]; + } +} diff --git a/tests/lib/Tab/LocationView/AbstractTabVisibilityTestCase.php b/tests/lib/Tab/LocationView/AbstractTabVisibilityTestCase.php new file mode 100644 index 0000000000..879f730051 --- /dev/null +++ b/tests/lib/Tab/LocationView/AbstractTabVisibilityTestCase.php @@ -0,0 +1,48 @@ + $parameters + */ + final public function testTabVisibilityInGivenUserMode(string $userMode, array $parameters, bool $expectedResult): void + { + $userSetting = $this->createMock(UserSetting::class); + $userSetting->method('__get')->with('value')->willReturn($userMode); + + $userSettingService = $this->createMock(UserSettingService::class); + $userSettingService->method('getUserSetting')->with(UserMode::IDENTIFIER)->willReturn($userSetting); + + $actualResult = $this->createTabForVisibilityInGivenUserModeTest($userSettingService)->evaluate($parameters); + + self::assertEquals($expectedResult, $actualResult); + } + + /** + * @return iterable, bool}> + */ + abstract public function dataProviderForTestTabVisibilityInGivenUserMode(): iterable; + + /** + * @return \Ibexa\Contracts\AdminUi\Tab\TabInterface&\Ibexa\Contracts\AdminUi\Tab\ConditionalTabInterface + */ + abstract protected function createTabForVisibilityInGivenUserModeTest( + UserSettingService $userSettingService + ): TabInterface; +} diff --git a/tests/lib/Tab/LocationView/AuthorsTabVisibilityTest.php b/tests/lib/Tab/LocationView/AuthorsTabVisibilityTest.php new file mode 100644 index 0000000000..c40700c65a --- /dev/null +++ b/tests/lib/Tab/LocationView/AuthorsTabVisibilityTest.php @@ -0,0 +1,38 @@ +createMock(Environment::class), + $this->createMock(TranslatorInterface::class), + $this->createMock(UserService::class), + $userSettingService, + $this->createMock(EventDispatcherInterface::class) + ); + } + + public function dataProviderForTestTabVisibilityInGivenUserMode(): iterable + { + yield 'smart mode' => [UserMode::SMART, [], true]; + yield 'expert mode' => [UserMode::EXPERT, [], false]; + } +} diff --git a/tests/lib/Tab/LocationView/DetailsTabVisibilityTest.php b/tests/lib/Tab/LocationView/DetailsTabVisibilityTest.php new file mode 100644 index 0000000000..d5e7cb5260 --- /dev/null +++ b/tests/lib/Tab/LocationView/DetailsTabVisibilityTest.php @@ -0,0 +1,44 @@ +createMock(Environment::class), + $this->createMock(TranslatorInterface::class), + $this->createMock(SectionService::class), + $this->createMock(DatasetFactory::class), + $this->createMock(FormFactoryInterface::class), + $this->createMock(PermissionResolver::class), + $userSettingService, + $this->createMock(EventDispatcherInterface::class) + ); + } + + public function dataProviderForTestTabVisibilityInGivenUserMode(): iterable + { + yield 'smart mode' => [UserMode::SMART, [], false]; + yield 'expert mode' => [UserMode::EXPERT, [], true]; + } +} diff --git a/tests/lib/Tab/LocationView/LocationsTabVisibilityTest.php b/tests/lib/Tab/LocationView/LocationsTabVisibilityTest.php new file mode 100644 index 0000000000..bd4c8cef1b --- /dev/null +++ b/tests/lib/Tab/LocationView/LocationsTabVisibilityTest.php @@ -0,0 +1,51 @@ +createMock(Environment::class), + $this->createMock(TranslatorInterface::class), + $this->createMock(FormFactory::class), + $this->createMock(UrlGeneratorInterface::class), + $this->createMock(PermissionResolver::class), + $this->createMock(EventDispatcherInterface::class), + $this->createMock(SearchService::class), + $this->createMock(RequestStack::class), + new Mapper($this->createMock(ValueFactory::class)), + $this->createMock(ConfigResolverInterface::class), + $userSettingService + ); + } + + public function dataProviderForTestTabVisibilityInGivenUserMode(): iterable + { + yield 'smart mode' => [UserMode::SMART, [], false]; + yield 'expert mode' => [UserMode::EXPERT, [], true]; + } +} diff --git a/tests/lib/Tab/LocationView/VersionsTabVisibilityTest.php b/tests/lib/Tab/LocationView/VersionsTabVisibilityTest.php new file mode 100644 index 0000000000..eb81241ff1 --- /dev/null +++ b/tests/lib/Tab/LocationView/VersionsTabVisibilityTest.php @@ -0,0 +1,67 @@ +exampleContent = $this->createMock(Content::class); + } + + protected function createTabForVisibilityInGivenUserModeTest(UserSettingService $userSettingService): TabInterface + { + $permissionResolver = $this->createMock(PermissionResolver::class); + $permissionResolver->method('canUser')->with('content', 'versionread', $this->exampleContent)->willReturn(true); + + return new VersionsTab( + $this->createMock(Environment::class), + $this->createMock(TranslatorInterface::class), + $this->createMock(DatasetFactory::class), + $this->createMock(FormFactory::class), + $this->createMock(UrlGeneratorInterface::class), + $this->createMock(PermissionResolver::class), + $this->createMock(UserService::class), + $userSettingService, + $this->createMock(EventDispatcherInterface::class), + ); + } + + public function dataProviderForTestTabVisibilityInGivenUserMode(): iterable + { + yield 'smart mode' => [ + UserMode::SMART, + ['content' => $this->exampleContent], + false, + ]; + + yield 'expert mode' => [ + UserMode::EXPERT, + ['content' => $this->exampleContent], + true, + ]; + } +}