Skip to content

Commit b7ff3ad

Browse files
authored
Merge pull request #52 from Sebobo/feature/neos9
!!! FEATURE: Neos 9 compatibility
2 parents 517875c + 9f0664d commit b7ff3ad

File tree

9 files changed

+341
-232
lines changed

9 files changed

+341
-232
lines changed

Classes/Controller/PreferencesController.php

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@
1313
*/
1414

1515
use Doctrine\ORM\EntityManagerInterface;
16-
use Neos\ContentRepository\Domain\Model\NodeInterface;
16+
use Neos\ContentRepository\Core\Feature\Security\Exception\AccessDenied;
17+
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
18+
use Neos\ContentRepository\Core\SharedModel\Node\NodeAddress;
19+
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
1720
use Neos\Flow\Annotations as Flow;
1821
use Neos\Flow\Mvc\Controller\ActionController;
1922
use Neos\Flow\Mvc\View\JsonView;
20-
use Neos\Neos\Controller\CreateContentContextTrait;
2123
use Neos\Neos\Domain\Model\UserPreferences;
24+
use Neos\Neos\Domain\NodeLabel\NodeLabelGeneratorInterface;
2225
use Neos\Neos\Service\LinkingService;
2326
use Neos\Neos\Service\UserService;
24-
use Neos\Neos\Ui\ContentRepository\Service\NodeService;
2527

2628
class PreferencesController extends ActionController
2729
{
28-
use CreateContentContextTrait;
29-
3030
protected const FAVOURITES_PREFERENCE = 'commandBar.favourites';
3131
protected const RECENT_COMMANDS_PREFERENCE = 'commandBar.recentCommands';
3232
protected const RECENT_DOCUMENTS_PREFERENCE = 'commandBar.recentDocuments';
@@ -36,8 +36,9 @@ class PreferencesController extends ActionController
3636
public function __construct(
3737
protected UserService $userService,
3838
protected EntityManagerInterface $entityManager,
39-
protected NodeService $nodeService,
4039
protected LinkingService $linkingService,
40+
protected ContentRepositoryRegistry $contentRepositoryRegistry,
41+
protected NodeLabelGeneratorInterface $nodeLabelGenerator,
4142
) {
4243
}
4344

@@ -47,7 +48,9 @@ public function getPreferencesAction(): void
4748
$this->view->assign('value', [
4849
'favouriteCommands' => $preferences->get(self::FAVOURITES_PREFERENCE) ?? [],
4950
'recentCommands' => $preferences->get(self::RECENT_COMMANDS_PREFERENCE) ?? [],
50-
'recentDocuments' => $this->mapContextPathsToNodes($preferences->get(self::RECENT_DOCUMENTS_PREFERENCE) ?? []),
51+
'recentDocuments' => $this->mapContextPathsToNodes(
52+
$preferences->get(self::RECENT_DOCUMENTS_PREFERENCE) ?? []
53+
),
5154
'showBranding' => $this->settings['features']['showBranding'],
5255
]);
5356
}
@@ -94,23 +97,28 @@ public function addRecentCommandAction(string $commandId): void
9497
/**
9598
* Updates the list of recently used documents in the user preferences
9699
*
97-
* @Flow\SkipCsrfProtection
98-
* @param string $nodeContextPath a context path to add to the recently visited documents
100+
* @param string $nodeContextPath a node to add to the recently visited documents
99101
*/
102+
#[Flow\SkipCsrfProtection]
100103
public function addRecentDocumentAction(string $nodeContextPath): void
101104
{
102105
$preferences = $this->getUserPreferences();
103106

107+
/** @var string[]|null $recentDocuments */
104108
$recentDocuments = $preferences->get(self::RECENT_DOCUMENTS_PREFERENCE);
105109
if ($recentDocuments === null) {
106110
$recentDocuments = [];
107111
}
108112

109113
// Remove the command from the list if it is already in there (to move it to the top)
110-
$recentDocuments = array_filter($recentDocuments,
111-
static fn($existingContextPath) => $existingContextPath !== $nodeContextPath);
114+
$recentDocuments = array_filter(
115+
$recentDocuments,
116+
static fn($existingContextPath) => $existingContextPath !== $nodeContextPath
117+
);
118+
112119
// Add the path to the top of the list
113120
array_unshift($recentDocuments, $nodeContextPath);
121+
114122
// Limit the list to 5 items
115123
$recentDocuments = array_slice($recentDocuments, 0, 5);
116124

@@ -130,32 +138,52 @@ protected function getUserPreferences(): UserPreferences
130138
}
131139

