Skip to content

Commit 9a7e59a

Browse files
authored
Merge pull request #923 from phpDocumentor/backport/1.x/pr-900
[1.x] Merge pull request #900 from phpDocumentor/task/menu-external
2 parents 4174c75 + caf4b09 commit 9a7e59a

File tree

14 files changed

+412
-65
lines changed

14 files changed

+412
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Compiler\NodeTransformers\MenuNodeTransformers;
15+
16+
use phpDocumentor\Guides\Compiler\CompilerContext;
17+
use phpDocumentor\Guides\Nodes\DocumentTree\ExternalEntryNode;
18+
use phpDocumentor\Guides\Nodes\Menu\ExternalMenuEntryNode;
19+
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
20+
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
21+
use phpDocumentor\Guides\Nodes\Menu\TocNode;
22+
use phpDocumentor\Guides\Nodes\Node;
23+
use phpDocumentor\Guides\Nodes\TitleNode;
24+
use Psr\Log\LoggerInterface;
25+
26+
use function assert;
27+
28+
final class ExternalMenuEntryNodeTransformer extends AbstractMenuEntryNodeTransformer
29+
{
30+
use MenuEntryManagement;
31+
use SubSectionHierarchyHandler;
32+
33+
public function __construct(
34+
LoggerInterface $logger,
35+
) {
36+
parent::__construct($logger);
37+
}
38+
39+
public function supports(Node $node): bool
40+
{
41+
return $node instanceof ExternalMenuEntryNode;
42+
}
43+
44+
/** @return list<MenuEntryNode> */
45+
protected function handleMenuEntry(MenuNode $currentMenu, MenuEntryNode $entryNode, CompilerContext $compilerContext): array
46+
{
47+
assert($entryNode instanceof ExternalMenuEntryNode);
48+
49+
$newEntryNode = new ExternalEntryNode(
50+
$entryNode->getUrl(),
51+
($entryNode->getValue() ?? TitleNode::emptyNode())->toString(),
52+
);
53+
54+
if ($currentMenu instanceof TocNode) {
55+
$this->attachDocumentEntriesToParents([$newEntryNode], $compilerContext, '');
56+
}
57+
58+
return [$entryNode];
59+
}
60+
61+
public function getPriority(): int
62+
{
63+
// After DocumentEntryTransformer
64+
return 4500;
65+
}
66+
}

packages/guides/src/Compiler/NodeTransformers/MenuNodeTransformers/MenuEntryManagement.php

+23-20
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,43 @@
1515

1616
use phpDocumentor\Guides\Compiler\CompilerContext;
1717
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
18+
use phpDocumentor\Guides\Nodes\DocumentTree\ExternalEntryNode;
1819

1920
use function sprintf;
2021
use function str_starts_with;
2122

