Skip to content

Commit 11e48a4

Browse files
authored
Merge pull request #1282 from CybotTM/fix/empty-filename-url-generator
[BUGFIX] Improve URL generation for empty filenames and known documents
2 parents d467c79 + 4e8a6a1 commit 11e48a4

File tree

6 files changed

+63
-18
lines changed

6 files changed

+63
-18
lines changed

packages/guides/src/Renderer/UrlGenerator/AbstractUrlGenerator.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
namespace phpDocumentor\Guides\Renderer\UrlGenerator;
1515

16-
use Exception;
1716
use League\Uri\BaseUri;
1817
use phpDocumentor\Guides\ReferenceResolvers\DocumentNameResolverInterface;
1918
use phpDocumentor\Guides\RenderContext;
19+
use phpDocumentor\Guides\Renderer\UrlGenerator\Exception\InvalidUrlException;
2020

2121
use function filter_var;
2222
use function sprintf;
@@ -32,8 +32,13 @@ public function __construct(private readonly DocumentNameResolverInterface $docu
3232

3333
public function createFileUrl(RenderContext $context, string $filename, string|null $anchor = null): string
3434
{
35-
return $filename . '.' . $context->getOutputFormat() .
36-
($anchor !== null ? '#' . $anchor : '');
35+
$anchorSuffix = $anchor !== null && $anchor !== '' ? '#' . $anchor : '';
36+
37+
if ($filename === '') {
38+
return $anchorSuffix !== '' ? $anchorSuffix : '#';
39+
}
40+
41+
return $filename . '.' . $context->getOutputFormat() . $anchorSuffix;
3742
}
3843

3944
abstract public function generateInternalPathFromRelativeUrl(
@@ -50,15 +55,20 @@ public function generateCanonicalOutputUrl(RenderContext $context, string $refer
5055
return $reference;
5156
}
5257

58+
// Pass through email addresses (for mailto: link generation)
5359
if (filter_var($reference, FILTER_VALIDATE_EMAIL) !== false) {
5460
return $reference;
5561
}
5662

63+
// If reference is already a known document, it's already canonical - use directly
5764
if ($context->getProjectNode()->findDocumentEntry($reference) !== null) {
58-
// todo: this is a hack, existing documents are expected to be handled like absolute links in some places
59-
$reference = '/' . $reference;
65+
return $this->generateInternalUrl(
66+
$context,
67+
$this->createFileUrl($context, $reference, $anchor),
68+
);
6069
}
6170

71+
// Otherwise, resolve relative reference to canonical path
6272
$canonicalUrl = $this->documentNameResolver->canonicalUrl(
6373
$context->getDirName(),
6474
$reference,
@@ -75,7 +85,7 @@ public function generateInternalUrl(
7585
string $canonicalUrl,
7686
): string {
7787
if (!$this->isRelativeUrl($canonicalUrl)) {
78-
throw new Exception(sprintf('%s::%s may only be applied to relative URLs, %s cannot be handled', self::class, __METHOD__, $canonicalUrl));
88+
throw new InvalidUrlException(sprintf('%s::%s may only be applied to relative URLs, %s cannot be handled', self::class, __METHOD__, $canonicalUrl));
7989
}
8090

8191
return $this->generateInternalPathFromRelativeUrl($renderContext, $canonicalUrl);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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\Renderer\UrlGenerator\Exception;
15+
16+
use Exception;
17+
18+
final class InvalidUrlException extends Exception
19+
{
20+
}

packages/guides/tests/unit/Renderer/UrlGenerator/RelativeUrlGeneratorTest.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use phpDocumentor\Guides\ReferenceResolvers\DocumentNameResolverInterface;
1717
use phpDocumentor\Guides\RenderContext;
18+
use phpDocumentor\Guides\Renderer\UrlGenerator\Exception\InvalidUrlException;
1819
use PHPUnit\Framework\Attributes\DataProvider;
1920
use PHPUnit\Framework\TestCase;
2021

@@ -72,15 +73,10 @@ public static function generateRelativeInternalUrlProvider(): array
7273
}
7374

7475
#[DataProvider('fileUrlProvider')]
75-
public function testCreateFileUrl(string $expected, string $filename, string $outputFormat = 'html', string|null $anchor = null, string $skip = ''): void
76+
public function testCreateFileUrl(string $expected, string $filename, string $outputFormat = 'html', string|null $anchor = null): void
7677
{
77-
if ($skip !== '') {
78-
self::markTestSkipped($skip);
79-
}
80-
8178
$urlGenerator = new RelativeUrlGenerator(self::createStub(DocumentNameResolverInterface::class));
8279
$renderContext = $this->createMock(RenderContext::class);
83-
$renderContext->method('getCurrentFileName')->willReturn($filename);
8480
$renderContext->method('getOutputFormat')->willReturn($outputFormat);
8581
self::assertSame($expected, $urlGenerator->createFileUrl($renderContext, $filename, $anchor));
8682
}
@@ -113,15 +109,34 @@ public static function fileUrlProvider(): array
113109
'filename' => '',
114110
'outputFormat' => 'html',
115111
'anchor' => 'anchor',
116-
'skip' => 'Empty filenames are not supported',
117112
],
118-
'Empty File with empty anchor' => [
113+
'Empty File with null anchor' => [
119114
'expected' => '#',
120115
'filename' => '',
121116
'outputFormat' => 'html',
122117
'anchor' => null,
123-
'skip' => 'Empty filenames are not supported',
118+
],
119+
'Empty File with empty string anchor' => [
120+
'expected' => '#',
121+
'filename' => '',
122+
'outputFormat' => 'html',
123+
'anchor' => '',
124+
],
125+
'File with empty string anchor' => [
126+
'expected' => 'file.html',
127+
'filename' => 'file',
128+
'outputFormat' => 'html',
129+
'anchor' => '',
124130
],
125131
];
126132
}
133+
134+
public function testGenerateInternalUrlThrowsOnAbsoluteUrl(): void
135+
{
136+
$urlGenerator = new RelativeUrlGenerator(self::createStub(DocumentNameResolverInterface::class));
137+
$renderContext = $this->createMock(RenderContext::class);
138+
139+
$this->expectException(InvalidUrlException::class);
140+
$urlGenerator->generateInternalUrl($renderContext, 'https://example.com/page.html');
141+
}
127142
}

tests/Integration/tests-full/bootstrap/bootstrap-menu-external-linktargets/expected/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ <h1>Document Title</h1>
8787

8888
</li>
8989
<li class="toc-item">
90-
<a href="/page1.html#">Some Page</a>
90+
<a href="/page1.html">Some Page</a>
9191

9292

9393
</li>

tests/Integration/tests-full/bootstrap/bootstrap-menu-external-on-subpage/expected/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ <h1>Document Title</h1>
109109

110110
</li>
111111
<li class="toc-item">
112-
<a href="/page1.html#">Some Page</a>
112+
<a href="/page1.html">Some Page</a>
113113
<ul class="menu-level-1">
114114
<li class="toc-item">
115115
<a href="https://example.com/index.html#another">Title 2</a>

tests/Integration/tests/toctree/toctree-external-linktargets/expected/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ <h1>Document Title</h1>
1515

1616
</li>
1717
<li class="toc-item">
18-
<a href="/page1.html#">Some Page</a>
18+
<a href="/page1.html">Some Page</a>
1919

2020

2121
</li>

0 commit comments

Comments
 (0)