132140
/**
133-
* @var string[] $contextPaths
141+
* @param string[] $nodeContextPaths
134142
*/
135-
protected function mapContextPathsToNodes(array $contextPaths): array
136-
{
137-
return array_reduce($contextPaths, function (array $carry, string $contextPath) {
138-
$node = $this->nodeService->getNodeFromContextPath($contextPath);
139-
if ($node instanceof NodeInterface) {
143+
protected function mapContextPathsToNodes(
144+
array $nodeContextPaths,
145+
): array {
146+
return array_filter(
147+
array_map(function (string $nodeContextPath) {
148+
$nodeAddress = NodeAddress::fromJsonString($nodeContextPath);
149+
150+
$contentRepository = $this->contentRepositoryRegistry->get($nodeAddress->contentRepositoryId);
151+
try {
152+
$subgraph = $contentRepository->getContentSubgraph(
153+
$nodeAddress->workspaceName,
154+
$nodeAddress->dimensionSpacePoint
155+
);
156+
} catch (AccessDenied) {
157+
// If the user does not have access to the subgraph, we skip this node
158+
return null;
159+
}
160+
161+
$node = $subgraph->findNodeById($nodeAddress->aggregateId);
162+
if (!$node) {
163+
return null;
164+
}
165+
140166
$uri = $this->getNodeUri($node);
141-
if ($uri) {
142-
$carry[]= [
143-
'name' => $node->getLabel(),
144-
'icon' => $node->getNodeType()->getConfiguration('ui.icon') ?? 'question',
145-
'uri' => $this->getNodeUri($node),
146-
'contextPath' => $contextPath,
147-
];
167+
if (!$uri) {
168+
return null;
148169
}
149-
}
150-
return $carry;
151-
}, []);
170+
171+
$nodeType = $contentRepository->getNodeTypeManager()->getNodeType($node->nodeTypeName);
172+
return [
173+
'name' => $this->nodeLabelGenerator->getLabel($node),
174+
'icon' => $nodeType?->getConfiguration('ui.icon') ?? 'question',
175+
'uri' => $this->getNodeUri($node),
176+
'contextPath' => $nodeContextPath,
177+
];
178+
}, $nodeContextPaths)
179+
);
152180
}
153181

154-
protected function getNodeUri(NodeInterface $node): string
182+
protected function getNodeUri(Node $node): string
155183
{
156184
try {
157185
return $this->linkingService->createNodeUri($this->controllerContext, $node, null, 'html', true);
158-
} catch (\Exception $e) {
186+
} catch (\Exception) {
159187
return '';
160188
}
161189
}

Classes/Domain/Dto/CommandDto.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515
use Neos\Flow\Annotations as Flow;
1616

1717
#[Flow\Proxy(false)]
18-
class CommandDto implements \JsonSerializable
18+
readonly class CommandDto implements \JsonSerializable
1919
{
2020

2121
public function __construct(
22-
public readonly string $id,
23-
public readonly string $name,
24-
public readonly string $description,
25-
public readonly string $action,
26-
public readonly string $icon,
27-
public readonly string $category = '',
22+
public string $id,
23+
public string $name,
24+
public string $description,
25+
public string $action,
26+
public string $icon,
27+
public string $category = '',
2828
) {
2929
}
3030

Classes/Helper/TranslationHelper.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,22 @@
1616
use Neos\Flow\I18n\EelHelper\TranslationParameterToken;
1717

1818
#[Flow\Proxy(false)]
19-
class TranslationHelper
19+
final class TranslationHelper
2020
{
21-
2221
public static function translateByShortHandString(string $shortHandString): string
2322
{
2423
$shortHandStringParts = explode(':', $shortHandString);
2524
if (count($shortHandStringParts) === 3) {
2625
[$package, $source, $id] = $shortHandStringParts;
27-
return (new TranslationParameterToken($id))
28-
->package($package)
29-
->source(str_replace('.', '/', $source))
30-
->translate();
26+
try {
27+
return (new TranslationParameterToken($id))
28+
->package($package)
29+
->source(str_replace('.', '/', $source))
30+
->translate();
31+
} catch (\Exception) {
32+
return $shortHandString; // Fallback to original string if translation fails
33+
}
3134
}
32-
3335
return $shortHandString;
3436
}
3537
}

Classes/Service/DataSource/CommandsDataSource.php

Lines changed: 74 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
* source code.
1313
*/
1414

15-
use Neos\ContentRepository\Domain\Model\NodeInterface;
15+
use Neos\ContentRepository\Core\Projection\ContentGraph\Node;
16+
use Neos\Flow\Http\Exception;
1617
use Neos\Flow\I18n\Translator;
18+
use Neos\Flow\Mvc\Routing\Exception\MissingActionNameException;
1719
use Neos\Flow\Mvc\Routing\UriBuilder;
20+
use Neos\Flow\Persistence\Exception\IllegalObjectTypeException;
1821
use Neos\Neos\Controller\Backend\MenuHelper;
22+
use Neos\Neos\Domain\Model\SiteNodeName;
1923
use Neos\Neos\Service\BackendRedirectionService;
2024
use Neos\Neos\Service\DataSource\AbstractDataSource;
2125
use Shel\Neos\CommandBar\Domain\Dto\CommandDto;
@@ -34,57 +38,37 @@ public function __construct(
3438
) {
3539
}
3640

37-
public function getData(NodeInterface $node = null, array $arguments = []): array
38-
{
41+
/**
42+
* @throws Exception
43+
* @throws MissingActionNameException
44+
* @throws IllegalObjectTypeException
45+
*/
46+
public function getData(
47+
Node $node = null,
48+
array $arguments = []
49+
): array {
3950
$this->uriBuilder->setRequest($this->controllerContext->getRequest()->getMainRequest());
4051

41-
$sitesForMenu = array_reduce($this->menuHelper->buildSiteList($this->controllerContext),
52+
$sitesForMenu = array_reduce(
53+
$this->menuHelper->buildSiteList($this->controllerContext),
54+
/** @param array{name: string, nodeName: SiteNodeName, uri: string, active: bool} $site */
4255
static function (array $carry, array $site) {
4356
if (!$site['uri']) {
4457
return $carry;
4558
}
46-
$carry[$site['nodeName']] = new CommandDto(
59+
$carry[$site['nodeName']->value] = new CommandDto(
4760
$site['name'],
4861
$site['name'],
4962
'',
5063
$site['uri'],
5164
'globe'
5265
);
5366
return $carry;
54-
}, []);
67+
},
68+
[]
69+
);
5570

56-
$modulesForMenu = array_reduce($this->menuHelper->buildModuleList($this->controllerContext),
57-
function (array $carry, array $module) {
58-
// Skip modules without submodules
59-
if (!$module['submodules']) {
60-
return $carry;
61-
}
62-
$carry[$module['group']] = [
63-
'name' => TranslationHelper::translateByShortHandString($module['label']),
64-
'description' => TranslationHelper::translateByShortHandString($module['description']),
65-
'icon' => $module['icon'],
66-
'subCommands' => array_reduce($module['submodules'],
67-
function (array $carry, array $submodule) {
68-
if ($submodule['hideInMenu']) {
69-
return $carry;
70-
}
71-
$carry[$submodule['module']] = new CommandDto(
72-
$submodule['modulePath'],
73-
TranslationHelper::translateByShortHandString($submodule['label']),
74-
TranslationHelper::translateByShortHandString($submodule['description']),
75-
$this->uriBuilder->uriFor(
76-
'index',
77-
['module' => $submodule['modulePath']],
78-
'Backend\Module',
79-
'Neos.Neos'
80-
),
81-
$submodule['icon'],
82-
);
83-
return $carry;
84-
}, []),
85-
];
86-
return $carry;
87-
}, []);
71+
$modulesForMenu = $this->getModuleCommands();
8872

8973
$commands = [
9074
'preferred-start-module' => new CommandDto(
@@ -94,6 +78,7 @@ function (array $carry, array $submodule) {
9478
$this->backendRedirectionService->getAfterLoginRedirectionUri($this->controllerContext),
9579
'home'
9680
),
81+
// TODO: Introduce group DTO
9782
'modules' => [
9883
'name' => $this->translate('CommandDataSource.category.modules'),
9984
'description' => $this->translate('CommandDataSource.category.modules.description'),
@@ -104,6 +89,7 @@ function (array $carry, array $submodule) {
10489

10590
// Only show site switch command if there is more than one site
10691
if (count($sitesForMenu) > 1) {
92+
// TODO: Introduce group DTO
10793
$commands['sites'] = [
10894
'name' => $this->translate('CommandDataSource.category.sites'),
10995
'description' => $this->translate('CommandDataSource.category.sites.description'),
@@ -115,8 +101,56 @@ function (array $carry, array $submodule) {
115101
return $commands;
116102
}
117103

118-
protected function translate($id): string
104+
protected function translate(string $id): string
105+
{
106+
try {
107+
return $this->translator->translateById($id, [], null, null, 'Main', 'Shel.Neos.CommandBar') ?? $id;
108+
} catch (\Exception) {
109+
return $id;
110+
}
111+
}
112+
113+
protected function getModuleCommands(): mixed
119114
{
120-
return $this->translator->translateById($id, [], null, null, 'Main', 'Shel.Neos.CommandBar') ?? $id;
115+
return array_reduce(
116+
$this->menuHelper->buildModuleList($this->controllerContext),
117+
function (array $carry, array $module) {
118+
// Skip modules without submodules
119+
if (!$module['submodules']) {
120+
return $carry;
121+
}
122+
123+
// TODO: Introduce group DTO
124+
$carry[$module['group']] = [
125+
'name' => TranslationHelper::translateByShortHandString($module['label']),
126+
'description' => TranslationHelper::translateByShortHandString($module['description']),
127+
'icon' => $module['icon'],
128+
'subCommands' => array_reduce(
129+
$module['submodules'],
130+
function (array $carry, array $submodule) {
131+
if ($submodule['hideInMenu']) {
132+
return $carry;
133+
}
134+
$carry[$submodule['module']] = new CommandDto(
135+
$submodule['modulePath'],
136+
TranslationHelper::translateByShortHandString($submodule['label']),
137+
TranslationHelper::translateByShortHandString($submodule['description']),
138+
$this->uriBuilder->uriFor(
139+
'index',
140+
['module' => $submodule['modulePath']],
141+
'Backend\Module',
142+
'Neos.Neos'
143+
),
144+
$submodule['icon'],
145+
);
146+
return $carry;
147+
},
148+
[]
149+
),
150+
];
151+
return $carry;
152+
},
153+
[]
154+
);
121155
}
122156
}

0 commit comments

Comments
 (0)