2223
trait MenuEntryManagement
2324
{
24-
/** @param DocumentEntryNode[] $documentEntriesInTree */
25+
/** @param array<DocumentEntryNode|ExternalEntryNode> $entryNodes */
2526
private function attachDocumentEntriesToParents(
26-
array $documentEntriesInTree,
27+
array $entryNodes,
2728
CompilerContext $compilerContext,
2829
string $currentPath,
2930
): void {
30-
foreach ($documentEntriesInTree as $documentEntryInToc) {
31-
if ($documentEntryInToc->isRoot() || $currentPath === $documentEntryInToc->getFile()) {
32-
// The root page may not be attached to any other
33-
continue;
34-
}
31+
foreach ($entryNodes as $entryNode) {
32+
if ($entryNode instanceof DocumentEntryNode) {
33+
if (($entryNode->isRoot() || $currentPath === $entryNode->getFile())) {
34+
// The root page may not be attached to any other
35+
continue;
36+
}
3537

36-
if ($documentEntryInToc->getParent() !== null && $documentEntryInToc->getParent() !== $compilerContext->getDocumentNode()->getDocumentEntry()) {
37-
$this->logger->warning(sprintf(
38-
'Document %s has been added to parents %s and %s. The `toctree` directive changes the '
39-
. 'position of documents in the document tree. Use the `menu` directive to only display a menu without changing the document tree.',
40-
$documentEntryInToc->getFile(),
41-
$documentEntryInToc->getParent()->getFile(),
42-
$compilerContext->getDocumentNode()->getDocumentEntry()->getFile(),
43-
), $compilerContext->getLoggerInformation());
44-
}
38+
if ($entryNode->getParent() !== null && $entryNode->getParent() !== $compilerContext->getDocumentNode()->getDocumentEntry()) {
39+
$this->logger->warning(sprintf(
40+
'Document %s has been added to parents %s and %s. The `toctree` directive changes the '
41+
. 'position of documents in the document tree. Use the `menu` directive to only display a menu without changing the document tree.',
42+
$entryNode->getFile(),
43+
$entryNode->getParent()->getFile(),
44+
$compilerContext->getDocumentNode()->getDocumentEntry()->getFile(),
45+
), $compilerContext->getLoggerInformation());
46+
}
4547

46-
if ($documentEntryInToc->getParent() !== null) {
47-
continue;
48+
if ($entryNode->getParent() !== null) {
49+
continue;
50+
}
4851
}
4952

50-
$documentEntryInToc->setParent($compilerContext->getDocumentNode()->getDocumentEntry());
51-
$compilerContext->getDocumentNode()->getDocumentEntry()->addChild($documentEntryInToc);
53+
$entryNode->setParent($compilerContext->getDocumentNode()->getDocumentEntry());
54+
$compilerContext->getDocumentNode()->getDocumentEntry()->addChild($entryNode);
5255
}
5356
}
5457

packages/guides/src/Compiler/NodeTransformers/MenuNodeTransformers/SubInternalMenuEntryNodeTransformer.php

+31-14
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
use phpDocumentor\Guides\Compiler\CompilerContext;
1717
use phpDocumentor\Guides\Exception\DocumentEntryNotFound;
1818
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
19+
use phpDocumentor\Guides\Nodes\DocumentTree\ExternalEntryNode;
20+
use phpDocumentor\Guides\Nodes\Menu\ExternalMenuEntryNode;
1921
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
2022
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
2123
use phpDocumentor\Guides\Nodes\Menu\MenuNode;
2224
use phpDocumentor\Guides\Nodes\Node;
25+
use phpDocumentor\Guides\Nodes\TitleNode;
2326

2427
use function assert;
2528
use function sprintf;
@@ -74,24 +77,38 @@ private function addSubEntries(
7477
return;
7578
}
7679

77-
foreach ($documentEntry->getChildren() as $subDocumentEntryNode) {
78-
$subMenuEntry = new InternalMenuEntryNode(
79-
$subDocumentEntryNode->getFile(),
80-
$subDocumentEntryNode->getTitle(),
81-
[],
82-
false,
83-
$currentLevel,
84-
'',
85-
self::isInRootline($subDocumentEntryNode, $compilerContext->getDocumentNode()->getDocumentEntry()),
86-
self::isCurrent($subDocumentEntryNode, $compilerContext->getDocumentNode()->getFilePath()),
87-
);
80+
foreach ($documentEntry->getMenuEntries() as $subEntryNode) {
81+
if ($subEntryNode instanceof DocumentEntryNode) {
82+
$subMenuEntry = new InternalMenuEntryNode(
83+
$subEntryNode->getFile(),
84+
$subEntryNode->getTitle(),
85+
[],
86+
false,
87+
$currentLevel,
88+
'',
89+
self::isInRootline($subEntryNode, $compilerContext->getDocumentNode()->getDocumentEntry()),
90+
self::isCurrent($subEntryNode, $compilerContext->getDocumentNode()->getFilePath()),
91+
);
92+
93+
if (!$currentMenu->hasOption('titlesonly') && $maxDepth - $currentLevel + 1 > 1) {
94+
$this->addSubSectionsToMenuEntries($subEntryNode, $subMenuEntry, $maxDepth - $currentLevel + 2);
95+
}
96+
97+
$sectionMenuEntry->addMenuEntry($subMenuEntry);
98+
$this->addSubEntries($currentMenu, $compilerContext, $subMenuEntry, $subEntryNode, $currentLevel + 1, $maxDepth);
99+
continue;
100+
}
88101

89-
if (!$currentMenu->hasOption('titlesonly') && $maxDepth - $currentLevel + 1 > 1) {
90-
$this->addSubSectionsToMenuEntries($subDocumentEntryNode, $subMenuEntry, $maxDepth - $currentLevel + 2);
102+
if (!($subEntryNode instanceof ExternalEntryNode)) {
103+
continue;
91104
}
92105

106+
$subMenuEntry = new ExternalMenuEntryNode(
107+
$subEntryNode->getValue(),
108+
TitleNode::fromString($subEntryNode->getTitle()),
109+
$currentLevel,
110+
);
93111
$sectionMenuEntry->addMenuEntry($subMenuEntry);
94-
$this->addSubEntries($currentMenu, $compilerContext, $subMenuEntry, $subDocumentEntryNode, $currentLevel + 1, $maxDepth);
95112
}
96113
}
97114
}

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

+44-11
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
use phpDocumentor\Guides\Compiler\CompilerPass;
1818
use phpDocumentor\Guides\Nodes\DocumentNode;
1919
use phpDocumentor\Guides\Nodes\DocumentTree\DocumentEntryNode;
20+
use phpDocumentor\Guides\Nodes\DocumentTree\EntryNode;
21+
use phpDocumentor\Guides\Nodes\DocumentTree\ExternalEntryNode;
22+
use phpDocumentor\Guides\Nodes\Menu\ExternalMenuEntryNode;
2023
use phpDocumentor\Guides\Nodes\Menu\InternalMenuEntryNode;
2124
use phpDocumentor\Guides\Nodes\Menu\MenuEntryNode;
2225
use phpDocumentor\Guides\Nodes\Menu\NavMenuNode;
2326
use phpDocumentor\Guides\Nodes\Menu\TocNode;
27+
use phpDocumentor\Guides\Nodes\TitleNode;
2428
use phpDocumentor\Guides\Settings\SettingsManager;
2529
use Throwable;
2630

@@ -115,10 +119,11 @@ private function getMenuEntryWithChildren(CompilerContext $compilerContext, Menu
115119
return $newMenuEntry;
116120
}
117121

122+
/** @param EntryNode<DocumentEntryNode|ExternalEntryNode>|ExternalEntryNode $entryNode */
118123
private function addSubEntries(
119124
CompilerContext $compilerContext,
120125
MenuEntryNode $sectionMenuEntry,
121-
DocumentEntryNode $documentEntry,
126+
EntryNode $entryNode,
122127
int $currentLevel,
123128
int $maxDepth,
124129
): void {
@@ -130,17 +135,45 @@ private function addSubEntries(
130135
return;
131136
}
132137

133-
foreach ($documentEntry->getChildren() as $subDocumentEntryNode) {
134-
$subMenuEntry = new InternalMenuEntryNode(
135-
$subDocumentEntryNode->getFile(),
136-
$subDocumentEntryNode->getTitle(),
137-
[],
138-
false,
139-
$currentLevel,
140-
'',
141-
);
138+
if (!$entryNode instanceof DocumentEntryNode) {
139+
return;
140+
}
141+
142+
foreach ($entryNode->getMenuEntries() as $subEntryNode) {
143+
$subMenuEntry = match ($subEntryNode::class) {
144+
DocumentEntryNode::class => $this->createInternalMenuEntry($subEntryNode, $currentLevel),
145+
ExternalEntryNode::class => $this->createExternalMenuEntry($subEntryNode, $currentLevel),
146+
};
147+
142148
$sectionMenuEntry->addMenuEntry($subMenuEntry);
143-
$this->addSubEntries($compilerContext, $subMenuEntry, $subDocumentEntryNode, $currentLevel + 1, $maxDepth);
149+
$this->addSubEntries(
150+
$compilerContext,
151+
$subMenuEntry,
152+
$subEntryNode,
153+
$currentLevel + 1,
154+
$maxDepth,
155+
);
144156
}
145157
}
158+
159+
private function createInternalMenuEntry(DocumentEntryNode $subEntryNode, int $currentLevel): InternalMenuEntryNode
160+
{
161+
return new InternalMenuEntryNode(
162+
$subEntryNode->getFile(),
163+
$subEntryNode->getTitle(),
164+
[],
165+
false,
166+
$currentLevel,
167+
'',
168+
);
169+
}
170+
171+
private function createExternalMenuEntry(ExternalEntryNode $subEntryNode, int $currentLevel): ExternalMenuEntryNode
172+
{
173+
return new ExternalMenuEntryNode(
174+
$subEntryNode->getValue(),
175+
TitleNode::fromString($subEntryNode->getTitle()),
176+
$currentLevel,
177+
);
178+
}
146179
}

packages/guides/src/Nodes/DocumentTree/DocumentEntryNode.php

+17-15
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@
1313

1414
namespace phpDocumentor\Guides\Nodes\DocumentTree;
1515

16-
use phpDocumentor\Guides\Nodes\AbstractNode;
17-
use phpDocumentor\Guides\Nodes\DocumentNode;
1816
use phpDocumentor\Guides\Nodes\TitleNode;
1917

20-
/** @extends AbstractNode<DocumentNode> */
21-
final class DocumentEntryNode extends AbstractNode
18+
use function array_filter;
19+
use function array_values;
20+
21+
/** @extends EntryNode<DocumentEntryNode|ExternalEntryNode> */
22+
final class DocumentEntryNode extends EntryNode
2223
{
23-
/** @var DocumentEntryNode[] */
24+
/** @var array<DocumentEntryNode|ExternalEntryNode> */
2425
private array $entries = [];
2526
/** @var SectionEntryNode[] */
2627
private array $sections = [];
27-
private DocumentEntryNode|null $parent = null;
2828

2929
public function __construct(
3030
private readonly string $file,
@@ -38,25 +38,27 @@ public function getTitle(): TitleNode
3838
return $this->titleNode;
3939
}
4040

41-
public function addChild(DocumentEntryNode $child): void
41+
public function addChild(DocumentEntryNode|ExternalEntryNode $child): void
4242
{
4343
$this->entries[] = $child;
4444
}
4545

46-
/** @return DocumentEntryNode[] */
46+
/** @return array<DocumentEntryNode> */
4747
public function getChildren(): array
4848
{
49-
return $this->entries;
50-
}
49+
// Filter the entries array to only include DocumentEntryNode instances
50+
$documentEntries = array_filter($this->entries, static function ($entry) {
51+
return $entry instanceof DocumentEntryNode;
52+
});
5153

52-
public function getParent(): DocumentEntryNode|null
53-
{
54-
return $this->parent;
54+
// Re-index the array to maintain numeric keys
55+
return array_values($documentEntries);
5556
}
5657

57-
public function setParent(DocumentEntryNode|null $parent): void
58+
/** @return array<DocumentEntryNode|ExternalEntryNode> */
59+
public function getMenuEntries(): array
5860
{
59-
$this->parent = $parent;
61+
return $this->entries;
6062
}
6163

6264
/** @return SectionEntryNode[] */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link https://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Guides\Nodes\DocumentTree;
15+
16+
use phpDocumentor\Guides\Nodes\AbstractNode;
17+
18+
/**
19+
* @template TValue
20+
* @extends AbstractNode<TValue>
21+
*/
22+
abstract class EntryNode extends AbstractNode
23+
{
24+
private DocumentEntryNode|null $parent = null;
25+
26+
public function getParent(): DocumentEntryNode|null
27+
{
28+
return $this->parent;
29+
}
30+
31+
public function setParent(DocumentEntryNode|null $parent): void
32+
{
33+
$this->parent = $parent;
34+
}
35+
}

0 commit comments

Comments
 (0)