Skip to content

Commit 9d860d2

Browse files
authored
Merge pull request #790 from phpDocumentor/feature/external-menu
[FEATURE] Support Menu entries with overriden titles and external urls
2 parents 21fa962 + 5acd7f5 commit 9d860d2

File tree

27 files changed

+456
-124
lines changed

27 files changed

+456
-124
lines changed

packages/guides-restructured-text/src/RestructuredText/Directives/ContentsDirective.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace phpDocumentor\Guides\RestructuredText\Directives;
66

77
use phpDocumentor\Guides\Nodes\Menu\ContentMenuNode;
8+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
89
use phpDocumentor\Guides\Nodes\Node;
910
use phpDocumentor\Guides\ReferenceResolvers\DocumentNameResolverInterface;
1011
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
@@ -38,7 +39,7 @@ public function process(
3839
$blockContext->getDocumentParserContext()->getContext()->getCurrentFileName(),
3940
);
4041

41-
return (new ContentMenuNode([$absoluteUrl]))
42+
return (new ContentMenuNode([new MenuDefinitionLineNode($absoluteUrl)]))
4243
->withOptions($this->optionsToArray($options))
4344
->withCaption($directive->getDataNode());
4445
}

packages/guides-restructured-text/src/RestructuredText/Directives/MenuDirective.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace phpDocumentor\Guides\RestructuredText\Directives;
66

