1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-10-23 21:28:56 +03:00

Verify that page/section links point at an existing section.

Only same-page section links were verified, so a page/section link with a bad section or a missing leading slash would silently render a broken anchor.

Require a link section to begin with a slash and, after all pages have been rendered, verify that each page/section link points at a section that exists on the target page. Links to a page that is not part of the build (e.g. a partial build) are skipped.

Also fix the news link to the 2.59.0 release notes, which was missing the leading slash and pointed at a broken anchor.
This commit is contained in:
David Steele
2026-07-21 21:31:15 +07:00
parent 5e173ee3bd
commit c8f4192c4a
4 changed files with 88 additions and 1 deletions

View File

@@ -355,6 +355,9 @@ eval
}
}
# Verify page/section links now that all pages have been rendered and their section anchors are known
$oManifest->linkVerify();
# Cache the manifest (mostly useful for testing rendering changes in the code)
if (!$bNoCache && !$bCacheOnly)
{

View File

@@ -620,6 +620,38 @@ sub renderOutGet
);
}
####################################################################################################################################
# linkVerify
#
# Verify that every page/section link points at a section that exists. This must be done after all pages have been rendered since
# the section anchors for a page are only known once it has been rendered. Links to a page that was not rendered in this build
# (e.g. a partial build with --include/--exclude) are skipped since the section anchors are not available to check.
####################################################################################################################################
sub linkVerify
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->linkVerify');
foreach my $rhLink (@{$self->{rhyLinkVerify} || []})
{
# Skip if the linked page was not rendered in this build
next if (!defined($self->{hPageAnchor}{$rhLink->{page}}));
# Error if the section does not exist on the page
if (!defined($self->{hPageAnchor}{$rhLink->{page}}{substr($rhLink->{section}, 1)}))
{
confess &log(
ERROR,
"page '$rhLink->{source}' has a link to page '$rhLink->{page}' section '$rhLink->{section}' that does not exist");
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# cacheKey
####################################################################################################################################

View File

@@ -200,6 +200,9 @@ sub new
$self->required($strPath);
}
}
# Register the section anchor map so cross-page links to this page can be verified
$self->{oManifest}->{hPageAnchor}{$self->{strRenderOutKey}} = $self->sectionAnchorMap();
}
if (defined($self->{oDoc}))
@@ -588,6 +591,40 @@ sub isRequired
defined(${$self->{oSectionRequired}}{$strPath}) ? true : false;
}
####################################################################################################################################
# sectionAnchorMap
#
# Build the set of section anchors for this page. An anchor is the section id prefixed with the parent anchors, which matches the
# anchor generated by the html renderer. A section with anchor set to no-inherit does not include its parent anchors.
####################################################################################################################################
sub sectionAnchorMap
{
my $self = shift;
my $rhAnchor = {};
foreach my $strPath (keys(%{$self->{oSection}}))
{
my @stryId;
my $oSection = ${$self->{oSection}}{$strPath};
# Prepend section ids walking up the parents until a section that does not inherit the parent anchor
while (defined($oSection))
{
unshift(@stryId, $oSection->paramGet('id'));
last if $oSection->paramTest(XML_SECTION_PARAM_ANCHOR, XML_SECTION_PARAM_ANCHOR_VALUE_NOINHERIT);
my $strParentPath = $oSection->paramGet('path-parent', false);
$oSection = defined($strParentPath) ? ${$self->{oSection}}{$strParentPath} : undef;
}
$rhAnchor->{join('/', @stryId)} = true;
}
return $rhAnchor;
}
####################################################################################################################################
# processTag
####################################################################################################################################
@@ -627,6 +664,12 @@ sub processTag
my $strPage = $self->variableReplace($oTag->paramGet('page', false));
my $strSection = $oTag->paramGet('section', false);
# A section link must begin with / since the leading / is stripped to form the anchor
if (defined($strSection) && substr($strSection, 0, 1) ne '/')
{
confess &log(ERROR, "link section '${strSection}' must begin with '/'");
}
# If a page/section link points to the current page then remove the page portion
if (defined($strPage) && defined($strSection) && defined($self->{strRenderOutKey}) &&
$strPage eq $self->{strRenderOutKey})
@@ -637,6 +680,15 @@ sub processTag
# If this is a page URL
if (defined($strPage))
{
# Record the page/section link so it can be verified once all pages have been rendered and their section
# anchors are known
if (defined($strSection))
{
push(
@{$self->{oManifest}->{rhyLinkVerify}},
{source => $self->{strRenderOutKey}, page => $strPage, section => $strSection});
}
# If the page wasn't rendered then point at the website
if (!defined($self->{oManifest}->renderOutGet($strType, $strPage, true)))
{

View File

@@ -49,7 +49,7 @@
<list-item>Exit async archive-push on first error (David Steele)</list-item>
</list>
<p>See the <link page="release" section="2.59.0">2.59.0 Release Notes</link> for additional features and improvements.</p>
<p>See the <link page="release" section="/2.59.0">2.59.0 Release Notes</link> for additional features and improvements.</p>
</section>
<section id="note">