From 872b49ee2edf85a18599230ff3f61f12c051da6c Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:09:06 -0400 Subject: [PATCH 01/22] Change to count unique users from the date range --- .../analytics/AnalyticsDataService.java | 42 +++++++++---------- .../persistence/UserRepository.java | 42 +++++-------------- .../login/UserLoginRepository.java | 9 ++++ 3 files changed, 38 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/simisinc/platform/application/analytics/AnalyticsDataService.java b/src/main/java/com/simisinc/platform/application/analytics/AnalyticsDataService.java index b3f04ed4..a3f50a91 100644 --- a/src/main/java/com/simisinc/platform/application/analytics/AnalyticsDataService.java +++ b/src/main/java/com/simisinc/platform/application/analytics/AnalyticsDataService.java @@ -33,6 +33,8 @@ import com.simisinc.platform.domain.model.analytics.PerformanceMetric; import com.simisinc.platform.domain.model.dashboard.ActiveSessionData; import com.simisinc.platform.domain.model.dashboard.StatisticsData; +import com.simisinc.platform.infrastructure.persistence.SessionRepository; +import com.simisinc.platform.infrastructure.persistence.UserRepository; import com.simisinc.platform.infrastructure.persistence.analytics.PerformanceMetricRepository; import com.simisinc.platform.infrastructure.persistence.cms.FileItemRepository; import com.simisinc.platform.infrastructure.persistence.cms.FileVersionRepository; @@ -41,8 +43,6 @@ import com.simisinc.platform.infrastructure.persistence.datasets.DatasetRepository; import com.simisinc.platform.infrastructure.persistence.items.ItemFileItemRepository; import com.simisinc.platform.infrastructure.persistence.login.UserLoginRepository; -import com.simisinc.platform.infrastructure.persistence.SessionRepository; -import com.simisinc.platform.infrastructure.persistence.UserRepository; /** * Service for aggregating and providing analytics data @@ -65,25 +65,26 @@ public static ObjectNode loadOverview(String rangeStart, String rangeEnd) { LocalDate end = LocalDate.parse(rangeEnd); // The queries will need to be updated to handle different date ranges - + // This method currently assumes daily data from based on date amounts int days = (int) ChronoUnit.DAYS.between(start, end) + 1; // Get data from repositories for current period List dailySessions = WebPageHitRepository.findDailySessions(days); - List dailyLogins = UserLoginRepository.findUniqueDailyLogins(days); List dailyHits = WebPageHitRepository.findDailyWebHits(days); // Calculate KPIs for current period long totalSessions = dailySessions.stream().mapToLong(d -> Long.parseLong(d.getValue())).sum(); long totalHits = dailyHits.stream().mapToLong(d -> Long.parseLong(d.getValue())).sum(); - long totalUsers = dailyLogins.stream().mapToLong(d -> Long.parseLong(d.getValue())).sum(); + + long totalUniqueLogins = UserLoginRepository.countUniqueLogins( + Timestamp.valueOf(start.atStartOfDay()), + Timestamp.valueOf(end.plusDays(1).atStartOfDay())); // Calculate new users based on user creation dates within range long newUsersCount = UserRepository.countNewUsers( Timestamp.valueOf(start.atStartOfDay()), - Timestamp.valueOf(end.plusDays(1).atStartOfDay()) - ); + Timestamp.valueOf(end.plusDays(1).atStartOfDay())); // Calculate bounce rate based on single-page sessions double bounceRate = SessionRepository.findBounceRate(days) / 100.0; @@ -94,14 +95,12 @@ public static ObjectNode loadOverview(String rangeStart, String rangeEnd) { // Get data for previous period to calculate trends // Query the previous equivalent period List prevDailySessions = WebPageHitRepository.findDailySessions(days * 2); - List prevDailyLogins = UserLoginRepository.findUniqueDailyLogins(days * 2); List prevDailyHits = WebPageHitRepository.findDailyWebHits(days * 2); // Extract previous period data (older half of the data) long prevTotalSessions = 0; long prevTotalHits = 0; - long prevTotalUsers = 0; - + if (prevDailySessions != null && prevDailySessions.size() > days) { prevTotalSessions = prevDailySessions.stream() .skip(Math.max(0, prevDailySessions.size() - days * 2)) @@ -116,13 +115,6 @@ public static ObjectNode loadOverview(String rangeStart, String rangeEnd) { .mapToLong(d -> Long.parseLong(d.getValue())) .sum(); } - if (prevDailyLogins != null && prevDailyLogins.size() > days) { - prevTotalUsers = prevDailyLogins.stream() - .skip(Math.max(0, prevDailyLogins.size() - days * 2)) - .limit(days) - .mapToLong(d -> Long.parseLong(d.getValue())) - .sum(); - } double prevAvgSessionDuration = SessionRepository.findAverageSessionDuration(days * 2); @@ -132,13 +124,17 @@ public static ObjectNode loadOverview(String rangeStart, String rangeEnd) { // Calculate previous period new users LocalDate prevStart = start.minusDays(days); LocalDate prevEnd = end.minusDays(days); + long prevNewUsersCount = UserRepository.countNewUsers( Timestamp.valueOf(prevStart.atStartOfDay()), - Timestamp.valueOf(prevEnd.plusDays(1).atStartOfDay()) - ); + Timestamp.valueOf(prevEnd.plusDays(1).atStartOfDay())); + + long prevUniqueLogins = UserLoginRepository.countUniqueLogins( + Timestamp.valueOf(prevStart.atStartOfDay()), + Timestamp.valueOf(prevEnd.plusDays(1).atStartOfDay())); // Calculate trend percentages - double usersTrend = calculateTrendPercentage(prevTotalUsers, totalUsers); + double usersTrend = calculateTrendPercentage(prevUniqueLogins, totalUniqueLogins); double sessionsTrend = calculateTrendPercentage(prevTotalSessions, totalSessions); double pageViewsTrend = calculateTrendPercentage(prevTotalHits, totalHits); double avgDurationTrend = calculateTrendPercentage(prevAvgSessionDuration, avgSessionDuration); @@ -153,7 +149,7 @@ public static ObjectNode loadOverview(String rangeStart, String rangeEnd) { // KPIs with trend values ObjectNode kpis = response.putObject("kpis"); - kpis.put("activeUsers", totalUsers); + kpis.put("activeUsers", totalUniqueLogins); kpis.put("activeUsersTrend", Math.round(usersTrend * 10.0) / 10.0); kpis.put("sessions", totalSessions); kpis.put("sessionsTrend", Math.round(sessionsTrend * 10.0) / 10.0); @@ -240,7 +236,7 @@ public static ObjectNode loadContent(String rangeStart, String rangeEnd, int day // @todo additional data response.putArray("searchQueries"); - + response.putArray("referrers"); return response; @@ -270,7 +266,7 @@ public static ObjectNode loadAudience(String rangeStart, String rangeEnd) { // Get devices and browsers data from sessions List devices = SessionRepository.findTopDevices(days); List browsers = SessionRepository.findTopBrowsers(days); - + // Get average session duration double avgSessionDuration = SessionRepository.findAverageSessionDuration(days); diff --git a/src/main/java/com/simisinc/platform/infrastructure/persistence/UserRepository.java b/src/main/java/com/simisinc/platform/infrastructure/persistence/UserRepository.java index e429b3b6..a5170c98 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/persistence/UserRepository.java +++ b/src/main/java/com/simisinc/platform/infrastructure/persistence/UserRepository.java @@ -165,8 +165,7 @@ public static List findAll(UserSpecification specification, DataConstraint constraints = new DataConstraints(); } constraints.setDefaultColumnToSortBy("user_id desc"); - DataResult result = query(specification, constraints); - return (List) result.getRecords(); + return query(specification, constraints).getRecords(); } public static List findMonthlyUserRegistrations(int monthsLimit) { @@ -215,43 +214,22 @@ public static List findDailyUserRegistrations(int daysToLimit) { return records; } + /** Count total users of the system */ public static long countTotalUsers() { - long count = -1; - String SQL_QUERY = "SELECT COUNT(user_id) AS user_count " + - "FROM users "; - try (Connection connection = DB.getConnection(); - PreparedStatement pst = connection.prepareStatement(SQL_QUERY); - ResultSet rs = pst.executeQuery()) { - if (rs.next()) { - count = rs.getLong("user_count"); - } - } catch (SQLException se) { - LOG.error("SQLException: " + se.getMessage()); - } - return count; + return DB.SELECT("COUNT(user_id) AS user_count") + .FROM("users") + .returnValue(Long.class); } /** * Count users created within a date range */ public static long countNewUsers(Timestamp startDate, Timestamp endDate) { - long count = 0; - String sqlQuery = "SELECT COUNT(user_id) AS new_user_count " + - "FROM users " + - "WHERE created >= ? " + - "AND created < ?"; - try (Connection connection = DB.getConnection(); - PreparedStatement pst = connection.prepareStatement(sqlQuery)) { - pst.setTimestamp(1, startDate); - pst.setTimestamp(2, endDate); - ResultSet rs = pst.executeQuery(); - if (rs.next()) { - count = rs.getLong("new_user_count"); - } - } catch (SQLException se) { - LOG.error("SQLException: " + se.getMessage()); - } - return count; + return DB.SELECT("COUNT(user_id) AS new_user_count") + .FROM("users") + .WHERE("created >= ?", startDate) + .AND("created < ?", endDate) + .returnValue(Long.class); } public static User save(User record) { diff --git a/src/main/java/com/simisinc/platform/infrastructure/persistence/login/UserLoginRepository.java b/src/main/java/com/simisinc/platform/infrastructure/persistence/login/UserLoginRepository.java index c37b81fb..48981c86 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/persistence/login/UserLoginRepository.java +++ b/src/main/java/com/simisinc/platform/infrastructure/persistence/login/UserLoginRepository.java @@ -157,6 +157,15 @@ public static long findUniqueAuthenticatedUsers(int daysToLimit) { return count; } + /** Count unique users in the time period */ + public static long countUniqueLogins(Timestamp startDate, Timestamp endDate) { + return DB.SELECT("COUNT(DISTINCT user_id) AS unique_user_count") + .FROM("user_logins") + .WHERE("created >= ?", startDate) + .AND("created < ?", endDate) + .returnValue(Long.class); + } + public static UserLogin save(UserLogin record) { // if (record.getId() > -1) { // return update(record); From 97ed86beb543c54a321bc81a8d227b0d4817306c Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:12:55 -0400 Subject: [PATCH 02/22] Updated sanitize url coverage --- .../platform/application/cms/UrlCommand.java | 60 ++++++++++++++----- .../application/http/HttpGetCommand.java | 15 ++++- .../widgets/cms/RemoteContentWidget.java | 10 +++- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java b/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java index 3c78a95b..81816bc9 100644 --- a/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java +++ b/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java @@ -20,6 +20,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; +import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -36,22 +37,22 @@ public class UrlCommand { private static Log LOG = LogFactory.getLog(UrlCommand.class); - public static String encode(String url) { - if (StringUtils.isBlank(url)) { - LOG.debug("URL is blank"); - return "#"; - } + // URL characters that cannot break out of an href/src attribute: no quotes, angle brackets, + // backslash, backtick, or whitespace. + private static final Pattern SAFE_URL = Pattern.compile("^[A-Za-z0-9/?&=#%._~:@!$()*+,;-]*$"); - // Validate first - String[] schemes = { "http", "https" }; - UrlValidator urlValidator = new UrlValidator(schemes); - if (!urlValidator.isValid(url)) { + // Site-relative path + optional query/fragment built from URL-safe + // characters only: no quotes, angle brackets, backslash, colon, or whitespace + private static final Pattern SAFE_RETURN_PAGE = Pattern.compile("^/[A-Za-z0-9/?&=#%._~+,;-]*$"); + + public static String encode(String url) { + // Sanitize the URL first + String sanitizedUrl = sanitizeUrl(url); + if (StringUtils.isBlank(sanitizedUrl)) { + LOG.debug("URL is not safe"); return "#"; } - - // @todo handle invalid urls - - return url; + return sanitizedUrl; } public static String encodeUri(String uri) { @@ -94,12 +95,39 @@ public static String getValidReturnPage(String returnPage) { if (StringUtils.isBlank(returnPage)) { return null; } - if (returnPage.contains(":")) { + if (returnPage.startsWith("//")) { return null; } - if (returnPage.startsWith("/")) { - return returnPage; + if (!SAFE_RETURN_PAGE.matcher(returnPage).matches()) { + return null; } return returnPage; } + + /** + * Returns the url when it is safe to place in an href/src attribute -- a site-relative path, + * anchor, or an http(s)/mailto/tel absolute url with no attribute-breakout characters -- otherwise + * null. Active schemes such as javascript: and data: are rejected, as are protocol-relative "//" + * targets. + */ + public static String sanitizeUrl(String url) { + if (StringUtils.isBlank(url)) { + return null; + } + String value = url.trim(); + if (value.startsWith("//") || !SAFE_URL.matcher(value).matches()) { + return null; + } + // If the value carries a scheme, allow only the safe ones + String lower = value.toLowerCase(); + if (lower.matches("^[a-z][a-z0-9+.-]*:.*")) { + if (lower.startsWith("http://") || lower.startsWith("https://") + || lower.startsWith("mailto:") || lower.startsWith("tel:")) { + return value; + } + return null; + } + // No scheme: a site-relative path, anchor, or query + return value; + } } diff --git a/src/main/java/com/simisinc/platform/application/http/HttpGetCommand.java b/src/main/java/com/simisinc/platform/application/http/HttpGetCommand.java index 87d82d4e..a7a3cd0b 100644 --- a/src/main/java/com/simisinc/platform/application/http/HttpGetCommand.java +++ b/src/main/java/com/simisinc/platform/application/http/HttpGetCommand.java @@ -36,11 +36,24 @@ */ public class HttpGetCommand { - private static Log LOG = LogFactory.getLog(HttpDownloadFileCommand.class); + private static Log LOG = LogFactory.getLog(HttpGetCommand.class); public static final int GET = 1; public static final int DELETE = 2; + /** + * Validates that {@code url} is SSRF-safe, then fetches it. Returns null and logs a + * warning if the guard rejects the URL. Use this for any URL derived from untrusted input. + */ + public static String executeUserUrl(String url) { + RemoteUrlValidationCommand.ValidationResult validation = RemoteUrlValidationCommand.validate(url); + if (!validation.isAllowed()) { + LOG.warn("Blocked an SSRF-unsafe user-supplied url: " + url); + return null; + } + return execute(url, GET); + } + public static String execute(String url) { return execute(url, GET); } diff --git a/src/main/java/com/simisinc/platform/presentation/widgets/cms/RemoteContentWidget.java b/src/main/java/com/simisinc/platform/presentation/widgets/cms/RemoteContentWidget.java index 9d793bd7..f401c0ea 100644 --- a/src/main/java/com/simisinc/platform/presentation/widgets/cms/RemoteContentWidget.java +++ b/src/main/java/com/simisinc/platform/presentation/widgets/cms/RemoteContentWidget.java @@ -1,4 +1,5 @@ /* + * Copyright 2025-2026 Matt Rajkowski (https://github.com/rajkowski) * Copyright 2022 SimIS Inc. (https://www.simiscms.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +30,7 @@ import org.jsoup.select.Elements; import com.github.benmanes.caffeine.cache.Cache; +import com.simisinc.platform.application.cms.UrlCommand; import com.simisinc.platform.application.http.HttpGetCommand; import com.simisinc.platform.infrastructure.cache.CacheManager; import com.simisinc.platform.presentation.controller.WidgetContext; @@ -66,7 +68,11 @@ public WidgetContext execute(WidgetContext context) { // Create a wrapper for images if (url.endsWith(".gif") || url.endsWith(".png") || url.endsWith(".jpg")) { - content = ""; + String safeUrl = UrlCommand.sanitizeUrl(url); + if (safeUrl == null) { + return null; + } + content = ""; cache.put(url, content); return useReturnType(context, content); } @@ -75,7 +81,7 @@ public WidgetContext execute(WidgetContext context) { // @todo if this fails we don't want many more requests... try { long startRequestTime = System.currentTimeMillis(); - String remoteContent = HttpGetCommand.execute(url); + String remoteContent = HttpGetCommand.executeUserUrl(url); if (StringUtils.isBlank(remoteContent)) { return null; } From c5faf645c9123e7e93b14846007afbddc9908020 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:13:27 -0400 Subject: [PATCH 03/22] Updated sanitize url coverage --- .../http/RemoteUrlValidationCommand.java | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/main/java/com/simisinc/platform/application/http/RemoteUrlValidationCommand.java diff --git a/src/main/java/com/simisinc/platform/application/http/RemoteUrlValidationCommand.java b/src/main/java/com/simisinc/platform/application/http/RemoteUrlValidationCommand.java new file mode 100644 index 00000000..9d185d3f --- /dev/null +++ b/src/main/java/com/simisinc/platform/application/http/RemoteUrlValidationCommand.java @@ -0,0 +1,155 @@ +/* + * Copyright 2026 SimIS Inc. (https://www.simiscms.com) + * + * Licensed 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. + */ + +package com.simisinc.platform.application.http; + +import java.net.InetAddress; +import java.net.URI; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.validator.routines.UrlValidator; + +/** + * SSRF guard for server-side fetches of a caller-supplied (untrusted) URL. Applied where the + * URL is arbitrary user/admin input or is derived from a fetched response -- NOT on the + * fixed third-party API clients (OAuth, Stripe, ...), which legitimately target + * configured endpoints that may be internal in some deployments. + * + * @author Liz Houser + * @created 7/23/2026 + */ +public class RemoteUrlValidationCommand { + + private static final Log LOG = LogFactory.getLog(RemoteUrlValidationCommand.class); + + private static final String[] ALLOWED_SCHEMES = { "http", "https" }; + + /** + * The result of validating a URL: whether the fetch is allowed, and -- when it is -- the + * exact host and address(es) that were checked, so a caller can pin a subsequent connection + * to precisely those bytes instead of letting it re-resolve the host. + */ + public static final class ValidationResult { + private final boolean allowed; + private final String host; + private final InetAddress[] addresses; + + // Package-private rather than private: lets same-package tests build a fixed + // ValidationResult to stub RemoteUrlValidationCommand.validate(...) with (see + // HttpGetCommandExecuteUserUrlPinTest), without opening construction to other packages. + ValidationResult(boolean allowed, String host, InetAddress[] addresses) { + this.allowed = allowed; + this.host = host; + this.addresses = addresses; + } + + public boolean isAllowed() { + return allowed; + } + + /** The URL's host, when it could be extracted -- regardless of whether it was allowed. */ + public String getHost() { + return host; + } + + /** + * The exact addresses {@code host} resolved to at validation time, already confirmed + * public/routable. Only meaningful when {@link #isAllowed()} is true; callers must check + * that first. + */ + public InetAddress[] getAddresses() { + return addresses; + } + } + + /** + * Determines whether a server-side fetch of the given URL is permitted. Fails closed: + * a blank, malformed, non-http(s), unresolvable, or internally-routed URL returns false. + * + * @param url the caller-supplied URL to be fetched + * @return true only if the URL is safe to fetch + */ + public static boolean isFetchAllowed(String url) { + return validate(url).isAllowed(); + } + + /** + * Same check as {@link #isFetchAllowed(String)}, but also returns the host and exact + * address(es) that were validated, so a caller can pin a subsequent connection to them. + * Fails closed identically: a blank, malformed, non-http(s), unresolvable, or + * internally-routed URL yields a result with {@code isAllowed() == false}. + * + * @param url the caller-supplied URL to be fetched + * @return the validation result; check {@link ValidationResult#isAllowed()} first + */ + public static ValidationResult validate(String url) { + if (StringUtils.isBlank(url)) { + return new ValidationResult(false, null, null); + } + if (!new UrlValidator(ALLOWED_SCHEMES).isValid(url)) { + LOG.debug("Blocked a non-http(s) or malformed url: " + url); + return new ValidationResult(false, null, null); + } + try { + String host = URI.create(url).getHost(); + if (StringUtils.isBlank(host)) { + LOG.debug("Blocked a url with no resolvable host: " + url); + return new ValidationResult(false, null, null); + } + InetAddress[] addresses = InetAddress.getAllByName(host); + if (addresses == null || addresses.length == 0) { + return new ValidationResult(false, host, null); + } + // Every address the host resolves to must be public/routable; if any is internal, + // reject -- a host that resolves to both a public and a private address is not safe. + for (InetAddress address : addresses) { + if (isBlockedAddress(address)) { + LOG.warn("Blocked an SSRF-unsafe address for host " + host + ": " + address.getHostAddress()); + return new ValidationResult(false, host, null); + } + } + return new ValidationResult(true, host, addresses); + } catch (Exception e) { + // Unresolvable host, malformed authority, etc. -- fail closed. + LOG.warn("Blocked a url that could not be validated: " + url); + return new ValidationResult(false, null, null); + } + } + + /** + * True when the address is one an SSRF payload uses to reach the host or internal network: + * loopback, any-local, link-local (incl. the cloud metadata endpoint), private/site-local, + * IPv6 unique-local, or multicast. + */ + static boolean isBlockedAddress(InetAddress address) { + if (address.isLoopbackAddress() // 127.0.0.0/8, ::1 + || address.isAnyLocalAddress() // 0.0.0.0, :: + || address.isLinkLocalAddress() // 169.254.0.0/16 (cloud metadata), fe80::/10 + || address.isSiteLocalAddress() // 10/8, 172.16/12, 192.168/16 + || address.isMulticastAddress()) { + return true; + } + // InetAddress.isSiteLocalAddress() does not recognize IPv6 unique-local (fc00::/7); + // block it explicitly. + byte[] bytes = address.getAddress(); + if (bytes.length == 16 && (bytes[0] & 0xFE) == 0xFC) { + return true; + } + return false; + } +} From cb5621a9c9677e0005bd7182e0ecac3662b9eb62 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:13:55 -0400 Subject: [PATCH 04/22] Updated debug to trace --- .../scheduler/ecommerce/OrderManagementProcessNewOrders.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/simisinc/platform/infrastructure/scheduler/ecommerce/OrderManagementProcessNewOrders.java b/src/main/java/com/simisinc/platform/infrastructure/scheduler/ecommerce/OrderManagementProcessNewOrders.java index a0b5c7a0..3a76bc8b 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/scheduler/ecommerce/OrderManagementProcessNewOrders.java +++ b/src/main/java/com/simisinc/platform/infrastructure/scheduler/ecommerce/OrderManagementProcessNewOrders.java @@ -62,7 +62,7 @@ private static void executeForTenant() { // Determine the processor String service = LoadSitePropertyCommand.loadByName("ecommerce.orderFulfillment"); if (StringUtils.isBlank(service) || "None".equalsIgnoreCase(service)) { - LOG.debug("Order fulfillment is not configured"); + LOG.trace("Order fulfillment is not configured"); return; } From bf65273b15b1afe8875e0326ce694e42ce7ebf55 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:16:51 -0400 Subject: [PATCH 05/22] Update placeholder example --- src/main/webapp/WEB-INF/jsp/admin/site-properties-editor.jsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/WEB-INF/jsp/admin/site-properties-editor.jsp b/src/main/webapp/WEB-INF/jsp/admin/site-properties-editor.jsp index e8253a53..c1559505 100644 --- a/src/main/webapp/WEB-INF/jsp/admin/site-properties-editor.jsp +++ b/src/main/webapp/WEB-INF/jsp/admin/site-properties-editor.jsp @@ -132,7 +132,7 @@
- "> + ">
From 7489f9fe09eae79d82d618e0a94cdd218985fca0 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:18:47 -0400 Subject: [PATCH 06/22] Update placeholder example --- src/main/webapp/WEB-INF/jsp/items/item-business-form.jsp | 2 +- src/main/webapp/WEB-INF/jsp/items/item-job-form.jsp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/WEB-INF/jsp/items/item-business-form.jsp b/src/main/webapp/WEB-INF/jsp/items/item-business-form.jsp index b7b67aa6..356f19b7 100644 --- a/src/main/webapp/WEB-INF/jsp/items/item-business-form.jsp +++ b/src/main/webapp/WEB-INF/jsp/items/item-business-form.jsp @@ -147,7 +147,7 @@
Website * - " required="true"> + " required="true">
Phone * diff --git a/src/main/webapp/WEB-INF/jsp/items/item-job-form.jsp b/src/main/webapp/WEB-INF/jsp/items/item-job-form.jsp index 3c3439d6..29030744 100644 --- a/src/main/webapp/WEB-INF/jsp/items/item-job-form.jsp +++ b/src/main/webapp/WEB-INF/jsp/items/item-job-form.jsp @@ -135,7 +135,7 @@
Optional Website for details and/or apply - "> + ">
Optional Phone Number To Call From d368fc917b424078aff9b02697c6604478b42bdb Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:19:47 -0400 Subject: [PATCH 07/22] Update local url link to render as link --- src/main/webapp/WEB-INF/jsp/portal/custom-fields.jsp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/webapp/WEB-INF/jsp/portal/custom-fields.jsp b/src/main/webapp/WEB-INF/jsp/portal/custom-fields.jsp index 19c16e22..1acadf64 100644 --- a/src/main/webapp/WEB-INF/jsp/portal/custom-fields.jsp +++ b/src/main/webapp/WEB-INF/jsp/portal/custom-fields.jsp @@ -51,6 +51,9 @@

${field.label}

+ +

+

:

From d30f3cae9ea342419ac07452ddb47203b03bc68c Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:22:22 -0400 Subject: [PATCH 08/22] Add noindex header --- .../presentation/controller/PageServlet.java | 5 +++ .../controller/XMLPageFactory.java | 3 ++ .../WEB-INF/web-layouts/page/cms-layout.xml | 34 +++++++++---------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/simisinc/platform/presentation/controller/PageServlet.java b/src/main/java/com/simisinc/platform/presentation/controller/PageServlet.java index f34c8c06..92359471 100644 --- a/src/main/java/com/simisinc/platform/presentation/controller/PageServlet.java +++ b/src/main/java/com/simisinc/platform/presentation/controller/PageServlet.java @@ -475,6 +475,11 @@ public void service(HttpServletRequest request, HttpServletResponse response) { pageRef = WebPageXmlLayoutCommand.retrievePage("_new_install_"); } + // Set the X-Robots-Tag header if the page is not found or it's marked as noindex for html or other content types + if (pageRef == null || pageRef.isNoIndex() || (webPage != null && !webPage.getSearchable())) { + response.setHeader("X-Robots-Tag", "noindex"); + } + // Still no page? show an error if (pageRef == null) { LOG.error("PAGE NOT FOUND: " + pageRequest.getPagePath() + " " + pageRequest.getRemoteAddr()); diff --git a/src/main/java/com/simisinc/platform/presentation/controller/XMLPageFactory.java b/src/main/java/com/simisinc/platform/presentation/controller/XMLPageFactory.java index b6ffe258..9466c743 100644 --- a/src/main/java/com/simisinc/platform/presentation/controller/XMLPageFactory.java +++ b/src/main/java/com/simisinc/platform/presentation/controller/XMLPageFactory.java @@ -143,6 +143,9 @@ public static Page parsePageDocument(Document document, Element e, Map - +
@@ -36,7 +36,7 @@ - +
@@ -73,7 +73,7 @@ - +
@@ -107,7 +107,7 @@
- +
@@ -117,7 +117,7 @@
- +
@@ -160,7 +160,7 @@
- +
@@ -178,7 +178,7 @@
- +
@@ -190,7 +190,7 @@
- +
@@ -220,28 +220,28 @@ - +
- +
- +
- +
@@ -250,19 +250,19 @@ - + - + - + - + - + From 09990fdf97d5e742cff913f7b718eecf4632b412 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:24:08 -0400 Subject: [PATCH 09/22] Adds a unique id for mailing list records --- .../GenerateMailingListUniqueIdCommand.java | 57 +++++++++++++++++++ .../mailinglists/SaveMailingListCommand.java | 27 +++++++-- .../mailinglists/MailingListRepository.java | 18 +++++- 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/simisinc/platform/application/mailinglists/GenerateMailingListUniqueIdCommand.java diff --git a/src/main/java/com/simisinc/platform/application/mailinglists/GenerateMailingListUniqueIdCommand.java b/src/main/java/com/simisinc/platform/application/mailinglists/GenerateMailingListUniqueIdCommand.java new file mode 100644 index 00000000..04f8a036 --- /dev/null +++ b/src/main/java/com/simisinc/platform/application/mailinglists/GenerateMailingListUniqueIdCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright 2026 Matt Rajkowski (https://github.com/rajkowski) + * Copyright 2022 SimIS Inc. (https://www.simiscms.com) + * + * Licensed 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. + */ + +package com.simisinc.platform.application.mailinglists; + +import org.apache.commons.lang3.StringUtils; + +import com.simisinc.platform.application.cms.MakeContentUniqueIdCommand; +import com.simisinc.platform.domain.model.mailinglists.MailingList; +import com.simisinc.platform.infrastructure.persistence.mailinglists.MailingListRepository; + +/** + * Generates a plain text string - a uniqueId for URLs and referencing + * + * @author matt rajkowski + * @created 4/19/18 2:47 PM + */ +public class GenerateMailingListUniqueIdCommand { + + public static String generateUniqueId(MailingList previousItem, MailingList item) { + + // Use an existing uniqueId + if (previousItem != null && StringUtils.isNotBlank(previousItem.getUniqueId())) { + // See if the name changed + if (previousItem.getName().equals(item.getName())) { + return previousItem.getUniqueId(); + } + } + + // Create a new one + String value = MakeContentUniqueIdCommand.parseToValidValue(item.getName()); + + // Find the next available unique instance + int count = 1; + String uniqueId = value; + while (MailingListRepository.findByUniqueId(uniqueId) != null) { + ++count; + uniqueId = value + "-" + count; + } + return uniqueId; + } + +} diff --git a/src/main/java/com/simisinc/platform/application/mailinglists/SaveMailingListCommand.java b/src/main/java/com/simisinc/platform/application/mailinglists/SaveMailingListCommand.java index 06e8aee7..a86ad9ad 100644 --- a/src/main/java/com/simisinc/platform/application/mailinglists/SaveMailingListCommand.java +++ b/src/main/java/com/simisinc/platform/application/mailinglists/SaveMailingListCommand.java @@ -1,4 +1,5 @@ /* + * Copyright 2026 Matt Rajkowski (https://github.com/rajkowski) * Copyright 2022 SimIS Inc. (https://www.simiscms.com) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,13 +17,14 @@ package com.simisinc.platform.application.mailinglists; -import com.simisinc.platform.application.DataException; -import com.simisinc.platform.domain.model.mailinglists.MailingList; -import com.simisinc.platform.infrastructure.persistence.mailinglists.MailingListRepository; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import com.simisinc.platform.application.DataException; +import com.simisinc.platform.domain.model.mailinglists.MailingList; +import com.simisinc.platform.infrastructure.persistence.mailinglists.MailingListRepository; + /** * Validates and saves a mailing list object * @@ -33,6 +35,9 @@ public class SaveMailingListCommand { private static Log LOG = LogFactory.getLog(SaveMailingListCommand.class); + private static final int MAX_NAME_LENGTH = 200; + private static final int MAX_TITLE_LENGTH = 200; + public static MailingList saveMailingList(MailingList mailingListBean) throws DataException { // Validate the required fields @@ -41,7 +46,19 @@ public static MailingList saveMailingList(MailingList mailingListBean) throws Da errorMessages.append("A name is required"); } - if (errorMessages.length() > 0) { + if (StringUtils.isBlank(mailingListBean.getTitle())) { + errorMessages.append("A title is required"); + } + + if (StringUtils.trimToEmpty(mailingListBean.getName()).length() > MAX_NAME_LENGTH) { + errorMessages.append("The name cannot exceed " + MAX_NAME_LENGTH + " characters"); + } + + if (StringUtils.trimToEmpty(mailingListBean.getTitle()).length() > MAX_TITLE_LENGTH) { + errorMessages.append("The title cannot exceed " + MAX_TITLE_LENGTH + " characters"); + } + + if (!errorMessages.isEmpty()) { throw new DataException("Please check the form and try again:\n" + errorMessages.toString()); } @@ -60,6 +77,8 @@ public static MailingList saveMailingList(MailingList mailingListBean) throws Da } mailingList.setCreatedBy(mailingListBean.getCreatedBy()); mailingList.setModifiedBy(mailingListBean.getCreatedBy()); + // @note set the uniqueId before setting the name + mailingList.setUniqueId(GenerateMailingListUniqueIdCommand.generateUniqueId(mailingList, mailingListBean)); mailingList.setName(mailingListBean.getName()); mailingList.setTitle(mailingListBean.getTitle()); mailingList.setDescription(mailingListBean.getDescription()); diff --git a/src/main/java/com/simisinc/platform/infrastructure/persistence/mailinglists/MailingListRepository.java b/src/main/java/com/simisinc/platform/infrastructure/persistence/mailinglists/MailingListRepository.java index 76245f13..5b15b9c8 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/persistence/mailinglists/MailingListRepository.java +++ b/src/main/java/com/simisinc/platform/infrastructure/persistence/mailinglists/MailingListRepository.java @@ -87,6 +87,16 @@ public static MailingList findById(long id) { .returnRecord(MailingListRepository::buildRecord); } + public static MailingList findByUniqueId(String uniqueId) { + if (StringUtils.isBlank(uniqueId)) { + return null; + } + return DB.SELECT("*") + .FROM(TABLE_NAME) + .WHERE("unique_id = ?", uniqueId) + .returnRecord(MailingListRepository::buildRecord); + } + public static MailingList findByName(String name) { if (StringUtils.isBlank(name)) { return null; @@ -123,6 +133,7 @@ public static MailingList save(MailingList record) { public static MailingList add(MailingList record) { long id = DB.INSERT().INTO(TABLE_NAME) .FIELD("list_order", record.getOrder()) + .FIELD("unique_id", StringUtils.trimToNull(record.getUniqueId())) .FIELD("name", record.getName().trim()) .FIELD("title", record.getTitle().trim()) .FIELD_UNLESS_NULL("description", record.getDescription()) @@ -144,14 +155,16 @@ public static MailingList add(MailingList record) { public static MailingList update(MailingList record) { Update update = DB.UPDATE(TABLE_NAME) .SET("list_order", record.getOrder()) + .SET("unique_id", StringUtils.trimToNull(record.getUniqueId())) .SET("name", StringUtils.trimToNull(record.getName())) .SET("title", StringUtils.trimToNull(record.getTitle())) .SET("description", StringUtils.trimToNull(record.getDescription())) .SET("show_online", record.getShowOnline()) .SET("enabled", record.getEnabled()) .SET("modified_by", record.getModifiedBy()) - .SET("modified", new Timestamp(System.currentTimeMillis())); - if (update.WHERE("list_id = ?", record.getId()).execute()) { + .SET("modified", new Timestamp(System.currentTimeMillis())) + .WHERE("list_id = ?", record.getId()); + if (update.execute().booleanValue()) { return record; } LOG.error("The update failed!"); @@ -191,6 +204,7 @@ private static MailingList buildRecord(ResultSet rs) { record.setLastEmailed(rs.getTimestamp("last_emailed")); record.setShowOnline(rs.getBoolean("show_online")); record.setEnabled(rs.getBoolean("enabled")); + record.setUniqueId(rs.getString("unique_id")); return record; } catch (SQLException se) { LOG.error("buildRecord", se); From a1674a128bf09a36126ea213871b5dcc37428cf0 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:24:25 -0400 Subject: [PATCH 10/22] Add noindex property --- .../simisinc/platform/presentation/controller/Page.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/simisinc/platform/presentation/controller/Page.java b/src/main/java/com/simisinc/platform/presentation/controller/Page.java index a9f7a374..4f8b76ec 100644 --- a/src/main/java/com/simisinc/platform/presentation/controller/Page.java +++ b/src/main/java/com/simisinc/platform/presentation/controller/Page.java @@ -37,6 +37,7 @@ public class Page implements Serializable { private String collectionUniqueId; private String itemUniqueId; private String cssClass = null; + private boolean noindex = false; private List
sections = new ArrayList
(); private List roles = new ArrayList(); @@ -138,4 +139,12 @@ public String getCssClass() { public void setCssClass(String cssClass) { this.cssClass = cssClass; } + + public boolean isNoIndex() { + return noindex; + } + + public void setNoIndex(boolean noindex) { + this.noindex = noindex; + } } From 28d5ed32c625c23c7d9adf1412446299fec2c743 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:24:56 -0400 Subject: [PATCH 11/22] Adds a unique id for mailing list records --- .../domain/model/mailinglists/MailingList.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/simisinc/platform/domain/model/mailinglists/MailingList.java b/src/main/java/com/simisinc/platform/domain/model/mailinglists/MailingList.java index 382ece23..021c5247 100644 --- a/src/main/java/com/simisinc/platform/domain/model/mailinglists/MailingList.java +++ b/src/main/java/com/simisinc/platform/domain/model/mailinglists/MailingList.java @@ -16,10 +16,10 @@ package com.simisinc.platform.domain.model.mailinglists; -import com.simisinc.platform.domain.model.Entity; - import java.sql.Timestamp; +import com.simisinc.platform.domain.model.Entity; + /** * Mailing Lists * @@ -31,6 +31,7 @@ public class MailingList extends Entity { private Long id = -1L; private int order = 100; + private String uniqueId = null; private String name = null; private String title = null; private String description = null; @@ -62,6 +63,14 @@ public void setOrder(int order) { this.order = order; } + public String getUniqueId() { + return uniqueId; + } + + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + public String getName() { return name; } From aefd02bbfe10b2641a95bfe9b18c544c047fd809 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 14:25:52 -0400 Subject: [PATCH 12/22] Adds a unique id for mailing list records --- src/main/webapp/WEB-INF/jsp/admin/mailing-lists.jsp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/WEB-INF/jsp/admin/mailing-lists.jsp b/src/main/webapp/WEB-INF/jsp/admin/mailing-lists.jsp index d0113cd7..3fca807b 100644 --- a/src/main/webapp/WEB-INF/jsp/admin/mailing-lists.jsp +++ b/src/main/webapp/WEB-INF/jsp/admin/mailing-lists.jsp @@ -30,6 +30,7 @@ Name + Unique Id Members Last Sent Visible? @@ -48,6 +49,7 @@ --%> + @@ -56,7 +58,7 @@ No - + Not configured @@ -76,7 +78,7 @@ - No mailing lists were found + No mailing lists were found From 1ce776f716721899887df8178795687e41697213 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 15:58:09 -0400 Subject: [PATCH 13/22] Updated url acceptance --- .../com/simisinc/platform/application/cms/UrlCommand.java | 5 ++--- .../com/zeroio/platform/application/cms/UrlCommandTest.java | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java b/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java index 81816bc9..0a1711a0 100644 --- a/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java +++ b/src/main/java/com/simisinc/platform/application/cms/UrlCommand.java @@ -46,10 +46,9 @@ public class UrlCommand { private static final Pattern SAFE_RETURN_PAGE = Pattern.compile("^/[A-Za-z0-9/?&=#%._~+,;-]*$"); public static String encode(String url) { - // Sanitize the URL first String sanitizedUrl = sanitizeUrl(url); if (StringUtils.isBlank(sanitizedUrl)) { - LOG.debug("URL is not safe"); + LOG.debug("URL is not acceptable"); return "#"; } return sanitizedUrl; @@ -128,6 +127,6 @@ public static String sanitizeUrl(String url) { return null; } // No scheme: a site-relative path, anchor, or query - return value; + return null; } } diff --git a/src/test/java/com/zeroio/platform/application/cms/UrlCommandTest.java b/src/test/java/com/zeroio/platform/application/cms/UrlCommandTest.java index 42fe6da9..a9dca123 100644 --- a/src/test/java/com/zeroio/platform/application/cms/UrlCommandTest.java +++ b/src/test/java/com/zeroio/platform/application/cms/UrlCommandTest.java @@ -1,5 +1,6 @@ /* * Copyright 2026 Matt Rajkowski (https://github.com/rajkowski) + * Copyright 2022 SimIS Inc. (https://www.simiscms.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +33,7 @@ void encode() { Assertions.assertEquals("http://example.com?name=value", UrlCommand.encode("http://example.com?name=value")); Assertions.assertEquals("http://example.com?name=value&name1=value1", UrlCommand.encode("http://example.com?name=value&name1=value1")); - Assertions.assertEquals("#", UrlCommand.encode("http://example.com ")); + Assertions.assertEquals("http://example.com", UrlCommand.encode("http://example.com ")); Assertions.assertEquals("#", UrlCommand.encode("something")); } From 1542bd7beef5a8b4f4f36a38a99686d4bafcecc8 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 16:16:59 -0400 Subject: [PATCH 14/22] Change to redirect to the new item after adding it --- .../widgets/items/CreateAnItemWidget.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/simisinc/platform/presentation/widgets/items/CreateAnItemWidget.java b/src/main/java/com/simisinc/platform/presentation/widgets/items/CreateAnItemWidget.java index b73731e4..c493f7e7 100644 --- a/src/main/java/com/simisinc/platform/presentation/widgets/items/CreateAnItemWidget.java +++ b/src/main/java/com/simisinc/platform/presentation/widgets/items/CreateAnItemWidget.java @@ -366,14 +366,18 @@ public WidgetContext post(WidgetContext context) throws InvocationTargetExceptio // Determine the page to return to String returnPage = context.getPreferences().getOrDefault("returnPage", UrlCommand.getValidReturnPage(context.getParameter("returnPage"))); - if (StringUtils.isBlank(returnPage)) { - returnPage = collection.createListingsLink(); - } + if (requiresApproval) { + if (StringUtils.isBlank(returnPage)) { + returnPage = collection.createListingsLink(); + } context.setSuccessMessage( "Thanks, the record was saved! We've notified an administrator to review your listing for approval."); } else { - context.setSuccessMessage("Thanks, the record was saved!"); + if (StringUtils.isBlank(returnPage)) { + returnPage = "/show/" + item.getUniqueId(); + } + context.setSuccessMessage("The record was saved!"); } context.setRedirect(returnPage); return context; From 4dfb7904e0eb90b56370b496d265a9ac709a282c Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 16:18:46 -0400 Subject: [PATCH 15/22] Updated to specify the DB columns --- .../infrastructure/persistence/cms/BlogPostRepository.java | 2 +- .../infrastructure/persistence/cms/WebPageRepository.java | 2 +- .../infrastructure/persistence/items/ItemRepository.java | 2 +- .../infrastructure/persistence/medicine/MedicineRepository.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/BlogPostRepository.java b/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/BlogPostRepository.java index c1f83320..a7207295 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/BlogPostRepository.java +++ b/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/BlogPostRepository.java @@ -91,7 +91,7 @@ private static void appendWhereClause(Select select, BlogPostSpecification speci } private static DataResult query(BlogPostSpecification specification, DataConstraints constraints) { - Select select = DB.SELECT().FROM(TABLE_NAME); + Select select = DB.SELECT("*").FROM(TABLE_NAME); appendWhereClause(select, specification); if (specification != null && StringUtils.isNotBlank(specification.getSearchTerm())) { String searchTerm = specification.getSearchTerm().trim(); diff --git a/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/WebPageRepository.java b/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/WebPageRepository.java index 363ca287..0d56d8c4 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/WebPageRepository.java +++ b/src/main/java/com/simisinc/platform/infrastructure/persistence/cms/WebPageRepository.java @@ -466,7 +466,7 @@ public static List findByContentUniqueId(String contentUniqueId) { return new ArrayList<>(); } // Query for pages where pageXml contains the uniqueId reference - return DB.SELECT().FROM(TABLE_NAME) + return DB.SELECT("*").FROM(TABLE_NAME) .WHERE("(page_xml LIKE ? OR draft_page_xml LIKE ?)", "%" + contentUniqueId + "%", "%" + contentUniqueId + "%") .returnList(WebPageRepository::buildRecord); } diff --git a/src/main/java/com/simisinc/platform/infrastructure/persistence/items/ItemRepository.java b/src/main/java/com/simisinc/platform/infrastructure/persistence/items/ItemRepository.java index bfcbfa47..e13221a5 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/persistence/items/ItemRepository.java +++ b/src/main/java/com/simisinc/platform/infrastructure/persistence/items/ItemRepository.java @@ -361,7 +361,7 @@ private static DataResult query(ItemSpecification specification, DataConst select.LEFT_JOIN("collections") .ON("(items.collection_id = collections.collection_id)"); if (specification.getId() != -1) { - select.WHERE("item_id = ?", specification.getId()); + select.AND("item_id = ?", specification.getId()); } if (specification.getExcludeId() != -1) { select.AND("item_id <> ?", specification.getExcludeId()); diff --git a/src/main/java/com/simisinc/platform/infrastructure/persistence/medicine/MedicineRepository.java b/src/main/java/com/simisinc/platform/infrastructure/persistence/medicine/MedicineRepository.java index 46b7d77b..410d2916 100644 --- a/src/main/java/com/simisinc/platform/infrastructure/persistence/medicine/MedicineRepository.java +++ b/src/main/java/com/simisinc/platform/infrastructure/persistence/medicine/MedicineRepository.java @@ -291,7 +291,7 @@ public static Medicine findById(long id) { if (id == -1) { return null; } - return DB.SELECT().FROM(TABLE_NAME) + return DB.SELECT("*").FROM(TABLE_NAME) .WHERE("medicine_id = ?", id) .returnRecord(MedicineRepository::buildRecord); } From 30000af60bc5d219d84fbc3da4eeeedc6450b031 Mon Sep 17 00:00:00 2001 From: Matt Rajkowski Date: Fri, 18 Sep 2026 16:19:50 -0400 Subject: [PATCH 16/22] Improve layout --- .../WEB-INF/web-templates/page/cms/Blog Post Article Page.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/WEB-INF/web-templates/page/cms/Blog Post Article Page.xml b/src/main/webapp/WEB-INF/web-templates/page/cms/Blog Post Article Page.xml index 45c59e37..68171bfe 100644 --- a/src/main/webapp/WEB-INF/web-templates/page/cms/Blog Post Article Page.xml +++ b/src/main/webapp/WEB-INF/web-templates/page/cms/Blog Post Article Page.xml @@ -16,7 +16,7 @@ -->