Generate URLs for Internal Links in Hippo
Hippo CMS treats internal links differently from external ones. Even when they are a part of an HTML field in a content document, they are stored as a reference to the referred content document and not as a regular hyperlink. When you're using the hst:html
tag for rendering HTML fields in the template, it will take care of that automatically. But if you want to use the raw HTML string in some other way, the internal links inside it won't be valid:
<a href="sample-document">Internal link</a>
<a href="http://www.damirscorner.com">External link</a>
In my case, I was linking to a content document which was published at /site/content/sample-document.html
and /site/content/sample-document.html
. This is correctly reflected in the URL when rendering the HTML using the hst:html
tag:
<a href="/site/content/sample-document.html">Internal link</a>
<a href="http://www.damirscorner.com">External link</a>
The sample-document
value of the href
attribute in the original HTML string is actually a unique name for the internal link in that specific HTML value. If you search for the content document in the Hippo Console, you will see that the node corresponding to that HTML field has a sub node with a matching name:
This node points at the target of the internal link. To get the URL for it, the HstLinkCreator
helper class needs to be used:
ContentDocument content = getHippoBeanForPath(documentPath, ContentDocument.class);
if (content != null) {
HippoHtml hippoHtml = content.getContent();
String htmlString = hippoHtml.getContent();
HstRequestContext requestContext = request.getRequestContext();
HstLinkCreator linkCreator = requestContext.getHstLinkCreator();
// get child nodes corresponding to internal links
List<Node> localLinkNodes = hippoHtml.getChildBeans(HippoFacetSelect.class)
.stream()
.map(HippoItem::getNode)
.collect(Collectors.toList());
for (Node node : localLinkNodes) {
try {
// node name matches href attribute value
String name = node.getName();
// createAll returns links to all pages with the target content document
List<HstLink> links = linkCreator.createAll(node, requestContext, true);
if (!links.isEmpty()) {
// generated URLs are ordered by their length, shortest first
String url = links.get(0).toUrlForm(requestContext, true);
htmlString = htmlString.replace("href=\"" + name + "\"", "href=\"" + url + "\"");
}
} catch (RepositoryException e) {
// skip node in case of failure
}
}
}
The code above replaces the invalid href
attribute values with valid URLs:
<a href="http://localhost:9778/site/content/sample-document">Internal link</a>
<a href="http://www.damirscorner.com">External link</a>
The last parameter of the createAll
method determines whether the method returns URLs only for the mount corresponding to the current request or across all mounts on a host. Since I was using this code in a custom REST service hosted on its own mount, I had to include all mounts to get any URLs at all.
It's also worth mentioning that the built-in Content REST API also takes care of internal link generation:
{
"id": "9caea793-42b1-4f3e-a8e5-d58f810ce2ef",
"name": "another-sample-document",
"displayName": "Another sample document",
"type": "hippolocallinkurls:contentdocument",
"locale": "en",
"pubState": "published",
"pubwfCreationDate": "2014-03-25T16:52:00.000+01:00",
"pubwfLastModificationDate": "2019-05-23T09:37:27.527+02:00",
"pubwfPublicationDate": "2019-05-23T09:37:43.364+02:00",
"items": {
"hippolocallinkurls:introduction": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"hippolocallinkurls:title": "Another Lorem",
"hippolocallinkurls:publicationdate": "2014-03-25T16:52:00.000+01:00",
"hippolocallinkurls:content": {
"type": "hippostd:html",
"content": "<p><a data-hippo-link=\"sample-document\">Internal link</a></p>\n\n<p><a href=\"http://www.damirscorner.com\">External link</a></p>",
"links": {
"sample-document": {
"type": "local",
"id": "64ab4648-0c20-40d2-9f18-d7a394f0334b",
"url": "http://localhost:8080/site/api/documents/64ab4648-0c20-40d2-9f18-d7a394f0334b"
}
}
}
}
}
Instead of replacing them in the HTML string, it returns them in a separate links
object. These can be mapped to the special data-hippo-link
attribute of a
elements. I only decided in favor of a custom REST service because of a rather limited querying support in the built-in API. If that's not a problem for your use case, the built-in APIs should work well enough for you.