7+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
78
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
89
use phpDocumentor\Guides\Nodes\Node;
910
use phpDocumentor\Guides\RestructuredText\Parser\BlockContext;
@@ -42,13 +43,13 @@ public function process(
4243
$options['titlesonly'] = new DirectiveOption('titlesonly', false);
4344
$options['globExclude'] ??= new DirectiveOption('globExclude', 'index,Index');
4445

45-
$toctreeFiles = $this->toctreeBuilder->buildToctreeFiles(
46+
$toctreeFiles = $this->toctreeBuilder->buildToctreeEntries(
4647
$parserContext,
4748
$blockContext->getDocumentIterator(),
4849
$options,
4950
);
5051
if (count($toctreeFiles) === 0) {
51-
$toctreeFiles[] = '/*';
52+
$toctreeFiles[] = new MenuDefinitionLineNode('/*');
5253
}
5354

5455
return (new NavMenuNode($toctreeFiles))->withOptions($this->optionsToArray($options));

packages/guides-restructured-text/src/RestructuredText/Directives/ToctreeDirective.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function process(
4545
$options = $directive->getOptions();
4646
$options['globExclude'] ??= new DirectiveOption('globExclude', 'index,Index');
4747

48-
$toctreeFiles = $this->toctreeBuilder->buildToctreeFiles(
48+
$toctreeFiles = $this->toctreeBuilder->buildToctreeEntries(
4949
$parserContext,
5050
$blockContext->getDocumentIterator(),
5151
$options,

packages/guides-restructured-text/src/RestructuredText/Toc/ToctreeBuilder.php

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,51 @@
44

55
namespace phpDocumentor\Guides\RestructuredText\Toc;
66

7+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
78
use phpDocumentor\Guides\ParserContext;
9+
use phpDocumentor\Guides\RestructuredText\Parser\EmbeddedUriParser;
810
use phpDocumentor\Guides\RestructuredText\Parser\LinesIterator;
911

1012
use function array_filter;
1113
use function array_map;
1214

1315
class ToctreeBuilder
1416
{
17+
use EmbeddedUriParser;
18+
1519
/**
1620
* @param mixed[] $options
1721
*
18-
* @return string[]
22+
* @return MenuDefinitionLineNode[]
1923
*/
20-
public function buildToctreeFiles(
24+
public function buildToctreeEntries(
2125
ParserContext $parserContext,
2226
LinesIterator $lines,
2327
array $options,
2428
): array {
25-
$toctreeFiles = [];
29+
$toctreeEntries = [];
2630

27-
foreach ($this->parseToctreeFiles($lines) as $file) {
28-
$toctreeFiles[] = $file;
31+
foreach ($this->parseToctreeEntryLines($lines) as $entry) {
32+
$toctreeEntries[] = $entry;
2933
}
3034

31-
return $toctreeFiles;
35+
return $toctreeEntries;
3236
}
3337

34-
/** @return string[] */
35-
private function parseToctreeFiles(LinesIterator $lines): array
38+
/** @return MenuDefinitionLineNode[] */
39+
private function parseToctreeEntryLines(LinesIterator $lines): array
3640
{
37-
return array_filter(
41+
$linesArray = array_filter(
3842
array_map('trim', $lines->toArray()),
3943
static fn (string $file): bool => $file !== '',
4044
);
45+
46+
$result = [];
47+
foreach ($linesArray as $line) {
48+
$parsed = $this->extractEmbeddedUri($line);
49+
$result[] = new MenuDefinitionLineNode($parsed['uri'], $parsed['text']);
50+
}
51+
52+
return $result;
4153
}
4254
}

packages/guides/src/Compiler/NodeTransformers/ContentMenuNodeWithSectionEntryTransformer.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
1010
use phpDocumentor\Guides\Nodes\DocumentTree\SectionEntryNode;
1111
use phpDocumentor\Guides\Nodes\Menu\ContentMenuNode;
12-
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
12+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1313
use phpDocumentor\Guides\Nodes\Menu\TocNode;
1414
use phpDocumentor\Guides\Nodes\Node;
1515

@@ -40,7 +40,7 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
4040
// We do not add the main section as it repeats the document title
4141
foreach ($section->getChildren() as $subSectionEntryNode) {
4242
assert($subSectionEntryNode instanceof SectionEntryNode);
43-
$sectionMenuEntry = new MenuEntryNode(
43+
$sectionMenuEntry = new InternalMenuEntryNode(
4444
$documentEntry->getFile(),
4545
$subSectionEntryNode->getTitle(),
4646
[],
@@ -59,7 +59,7 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
5959
}
6060

6161
private function addSubSections(
62-
MenuEntryNode $sectionMenuEntry,
62+
InternalMenuEntryNode $sectionMenuEntry,
6363
SectionEntryNode $sectionEntryNode,
6464
DocumentEntryNode $documentEntry,
6565
int $currentLevel,
@@ -70,7 +70,7 @@ private function addSubSections(
7070
}
7171

7272
foreach ($sectionEntryNode->getChildren() as $subSectionEntryNode) {
73-
$subSectionMenuEntry = new MenuEntryNode(
73+
$subSectionMenuEntry = new InternalMenuEntryNode(
7474
$documentEntry->getFile(),
7575
$subSectionEntryNode->getTitle(),
7676
[],

packages/guides/src/Compiler/NodeTransformers/MenuNodeAddEntryTransformer.php

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,31 @@
88
use phpDocumentor\Guides\Compiler\NodeTransformer;
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
1010
use phpDocumentor\Guides\Nodes\DocumentTree\SectionEntryNode;
11-
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
11+
use phpDocumentor\Guides\Nodes\Menu\ExternalMenuEntryNode;
12+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
13+
use phpDocumentor\Guides\Nodes\Menu\MenuDefinitionLineNode;
1214
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
1315
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
1416
use phpDocumentor\Guides\Nodes\Menu\TocNode;
1517
use phpDocumentor\Guides\Nodes\Node;
18+
use phpDocumentor\Guides\Nodes\TitleNode;
1619
use Psr\Log\LoggerInterface;
1720

1821
use function array_pop;
1922
use function array_unique;
2023
use function assert;
2124
use function explode;
25+
use function filter_var;
2226
use function implode;
2327
use function in_array;
28+
use function is_string;
2429
use function preg_match;
2530
use function sprintf;
2631
use function str_replace;
2732
use function str_starts_with;
2833

34+
use const FILTER_VALIDATE_URL;
35+
2936
/** @implements NodeTransformer<MenuNode> */
3037
class MenuNodeAddEntryTransformer implements NodeTransformer
3138
{
@@ -45,7 +52,7 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
4552
return $node;
4653
}
4754

48-
$files = $node->getFiles();
55+
$parsedMenuEntryNodes = $node->getParsedMenuEntryNodes();
4956
$glob = $node->hasOption('glob');
5057
$globExclude = explode(',', $node->getOption('globExclude') . '');
5158

@@ -54,11 +61,20 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
5461
$documentEntriesInTree = [];
5562
$menuEntries = [];
5663

57-
foreach ($files as $file) {
64+
foreach ($parsedMenuEntryNodes as $parsedMenuEntryNode) {
65+
if (filter_var($parsedMenuEntryNode->getReference(), FILTER_VALIDATE_URL) !== false) {
66+
$menuEntry = new ExternalMenuEntryNode(
67+
$parsedMenuEntryNode->getReference(),
68+
TitleNode::fromString($parsedMenuEntryNode->getTitle() ?? $parsedMenuEntryNode->getReference()),
69+
);
70+
$menuEntries[] = $menuEntry;
71+
continue;
72+
}
73+
5874
foreach ($documentEntries as $documentEntry) {
5975
if (
60-
!self::isEqualAbsolutePath($documentEntry->getFile(), $file, $currentPath, $glob, $globExclude)
61-
&& !self::isEqualRelativePath($documentEntry->getFile(), $file, $currentPath, $glob, $globExclude)
76+
!self::isEqualAbsolutePath($documentEntry->getFile(), $parsedMenuEntryNode, $currentPath, $glob, $globExclude)
77+
&& !self::isEqualRelativePath($documentEntry->getFile(), $parsedMenuEntryNode, $currentPath, $glob, $globExclude)
6278
) {
6379
continue;
6480
}
@@ -69,9 +85,9 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
6985
}
7086

7187
$documentEntriesInTree[] = $documentEntry;
72-
$menuEntry = new MenuEntryNode(
88+
$menuEntry = new InternalMenuEntryNode(
7389
$documentEntry->getFile(),
74-
$documentEntry->getTitle(),
90+
is_string($parsedMenuEntryNode->getTitle()) ? TitleNode::fromString($parsedMenuEntryNode->getTitle()) : $documentEntry->getTitle(),
7591
[],
7692
false,
7793
1,
@@ -116,10 +132,10 @@ private function isCurrent(DocumentEntryNode $menuEntry, string $currentPath): b
116132
return $menuEntry->getFile() === $currentPath;
117133
}
118134

119-
private function addSubSections(MenuEntryNode $sectionMenuEntry, SectionEntryNode $sectionEntryNode, DocumentEntryNode $documentEntry, int $currentLevel): void
135+
private function addSubSections(InternalMenuEntryNode $sectionMenuEntry, SectionEntryNode $sectionEntryNode, DocumentEntryNode $documentEntry, int $currentLevel): void
120136
{
121137
foreach ($sectionEntryNode->getChildren() as $subSectionEntryNode) {
122-
$subSectionMenuEntry = new MenuEntryNode($documentEntry->getFile(), $subSectionEntryNode->getTitle(), [], false, $currentLevel + 1, $subSectionEntryNode->getId());
138+
$subSectionMenuEntry = new InternalMenuEntryNode($documentEntry->getFile(), $subSectionEntryNode->getTitle(), [], false, $currentLevel + 1, $subSectionEntryNode->getId());
123139
$sectionMenuEntry->addSection($subSectionMenuEntry);
124140
}
125141
}
@@ -136,8 +152,9 @@ public function getPriority(): int
136152
}
137153

138154
/** @param String[] $globExclude */
139-
private static function isEqualAbsolutePath(string $actualFile, string $expectedFile, string $currentFile, bool $glob, array $globExclude): bool
155+
private static function isEqualAbsolutePath(string $actualFile, MenuDefinitionLineNode $parsedMenuEntryNode, string $currentFile, bool $glob, array $globExclude): bool
140156
{
157+
$expectedFile = $parsedMenuEntryNode->getReference();
141158
if (!self::isAbsoluteFile($expectedFile)) {
142159
return false;
143160
}
@@ -150,8 +167,9 @@ private static function isEqualAbsolutePath(string $actualFile, string $expected
150167
}
151168

152169
/** @param String[] $globExclude */
153-
private static function isEqualRelativePath(string $actualFile, string $expectedFile, string $currentFile, bool $glob, array $globExclude): bool
170+
private static function isEqualRelativePath(string $actualFile, MenuDefinitionLineNode $parsedMenuEntryNode, string $currentFile, bool $glob, array $globExclude): bool
154171
{
172+
$expectedFile = $parsedMenuEntryNode->getReference();
155173
if (self::isAbsoluteFile($expectedFile)) {
156174
return false;
157175
}
@@ -225,14 +243,14 @@ private function attachDocumentEntriesToParents(
225243
}
226244
}
227245

228-
private function addSubSectionsToMenuEntries(DocumentEntryNode $documentEntry, MenuEntryNode $menuEntry): void
246+
private function addSubSectionsToMenuEntries(DocumentEntryNode $documentEntry, InternalMenuEntryNode $menuEntry): void
229247
{
230248
foreach ($documentEntry->getSections() as $section) {
231249
// We do not add the main section as it repeats the document title
232250
foreach ($section->getChildren() as $subSectionEntryNode) {
233251
assert($subSectionEntryNode instanceof SectionEntryNode);
234252
$currentLevel = $menuEntry->getLevel() + 1;
235-
$sectionMenuEntry = new MenuEntryNode(
253+
$sectionMenuEntry = new InternalMenuEntryNode(
236254
$documentEntry->getFile(),
237255
$subSectionEntryNode->getTitle(),
238256
[],

packages/guides/src/Compiler/NodeTransformers/MenuNodeAddSubDocumentsTransformer.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use phpDocumentor\Guides\Compiler\CompilerContext;
88
use phpDocumentor\Guides\Compiler\NodeTransformer;
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
10-
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
10+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1111
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
1212
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
1313
use phpDocumentor\Guides\Nodes\Menu\TocNode;
@@ -34,6 +34,10 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
3434

3535

3636
foreach ($node->getMenuEntries() as $menuEntry) {
37+
if (!$menuEntry instanceof InternalMenuEntryNode) {
38+
continue;
39+
}
40+
3741
$documentEntryOfMenuEntry = $compilerContext->getProjectNode()->getDocumentEntry($menuEntry->getUrl());
3842
$this->addSubEntries($compilerContext, $menuEntry, $documentEntryOfMenuEntry, $menuEntry->getLevel() + 1, $maxDepth);
3943
}
@@ -43,7 +47,7 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
4347

4448
private function addSubEntries(
4549
CompilerContext $compilerContext,
46-
MenuEntryNode $sectionMenuEntry,
50+
InternalMenuEntryNode $sectionMenuEntry,
4751
DocumentEntryNode $documentEntry,
4852
int $currentLevel,
4953
int $maxDepth,
@@ -53,7 +57,7 @@ private function addSubEntries(
5357
}
5458

5559
foreach ($documentEntry->getChildren() as $subDocumentEntryNode) {
56-
$subMenuEntry = new MenuEntryNode(
60+
$subMenuEntry = new InternalMenuEntryNode(
5761
$subDocumentEntryNode->getFile(),
5862
$subDocumentEntryNode->getTitle(),
5963
[],

packages/guides/src/Compiler/Passes/GlobalMenuPass.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use phpDocumentor\Guides\Compiler\CompilerPass;
99
use phpDocumentor\Guides\Nodes\DocumentNode;
1010
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
11+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1112
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
1213
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
1314
use phpDocumentor\Guides\Nodes\Menu\TocNode;
@@ -71,7 +72,7 @@ public function run(array $documents, CompilerContext $compilerContext): array
7172

7273
private function getNavMenuNodefromTocNode(CompilerContext $compilerContext, TocNode $tocNode, string|null $menuType = null): NavMenuNode
7374
{
74-
$node = new NavMenuNode($tocNode->getFiles());
75+
$node = new NavMenuNode($tocNode->getParsedMenuEntryNodes());
7576
$self = $this;
7677
$menuEntries = array_map(static function (MenuEntryNode $tocEntry) use ($compilerContext, $self) {
7778
return $self->getMenuEntryWithChildren($compilerContext, $tocEntry);
@@ -93,10 +94,14 @@ private function getNavMenuNodefromTocNode(CompilerContext $compilerContext, Toc
9394

9495
private function getMenuEntryWithChildren(CompilerContext $compilerContext, MenuEntryNode $menuEntry): MenuEntryNode
9596
{
97+
if (!$menuEntry instanceof InternalMenuEntryNode) {
98+
return $menuEntry;
99+
}
100+
101+
$newMenuEntry = new InternalMenuEntryNode($menuEntry->getUrl(), $menuEntry->getValue(), [], false, 2);
96102
$maxdepth = $this->settingsManager->getProjectSettings()->getMaxMenuDepth();
97103
$maxdepth = $maxdepth < 1 ? PHP_INT_MAX : $maxdepth + 1;
98104
$documentEntryOfMenuEntry = $compilerContext->getProjectNode()->getDocumentEntry($menuEntry->getUrl());
99-
$newMenuEntry = new MenuEntryNode($menuEntry->getUrl(), $menuEntry->getValue(), [], false, 2);
100105
$this->addSubEntries($compilerContext, $newMenuEntry, $documentEntryOfMenuEntry, 3, $maxdepth);
101106

102107
return $newMenuEntry;
@@ -113,8 +118,12 @@ private function addSubEntries(
113118
return;
114119
}
115120

121+
if (!$sectionMenuEntry instanceof InternalMenuEntryNode) {
122+
return;
123+
}
124+
116125
foreach ($documentEntry->getChildren() as $subDocumentEntryNode) {
117-
$subMenuEntry = new MenuEntryNode(
126+
$subMenuEntry = new InternalMenuEntryNode(
118127
$subDocumentEntryNode->getFile(),
119128
$subDocumentEntryNode->getTitle(),
120129
[],

packages/guides/src/NodeRenderers/Html/BreadCrumbNodeRenderer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use phpDocumentor\Guides\NodeRenderers\NodeRenderer;
88
use phpDocumentor\Guides\Nodes\BreadCrumbNode;
99
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
10+
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
1011
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
1112
use phpDocumentor\Guides\Nodes\Node;
1213
use phpDocumentor\Guides\RenderContext;
@@ -97,7 +98,7 @@ private function buildBreadcrumb(
9798
int $level,
9899
bool $isCurrent,
99100
): array {
100-
$entry = new MenuEntryNode(
101+
$entry = new InternalMenuEntryNode(
101102
$documentEntry->getFile(),
102103
$documentEntry->getTitle(),
103104
[],

packages/guides/src/NodeRenderers/Html/MenuEntryRenderer.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ public function supports(string $nodeFqcn): bool
2929

3030
public function render(Node $node, RenderContext $renderContext): string
3131
{
32+
$url = $this->urlGenerator->generateCanonicalOutputUrl($renderContext, $node->getUrl(), $node->getValue()->getId());
33+
3234
return $this->renderer->renderTemplate(
3335
$renderContext,
3436
'body/menu/menu-item.html.twig',
3537
[
36-
'url' => $this->urlGenerator->generateCanonicalOutputUrl($renderContext, $node->getUrl(), $node->getValue()->getId()),
38+
'url' => $url,
3739
'node' => $node,
3840
],
3941
);

0 commit comments

Comments
 (0)