From 840f9138587330d0be6c7773ca7f466bcaac86cc Mon Sep 17 00:00:00 2001 From: "David M. Johnson" Date: Sat, 14 Nov 2026 22:13:21 -0500 Subject: [PATCH] Show entry titles as text in the pager and the big calendar The next/previous links on a permalink page and the titles in the big calendar now go through a new Utilities.plainTextExcerpt: markup removed, entities decoded, cut at a word boundary, then HTML-encoded. Titles saved with entities are no longer encoded twice. --- CHANGES.md | 11 +++ .../tags/calendar/BigWeblogCalendarModel.java | 12 +-- .../pagers/WeblogEntriesPermalinkPager.java | 4 +- .../roller/weblogger/util/Utilities.java | 25 +++++ .../WeblogEntriesPermalinkPagerTitleTest.java | 97 +++++++++++++++++++ .../roller/weblogger/util/UtilitiesTest.java | 16 +++ 6 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 app/src/test/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPagerTitleTest.java diff --git a/CHANGES.md b/CHANGES.md index ae054e20d7..f1473e3cc0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ # Apache Roller — Changes +## 6.1.8 (in development) + +### Bug fixes + +- **Entry titles show as text in the next/previous links and the big + calendar.** The links on a permalink page and the titles in + `#showWeblogEntryCalendarBig` now drop markup, decode entities and encode the + result, the same way the rest of the page shows titles. A title saved with + `&` no longer turns into `&amp;`, and the calendar cuts long titles + at 20 visible characters. + ## 6.1.7 ### Improvements diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/core/tags/calendar/BigWeblogCalendarModel.java b/app/src/main/java/org/apache/roller/weblogger/ui/core/tags/calendar/BigWeblogCalendarModel.java index 5b9db4d701..566dadf24d 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/core/tags/calendar/BigWeblogCalendarModel.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/core/tags/calendar/BigWeblogCalendarModel.java @@ -30,6 +30,7 @@ import org.apache.roller.weblogger.business.WebloggerFactory; import org.apache.roller.weblogger.business.WeblogEntryManager; import org.apache.roller.weblogger.pojos.WeblogEntry; +import org.apache.roller.weblogger.util.Utilities; import org.apache.roller.weblogger.pojos.WeblogEntrySearchCriteria; import org.apache.roller.weblogger.ui.rendering.util.WeblogPageRequest; import org.apache.roller.util.DateUtil; @@ -106,15 +107,12 @@ public String getContent(Date day) { sb.append(((WeblogEntry)entries.get(i)).getPermalink()); sb.append("\">"); - String title = ((WeblogEntry)entries.get(i)).getTitle().trim(); - if ( title.length()==0 ) { + String title = ((WeblogEntry)entries.get(i)).getTitle(); + if (title == null || title.trim().isEmpty()) { title = ((WeblogEntry)entries.get(i)).getAnchor(); } - if ( title.length() > 20 ) { - title = title.substring(0,20)+"..."; - } - - sb.append( title ); + // the title is printed as text, cut to fit the day's box + sb.append(Utilities.plainTextExcerpt(title, 20, 20, "...")); sb.append(""); } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPager.java b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPager.java index b92e3a4e49..d75b8afc4d 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPager.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPager.java @@ -110,7 +110,7 @@ public String getNextLink() { @Override public String getNextName() { if (getNextEntry() != null) { - String title = Utilities.truncateNicely(getNextEntry().getTitle(), 15, 20, "..."); + String title = Utilities.plainTextExcerpt(getNextEntry().getTitle(), 15, 20, "..."); return messageUtils.getString("weblogEntriesPager.single.next", new Object[] {title}); } return null; @@ -129,7 +129,7 @@ public String getPrevLink() { @Override public String getPrevName() { if (getPrevEntry() != null) { - String title = Utilities.truncateNicely(getPrevEntry().getTitle(), 15, 20, "..."); + String title = Utilities.plainTextExcerpt(getPrevEntry().getTitle(), 15, 20, "..."); return messageUtils.getString("weblogEntriesPager.single.prev", new Object[] {title}); } return null; diff --git a/app/src/main/java/org/apache/roller/weblogger/util/Utilities.java b/app/src/main/java/org/apache/roller/weblogger/util/Utilities.java index 643c7834c8..bf1931876e 100644 --- a/app/src/main/java/org/apache/roller/weblogger/util/Utilities.java +++ b/app/src/main/java/org/apache/roller/weblogger/util/Utilities.java @@ -639,6 +639,31 @@ public static String truncate(String str, int lower, int upper, return str2; } + /** + * Turns stored entry text, such as a title, into a short excerpt that is + * safe to print as HTML text: markup is removed, entities are decoded, the + * text is cut at a word boundary between lower and + * upper characters (with appendToEnd added when + * it is cut), and the result is HTML-encoded. + */ + public static String plainTextExcerpt(String str, int lower, int upper, + String appendToEnd) { + String text = unescapeHTML(removeHTML(str, false)); + if (upper < lower) { + upper = lower; + } + if (text.length() > upper) { + int loc = text.lastIndexOf(' ', upper); + if (loc >= lower) { + text = text.substring(0, loc); + } else { + text = text.substring(0, upper); + } + text = text + appendToEnd; + } + return escapeHTML(text); + } + /** * This method based on code from the String taglib at Apache Jakarta: * http:/ diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPagerTitleTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPagerTitleTest.java new file mode 100644 index 0000000000..7f95731914 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/ui/rendering/pagers/WeblogEntriesPermalinkPagerTitleTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. For additional + * information regarding copyright in this work, please see the NOTICE + * file in the top level directory of this distribution. + */ + +package org.apache.roller.weblogger.ui.rendering.pagers; + +import java.sql.Timestamp; + +import org.apache.roller.weblogger.TestUtils; +import org.apache.roller.weblogger.business.WeblogEntryManager; +import org.apache.roller.weblogger.business.WebloggerFactory; +import org.apache.roller.weblogger.pojos.User; +import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.roller.weblogger.pojos.WeblogEntry; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * The next and previous links on a permalink page show the neighbouring + * entries' titles as text. + */ +public class WeblogEntriesPermalinkPagerTitleTest { + + private User user; + private Weblog weblog; + + @BeforeEach + public void setUp() throws Exception { + TestUtils.setupWeblogger(); + user = TestUtils.setupUser("pagerTitleUser"); + weblog = TestUtils.setupWeblog("pagerTitleWeblog", user); + TestUtils.endSession(true); + } + + @AfterEach + public void tearDown() throws Exception { + TestUtils.teardownWeblog(weblog.getId()); + TestUtils.teardownUser(user.getUserName()); + TestUtils.endSession(true); + } + + private void entry(String anchor, String title, long pubTime) throws Exception { + WeblogEntry entry = TestUtils.setupWeblogEntry(anchor, weblog, user); + entry.setTitle(title); + entry.setPubTime(new Timestamp(pubTime)); + WeblogEntryManager mgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); + mgr.saveWeblogEntry(entry); + TestUtils.endSession(true); + } + + private WeblogEntriesPermalinkPager pagerFor(String anchor) throws Exception { + Weblog managed = TestUtils.getManagedWebsite(weblog); + return new WeblogEntriesPermalinkPager( + WebloggerFactory.getWeblogger().getUrlStrategy(), + managed, null, null, anchor, null, null, null, 0); + } + + @Test + public void neighbouringTitlesAreShownAsText() throws Exception { + long now = System.currentTimeMillis(); + entry("older", "Older", now - 3 * 60_000L); + entry("middle", "Middle", now - 2 * 60_000L); + entry("newer", "A & B bold", now - 60_000L); + + WeblogEntriesPermalinkPager pager = pagerFor("middle"); + + // markup is dropped, entities stay encoded exactly once + assertEquals("Older", pager.getPrevName()); + assertEquals("A & B bold", pager.getNextName()); + } + + @Test + public void encodedMarkupStaysEncoded() throws Exception { + long now = System.currentTimeMillis(); + entry("older", "<em> tags", now - 2 * 60_000L); + entry("middle", "Middle", now - 60_000L); + + assertEquals("<em> tags", pagerFor("middle").getPrevName()); + } +} diff --git a/app/src/test/java/org/apache/roller/weblogger/util/UtilitiesTest.java b/app/src/test/java/org/apache/roller/weblogger/util/UtilitiesTest.java index 2ab81ecdf2..9a31af40ff 100644 --- a/app/src/test/java/org/apache/roller/weblogger/util/UtilitiesTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/util/UtilitiesTest.java @@ -169,4 +169,20 @@ public void testAddNoFollow() { } */ + @Test + public void testPlainTextExcerpt() { + // markup is removed and the text is HTML-encoded once + assertEquals("Older", Utilities.plainTextExcerpt("Older", 15, 20, "...")); + assertEquals("A & B bold", Utilities.plainTextExcerpt("A & B bold", 15, 20, "...")); + assertEquals("<i> is italic", + Utilities.plainTextExcerpt("<i> is italic", 15, 20, "...")); + assertEquals(""quoted"", Utilities.plainTextExcerpt("\"quoted\"", 15, 20, "...")); + + // long text is cut at a word boundary, then encoded + assertEquals("The quick brown fox...", + Utilities.plainTextExcerpt("The quick brown fox jumps over", 15, 20, "...")); + assertEquals("<em>Emphasis</em>", + Utilities.plainTextExcerpt("<em>Emphasis</em>", 20, 20, "...")); + assertEquals("", Utilities.plainTextExcerpt(null, 15, 20, "...")); + } }