From 08528c4db8c1b687027ff9e51bd98e81a5fddcc9 Mon Sep 17 00:00:00 2001 From: "David M. Johnson" Date: Sat, 7 Nov 2026 15:01:57 -0500 Subject: [PATCH 1/4] Apply the Limited role to each entry in the entry editor (#235) * Apply the Limited role to each entry in the entry editor A Limited member can open, change and remove draft and pending entries only, as in AtomPub and XML-RPC. Other entries answer with the access denied page. * Check entry write access when the editor action is prepared The check now runs in myPrepare(), so it also covers the INPUT result after field validation fails. --- CHANGES.md | 3 + .../ui/struts2/editor/EntryEdit.java | 35 +++ .../ui/struts2/editor/EntryRemove.java | 22 +- .../editor/EntryWritePermissionTest.java | 214 ++++++++++++++++++ 4 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EntryWritePermissionTest.java diff --git a/CHANGES.md b/CHANGES.md index c63e2de41..17fe9ab67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -46,6 +46,9 @@ PDF or plain text. The media file editor sets a file's original path only when `mediafile.originalPathEdit.enabled=true`, as its form already shows. AtomPub media uploads no longer set one. +- **The entry editor applies the Limited role to each entry.** A Limited member + can open, change and remove draft and pending entries only, as in AtomPub and + XML-RPC. Other entries answer with the access denied page. - **Planet is off by default.** `planet.aggregator.enabled` now defaults to `false`. While it is off, the Planet admin pages, `/planetrss` and the Planet background tasks (`RefreshRollerPlanetTask`, `SyncWebsitesTask`) do diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java index 1d2f4e493..1c9ece2e8 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java @@ -80,6 +80,9 @@ public final class EntryEdit extends UIAction { // the entry we are adding or editing private WeblogEntry entry = null; + // set by myPrepare() when the user may not write the saved entry + private boolean writeDenied = false; + public EntryEdit() { this.desiredMenu = "editor"; } @@ -108,6 +111,13 @@ public void myPrepare() { WeblogEntryManager wmgr = WebloggerFactory.getWeblogger() .getWeblogEntryManager(); setEntry(wmgr.getWeblogEntry(getActionWeblog(), getBean().getId())); + // Checked here, not in the methods: when field validation + // fails, Struts returns INPUT without calling them, and the + // page shows the entry. + if (entry != null && !canWriteEntry()) { + writeDenied = true; + entry = null; + } } catch (WebloggerException ex) { log.error( "Error looking up entry by id - " + getBean().getId(), @@ -124,6 +134,9 @@ public void myPrepare() { @SkipValidation @Override public String execute() { + if (writeDenied) { + return DENIED; + } if (getActionName().equals("entryEdit")) { if (!requireEntry()) { return ERROR; @@ -152,6 +165,9 @@ public String execute() { * @return String The result of the action. */ public String saveDraft() { + if (writeDenied) { + return DENIED; + } if (!requireEntry()) { return INPUT; } @@ -170,6 +186,9 @@ public String saveDraft() { * @return String The result of the action. */ public String publish() { + if (writeDenied) { + return DENIED; + } if (!requireEntry()) { return INPUT; } @@ -618,8 +637,24 @@ private boolean requireEntry() { return true; } + /** + * The action lets LIMITED users in; a saved entry then decides what they + * may do with it. + */ + private boolean canWriteEntry() { + try { + return entry.hasWritePermissions(getAuthenticatedUser()); + } catch (WebloggerException ex) { + log.error("Error checking permissions on entry " + getBean().getId(), ex); + return false; + } + } + @SkipValidation public String firstSave() { + if (writeDenied) { + return DENIED; + } if (!requireEntry()) { return ERROR; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java index 25bb69edc..85471e030 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java @@ -55,6 +55,7 @@ public EntryRemove() { @Override public void myPrepare() { + setRemoveEntry(null); if (getRemoveId() != null) { try { WeblogEntryManager wmgr = WebloggerFactory.getWeblogger() @@ -68,11 +69,18 @@ public void myPrepare() { @Override public String execute() { + if (!canRemoveEntry()) { + return DENIED; + } return INPUT; } public String remove() { + if (!canRemoveEntry()) { + return DENIED; + } + if (getRemoveEntry() != null) { try { WeblogEntry entry = getRemoveEntry(); @@ -111,6 +119,18 @@ public String remove() { return INPUT; } + private boolean canRemoveEntry() { + if (getRemoveEntry() == null) { + return true; + } + try { + return getRemoveEntry().hasWritePermissions(getAuthenticatedUser()); + } catch (WebloggerException ex) { + log.error("Error checking permissions on entry " + getRemoveId(), ex); + return false; + } + } + public String getRemoveId() { return removeId; } @@ -127,7 +147,7 @@ public void setRemoveEntry(WeblogEntry removeEntry) { this.removeEntry = removeEntry; } - // allow LIMITED users to delete their own draft/pending blog entries + // lets LIMITED users in; canRemoveEntry() limits them to draft/pending entries @Override public List requiredWeblogPermissionActions() { return Collections.singletonList(WeblogPermission.EDIT_DRAFT); diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EntryWritePermissionTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EntryWritePermissionTest.java new file mode 100644 index 000000000..a029dc771 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EntryWritePermissionTest.java @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * 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 + * + * https://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 org.apache.roller.weblogger.ui.struts2.editor; + +import java.util.Collections; +import java.util.Locale; + +import org.apache.roller.weblogger.business.UserManager; +import org.apache.roller.weblogger.business.WeblogEntryManager; +import org.apache.roller.weblogger.business.Weblogger; +import org.apache.roller.weblogger.business.WebloggerFactory; +import org.apache.roller.weblogger.business.search.IndexManager; +import org.apache.roller.weblogger.pojos.User; +import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.roller.weblogger.pojos.WeblogCategory; +import org.apache.roller.weblogger.pojos.WeblogEntry; +import org.apache.roller.weblogger.pojos.WeblogEntry.PubStatus; +import org.apache.roller.weblogger.pojos.WeblogPermission; +import org.apache.roller.weblogger.ui.struts2.util.UIAction; +import org.apache.roller.weblogger.util.cache.CacheManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The entry actions admit a Limited member, so each one has to ask the entry + * whether that member may work on it. + */ +class EntryWritePermissionTest { + + private MockedStatic factory; + private MockedStatic cache; + private WeblogEntryManager entries; + private UserManager users; + private Weblog weblog; + private User user; + + @BeforeEach + void setUp() throws Exception { + entries = mock(WeblogEntryManager.class); + users = mock(UserManager.class); + Weblogger weblogger = mock(Weblogger.class); + when(weblogger.getWeblogEntryManager()).thenReturn(entries); + when(weblogger.getUserManager()).thenReturn(users); + when(weblogger.getIndexManager()).thenReturn(mock(IndexManager.class)); + factory = mockStatic(WebloggerFactory.class); + factory.when(WebloggerFactory::getWeblogger).thenReturn(weblogger); + cache = mockStatic(CacheManager.class); + + weblog = mock(Weblog.class); + user = mock(User.class); + when(user.getUserName()).thenReturn("member"); + } + + @AfterEach + void tearDown() { + cache.close(); + factory.close(); + } + + @Test + void limitedMemberCannotRemoveALiveEntry() throws Exception { + memberHas(WeblogPermission.EDIT_DRAFT); + for (PubStatus status : new PubStatus[]{PubStatus.PUBLISHED, PubStatus.SCHEDULED}) { + EntryRemove action = removeAction(entry(status)); + + assertEquals(UIAction.DENIED, action.execute()); + assertEquals(UIAction.DENIED, action.remove()); + } + verify(entries, never()).removeWeblogEntry(any()); + } + + @Test + void limitedMemberCanRemoveADraftOrPendingEntry() throws Exception { + memberHas(WeblogPermission.EDIT_DRAFT); + for (PubStatus status : new PubStatus[]{PubStatus.DRAFT, PubStatus.PENDING}) { + assertEquals(UIAction.SUCCESS, removeAction(entry(status)).remove()); + } + verify(entries, times(2)).removeWeblogEntry(any()); + } + + @Test + void authorCanRemoveAPublishedEntry() throws Exception { + memberHas(WeblogPermission.POST); + WeblogEntry entry = entry(PubStatus.PUBLISHED); + + assertEquals(UIAction.SUCCESS, removeAction(entry).remove()); + verify(entries).removeWeblogEntry(entry); + } + + @Test + void removeLooksTheEntryUpAgainWhateverWasPosted() throws Exception { + memberHas(WeblogPermission.EDIT_DRAFT); + EntryRemove action = action(new EntryRemove()); + action.setRemoveEntry(entry(PubStatus.DRAFT)); + + action.myPrepare(); + + assertEquals(UIAction.ERROR, action.remove()); + verify(entries, never()).removeWeblogEntry(any()); + } + + @Test + void limitedMemberCannotOpenOrSaveAPublishedEntry() throws Exception { + memberHas(WeblogPermission.EDIT_DRAFT); + WeblogEntry entry = entry(PubStatus.PUBLISHED); + + assertEquals(UIAction.DENIED, editAction(entry).execute()); + assertEquals(UIAction.DENIED, editAction(entry).firstSave()); + assertEquals(UIAction.DENIED, editAction(entry).saveDraft()); + assertEquals(UIAction.DENIED, editAction(entry).publish()); + + assertEquals(PubStatus.PUBLISHED, entry.getStatus()); + verify(entries, never()).saveWeblogEntry(any()); + } + + @Test + void failedValidationShowsNoEntryToALimitedMember() throws Exception { + // Struts returns INPUT without calling saveDraft() or publish() when + // a field fails validation; the page then shows getEntry(). + memberHas(WeblogPermission.EDIT_DRAFT); + + assertNull(editAction(entry(PubStatus.PUBLISHED)).getEntry()); + assertNull(editAction(entry(PubStatus.SCHEDULED)).getEntry()); + } + + @Test + void limitedMemberCanOpenADraft() throws Exception { + memberHas(WeblogPermission.EDIT_DRAFT); + + assertEquals(UIAction.INPUT, editAction(entry(PubStatus.DRAFT)).execute()); + } + + @Test + void authorCanOpenAPublishedEntry() throws Exception { + memberHas(WeblogPermission.POST); + + assertEquals(UIAction.INPUT, editAction(entry(PubStatus.PUBLISHED)).execute()); + } + + private void memberHas(String action) throws Exception { + WeblogPermission permission = new WeblogPermission( + weblog, user, Collections.singletonList(action)); + when(users.getWeblogPermission(weblog, user)).thenReturn(permission); + } + + private WeblogEntry entry(PubStatus status) throws Exception { + WeblogEntry entry = new WeblogEntry(); + entry.setId("entry-1"); + entry.setTitle("title"); + entry.setWebsite(weblog); + entry.setStatus(status); + entry.setCategory(mock(WeblogCategory.class)); + when(entries.getWeblogEntry(weblog, "entry-1")).thenReturn(entry); + return entry; + } + + private EntryRemove removeAction(WeblogEntry entry) { + EntryRemove action = action(new EntryRemove()); + action.setRemoveId(entry.getId()); + action.myPrepare(); + return action; + } + + private EntryEdit editAction(WeblogEntry entry) { + EntryEdit action = action(new EntryEdit()); + action.setActionName("entryEdit"); + action.getBean().setId(entry.getId()); + doReturn(Locale.ENGLISH).when(action).getLocale(); + action.myPrepare(); + return action; + } + + private T action(T instance) { + T action = spy(instance); + doAnswer(call -> call.getArgument(0)).when(action).getText(anyString()); + doAnswer(call -> call.getArgument(0)).when(action).getText(anyString(), anyList()); + doAnswer(call -> call.getArgument(0)).when(action) + .getText(anyString(), anyString(), anyString()); + action.setActionWeblog(weblog); + action.setAuthenticatedUser(user); + return action; + } +} From c7a12d0b013bee8b9655759aae5b53b31c453c1a Mon Sep 17 00:00:00 2001 From: "David M. Johnson" Date: Sat, 7 Nov 2026 15:17:22 -0500 Subject: [PATCH 2/4] Send weblog update pings without an XML-RPC client (#236) * Send weblog update pings without an XML-RPC client Roller now builds the weblogUpdates.ping request and reads the response itself, parsing it with the project's hardened XML parser (SecureXmlParsers) and reading only the flerror and message fields the protocol defines. Adds connection and read timeouts and a 1 MiB cap on the response. Removes the now-unused xmlrpc-client dependency; xmlrpc-common/server remain for the inbound XML-RPC servlet. Adds WeblogUpdatePingerTest. * Treat non-2xx ping responses and non-HTTP targets as failures A non-2xx HTTP status from a ping target is now surfaced as an IOException instead of being read and parsed as a (successful, empty) ping result, so a failing target is treated as failed and retried, as the previous client did. A ping target URL that is not HTTP(S) now yields an IOException instead of an uncaught ClassCastException. Adds tests for both via a loopback HTTP server; makes exchange() package-private for the tests. * Load the Pings action's ping target only by id The ping target that Ping Now, enable and disable work on now always comes from the stored ping targets. Add a test that binds request parameters through Struts. * Load editor action targets only by id, never from request parameters The editor and ping-target actions load the domain object they work on (ping target, category, entry, media directory, template, folder) from the weblog, by id, in myPrepare. Make those setters non-public and clear the field before the lookup, so a request parameter cannot supply a different object. Also set the authenticated user and the action weblog in UIActionInterceptor unconditionally, so a value bound from a request parameter is always overwritten from the session and the weblog handle. Add binding tests for the action targets and for the interceptor. * Let same-package tests set the entry on EntryRemove --- CHANGES.md | 6 + app/pom.xml | 6 - .../business/pings/WeblogUpdatePinger.java | 284 ++++++++++++++++-- .../ui/struts2/admin/PingTargets.java | 5 +- .../ui/struts2/editor/Bookmarks.java | 2 +- .../ui/struts2/editor/CategoryRemove.java | 4 +- .../ui/struts2/editor/EntryEdit.java | 3 +- .../ui/struts2/editor/EntryRemove.java | 6 +- .../ui/struts2/editor/MediaFileAdd.java | 4 +- .../ui/struts2/editor/MediaFileEdit.java | 5 +- .../struts2/editor/MediaFileImageChooser.java | 4 +- .../ui/struts2/editor/MediaFileView.java | 5 +- .../weblogger/ui/struts2/editor/Pings.java | 5 +- .../ui/struts2/editor/StylesheetEdit.java | 2 +- .../ui/struts2/editor/TemplateEdit.java | 2 +- .../ui/struts2/util/UIActionInterceptor.java | 17 +- .../pings/WeblogUpdatePingerTest.java | 224 ++++++++++++++ .../editor/EditorActionTargetBindingTest.java | 117 ++++++++ .../editor/PingsTargetBindingTest.java | 107 +++++++ .../struts2/util/UIActionInterceptorTest.java | 78 +++++ 20 files changed, 832 insertions(+), 54 deletions(-) create mode 100644 app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java create mode 100644 app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java create mode 100644 app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java create mode 100644 app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptorTest.java diff --git a/CHANGES.md b/CHANGES.md index 17fe9ab67..ae054e20d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,12 @@ ### Behaviour changes worth reading before upgrading +- **Weblog update pings no longer use the XML-RPC client library.** Roller + builds the `weblogUpdates.ping` request and reads the response itself, with a + connection timeout, a read timeout and a 1 MB cap on the response, and reads + only the `flerror` and `message` fields the protocol defines. Ping behaviour + is otherwise unchanged. + - **AtomPub honours `webservices.enableAtomPub` on every request.** While the setting is off, every AtomPub URL answers 404, not only the service document. - **AtomPub entry bodies default to a 1 MiB limit.** A larger entry is refused diff --git a/app/pom.xml b/app/pom.xml index 59e10851b..cb54682e5 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -388,12 +388,6 @@ limitations under the License. ${xmlrpc-version} - - org.apache.xmlrpc - xmlrpc-client - ${xmlrpc-version} - - org.apache.xmlrpc xmlrpc-server diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java index 49d0e22f9..98919792a 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java @@ -23,15 +23,27 @@ import org.apache.roller.weblogger.config.PingConfig; import org.apache.roller.weblogger.pojos.PingTarget; import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.roller.weblogger.util.SecureXmlParsers; import org.apache.xmlrpc.XmlRpcException; -import org.apache.xmlrpc.client.XmlRpcClient; -import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; +import javax.xml.parsers.SAXParser; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -39,11 +51,16 @@ /** * Utility for sending a weblog update ping. * - * This implements the WeblogUpdates.ping XML-RPC call described at - * www.xmlrpc.com - * as well as some variants required to interoperate with certain - * buggy but popular ping targets. + *

This implements the weblogUpdates.ping XML-RPC call described + * at www.xmlrpc.com, as well as + * some variants required to interoperate with certain buggy but popular ping + * targets. * + *

Roller builds the request and reads the response itself rather than through + * an XML-RPC client library. The response is parsed with {@link SecureXmlParsers}, + * which refuses document type declarations and resolves nothing the document + * names, and only the flerror and message fields the + * protocol defines are read; any other content is ignored. * * @author Anil Gangolli * @author llavandowska (for code refactored from the now-defunct RollerXmlRpcClient) @@ -51,6 +68,17 @@ public final class WeblogUpdatePinger { public static final Log LOGGER = LogFactory.getLog(WeblogUpdatePinger.class); + private static final String PING_METHOD_NAME = "weblogUpdates.ping"; + + /** How long to wait to open a connection to a ping target, in milliseconds. */ + private static final int CONNECT_TIMEOUT_MS = 15000; + + /** How long to wait for a ping target's response, in milliseconds. */ + private static final int READ_TIMEOUT_MS = 30000; + + /** Upper bound on the response we will read from a ping target, in bytes. */ + private static final int MAX_RESPONSE_BYTES = 1024 * 1024; + /** * Conveys a ping result. */ @@ -96,7 +124,7 @@ private WeblogUpdatePinger() { * @param website the website that changed (from which the ping originates) * @return the result message string sent by the server. * @throws IOException if an IOException occurs during the ping - * @throws XmlRpcException if the XML RPC client throws one + * @throws XmlRpcException if the ping target reports a fault */ public static PingResult sendPing(PingTarget pingTarget, Weblog website) throws IOException, XmlRpcException { String websiteUrl = website.getAbsoluteURL(); @@ -114,12 +142,9 @@ public static PingResult sendPing(PingTarget pingTarget, Weblog website) throws LOGGER.debug("Executing ping to '" + pingTargetUrl + "' for website '" + websiteUrl + "' (" + website.getName() + ")" + (variantOptions.isEmpty() ? "" : " with variant options " + variantOptions)); } - // Send the ping. - XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); - config.setServerURL(new URL(pingTargetUrl)); - XmlRpcClient client = new XmlRpcClient(); - client.setConfig(config); - PingResult pingResult = parseResult(client.execute("weblogUpdates.ping", params.toArray())); + // Send the ping and read the response. + byte[] response = exchange(pingTargetUrl, buildRequest(params)); + PingResult pingResult = parseResponse(response); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Ping result is: " + pingResult); @@ -127,29 +152,236 @@ public static PingResult sendPing(PingTarget pingTarget, Weblog website) throws return pingResult; } - private static PingResult parseResult(Object obj) { - // Deal with the fact that some buggy ping targets may not respond with the proper struct type. - if (obj == null) { - return new PingResult(null,null); + /** + * Build the XML-RPC weblogUpdates.ping request body for the + * given string parameters. + */ + static String buildRequest(List params) { + StringBuilder sb = new StringBuilder(256); + sb.append(""); + sb.append("").append(PING_METHOD_NAME).append(""); + sb.append(""); + for (String param : params) { + sb.append(""); + xmlEscape(sb, param); + sb.append(""); + } + sb.append(""); + return sb.toString(); + } + + /** + * POST the request body to the ping target and return the response bytes, + * capped at {@link #MAX_RESPONSE_BYTES}. + */ + static byte[] exchange(String pingTargetUrl, String requestBody) throws IOException { + URL url = new URL(pingTargetUrl); + URLConnection connection = url.openConnection(); + if (!(connection instanceof HttpURLConnection)) { + // Ping targets are HTTP(S) XML-RPC endpoints. Refuse anything else + // (for example file: or ftp:) with an IOException rather than failing + // with a ClassCastException that callers do not handle. + throw new IOException("Ping target URL is not an HTTP URL: " + pingTargetUrl); } + HttpURLConnection conn = (HttpURLConnection) connection; try { - // normal case: response is a struct (represented as a Map) with Boolean flerror and String fields. - Map result = (Map) obj; - return new PingResult((Boolean) result.get("flerror"), (String) result.get("message")); - } catch (Exception ex) { - // exception case: The caller responded with an unexpected type, though parsed at the basic XML RPC level. - // This effectively assumes flerror = false, and sets message = obj.toString(); + conn.setRequestMethod("POST"); + conn.setConnectTimeout(CONNECT_TIMEOUT_MS); + conn.setReadTimeout(READ_TIMEOUT_MS); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8"); + conn.setRequestProperty("User-Agent", "Apache Roller"); + + byte[] body = requestBody.getBytes(StandardCharsets.UTF_8); + conn.setFixedLengthStreamingMode(body.length); + try (OutputStream out = conn.getOutputStream()) { + out.write(body); + } + + int status = conn.getResponseCode(); + if (status < 200 || status >= 300) { + // A non-2xx status is a transport-level failure, not a ping + // result. Surface it so the ping is treated as failed (and + // retried by the queue processor), as the old client did. + throw new IOException("Ping target returned HTTP status " + status); + } + return readCapped(conn.getInputStream()); + } finally { + conn.disconnect(); + } + } + + /** Read at most {@link #MAX_RESPONSE_BYTES} bytes from the stream. */ + private static byte[] readCapped(InputStream in) throws IOException { + if (in == null) { + return new byte[0]; + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] chunk = new byte[8192]; + int total = 0; + int read; + while ((read = in.read(chunk)) != -1) { + total += read; + if (total > MAX_RESPONSE_BYTES) { + throw new IOException("Ping target response exceeded " + MAX_RESPONSE_BYTES + " bytes"); + } + buffer.write(chunk, 0, read); + } + return buffer.toByteArray(); + } + + /** + * Parse an XML-RPC ping response. Only flerror and + * message are read from a normal response; a fault is turned + * into an {@link XmlRpcException}. + * + * @throws XmlRpcException if the response is a fault + */ + static PingResult parseResponse(byte[] response) throws IOException, XmlRpcException { + if (response == null || response.length == 0) { + return new PingResult(null, null); + } + PingResponseHandler handler = new PingResponseHandler(); + try { + SAXParser parser = SecureXmlParsers.newSAXParserFactory().newSAXParser(); + parser.parse(new InputSource(new ByteArrayInputStream(response)), handler); + } catch (SAXException | javax.xml.parsers.ParserConfigurationException ex) { + // The target responded with something that is not a well-formed, safe + // XML-RPC response. Treat it as a non-error transmission, mirroring the + // prior lenient handling of buggy targets, without exposing the body. if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Invalid ping result of type: " + obj.getClass().getName() + "; proceeding with stand-in representative."); + LOGGER.debug("Could not parse ping response as XML-RPC; proceeding.", ex); + } + return new PingResult(null, null); + } + + if (handler.isFault()) { + throw new XmlRpcException(handler.getFaultCode(), handler.getFaultString()); + } + return new PingResult(handler.getFlerror(), handler.getMessage()); + } + + private static void xmlEscape(StringBuilder sb, String value) { + if (value == null) { + return; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + switch (c) { + case '&': sb.append("&"); break; + case '<': sb.append("<"); break; + case '>': sb.append(">"); break; + case '"': sb.append("""); break; + case '\'': sb.append("'"); break; + default: sb.append(c); + } + } + } + + /** + * Collects just the fields Roller needs from an XML-RPC ping response. + * Scalar values, including any {@code }, are read only as text. + */ + private static final class PingResponseHandler extends DefaultHandler { + private final StringBuilder text = new StringBuilder(); + private final Map members = new HashMap<>(); + private boolean inFault; + private boolean inStruct; + private String memberName; + private String scalar; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) { + text.setLength(0); + if ("fault".equals(qName)) { + inFault = true; + } else if ("struct".equals(qName)) { + inStruct = true; + } else if ("member".equals(qName)) { + memberName = null; + scalar = null; } - return new PingResult(null,obj.toString()); + } + + @Override + public void characters(char[] ch, int start, int length) { + text.append(ch, start, length); + } + + @Override + public void endElement(String uri, String localName, String qName) { + switch (qName) { + case "name": + memberName = text.toString().trim(); + break; + case "string": + case "boolean": + case "int": + case "i4": + case "double": + case "dateTime.iso8601": + case "base64": + // Record the raw text only; base64 is not decoded. + scalar = text.toString(); + break; + case "value": + if (scalar == null) { + // An XML-RPC with no type child is a string. + scalar = text.toString(); + } + break; + case "member": + if (memberName != null && inStruct) { + members.put(memberName, scalar != null ? scalar : ""); + } + scalar = null; + break; + case "struct": + inStruct = false; + break; + default: + break; + } + } + + boolean isFault() { + return inFault; + } + + int getFaultCode() { + String code = members.get("faultCode"); + if (code != null) { + try { + return Integer.parseInt(code.trim()); + } catch (NumberFormatException ignored) { + // fall through to default + } + } + return 0; + } + + String getFaultString() { + return members.get("faultString"); + } + + Boolean getFlerror() { + String value = members.get("flerror"); + if (value == null) { + return null; + } + value = value.trim(); + return "1".equals(value) || "true".equalsIgnoreCase(value); + } + + String getMessage() { + return members.get("message"); } } /** * Decide if the given exception appears to warrant later retrial attempts. * - * @param ex an exception thrown by the sendPing operation + * @param ex an exception thrown by the sendPing operation * @return true if the error warrants retrial */ public static boolean shouldRetry(Exception ex) { diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java index ff36eb3b7..363e7c549 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java @@ -77,6 +77,9 @@ public void myPrepare() { // load list of ping targets loadPingTargets(); + // The ping target always comes from the stored targets, by id. + pingTarget = null; + // load specified ping target if possible if(!StringUtils.isEmpty(getPingTargetId())) { try { @@ -195,7 +198,7 @@ public PingTarget getPingTarget() { return pingTarget; } - public void setPingTarget(PingTarget pingTarget) { + private void setPingTarget(PingTarget pingTarget) { this.pingTarget = pingTarget; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Bookmarks.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Bookmarks.java index df499162e..d1ef2399f 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Bookmarks.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Bookmarks.java @@ -301,7 +301,7 @@ public WeblogBookmarkFolder getFolder() { return folder; } - public void setFolder(WeblogBookmarkFolder folder) { + private void setFolder(WeblogBookmarkFolder folder) { this.folder = folder; if ( folder != null ) { this.folderId = folder.getId(); diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/CategoryRemove.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/CategoryRemove.java index 0dbd8dc68..ad3cb711f 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/CategoryRemove.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/CategoryRemove.java @@ -72,6 +72,8 @@ public List requiredWeblogPermissionActions() { public void myPrepare() { try { WeblogEntryManager wmgr = WebloggerFactory.getWeblogger().getWeblogEntryManager(); + // The category always comes from the weblog, by id. + category = null; if(!StringUtils.isEmpty(getRemoveId())) { setCategory(wmgr.getWeblogCategory(getActionWeblog(), getRemoveId())); } @@ -159,7 +161,7 @@ public WeblogCategory getCategory() { return category; } - public void setCategory(WeblogCategory category) { + private void setCategory(WeblogCategory category) { this.category = category; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java index 1c9ece2e8..57b1a7f51 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java @@ -625,7 +625,8 @@ public WeblogEntry getEntry() { return entry; } - public void setEntry(WeblogEntry entry) { + // package-private: set by the action only, never bound from a request + void setEntry(WeblogEntry entry) { this.entry = entry; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java index 85471e030..80bf288bf 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryRemove.java @@ -55,7 +55,8 @@ public EntryRemove() { @Override public void myPrepare() { - setRemoveEntry(null); + // The entry always comes from the weblog, by id. + removeEntry = null; if (getRemoveId() != null) { try { WeblogEntryManager wmgr = WebloggerFactory.getWeblogger() @@ -143,7 +144,8 @@ public WeblogEntry getRemoveEntry() { return removeEntry; } - public void setRemoveEntry(WeblogEntry removeEntry) { + // package-private: set by the action only, never bound from a request + void setRemoveEntry(WeblogEntry removeEntry) { this.removeEntry = removeEntry; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileAdd.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileAdd.java index 9c9fdfcf0..f0aeee6ed 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileAdd.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileAdd.java @@ -82,6 +82,8 @@ public void myPrepare() { try { MediaFileManager mgr = WebloggerFactory.getWeblogger() .getMediaFileManager(); + // The directory always comes from the weblog, by id or name. + directory = null; if (!StringUtils.isEmpty(bean.getDirectoryId())) { setDirectory(mgr.getMediaFileDirectory(getActionWeblog(), bean.getDirectoryId())); @@ -260,7 +262,7 @@ public MediaFileDirectory getDirectory() { return directory; } - public void setDirectory(MediaFileDirectory directory) { + private void setDirectory(MediaFileDirectory directory) { this.directory = directory; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileEdit.java index 789ba2a6d..76afc4ceb 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileEdit.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileEdit.java @@ -69,6 +69,8 @@ public void myPrepare() { refreshAllDirectories(); try { MediaFileManager mgr = WebloggerFactory.getWeblogger().getMediaFileManager(); + // The directory always comes from the weblog, by id. + directory = null; if (!StringUtils.isEmpty(bean.getDirectoryId())) { setDirectory(mgr.getMediaFileDirectory(getActionWeblog(), bean.getDirectoryId())); } @@ -187,7 +189,8 @@ public MediaFileDirectory getDirectory() { return directory; } - public void setDirectory(MediaFileDirectory directory) { + // package-private: set by the action only, never bound from a request + void setDirectory(MediaFileDirectory directory) { this.directory = directory; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileImageChooser.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileImageChooser.java index c16f0a78e..a80e01b50 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileImageChooser.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileImageChooser.java @@ -62,6 +62,8 @@ public MediaFileImageChooser() { @Override public void myPrepare() { refreshAllDirectories(); + // The current directory is set from the weblog, by id. + currentDirectory = null; } @Override @@ -196,7 +198,7 @@ public MediaFileDirectory getCurrentDirectory() { * @param currentDirectory * the currentDirectory to set */ - public void setCurrentDirectory(MediaFileDirectory currentDirectory) { + private void setCurrentDirectory(MediaFileDirectory currentDirectory) { this.currentDirectory = currentDirectory; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java index 32b83dc34..7f3dfc212 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/MediaFileView.java @@ -125,6 +125,9 @@ public void myPrepare() { } refreshAllDirectories(); + + // The current directory is set by view(), from the weblog, by id. + currentDirectory = null; } /** @@ -400,7 +403,7 @@ public MediaFileDirectory getCurrentDirectory() { return currentDirectory; } - public void setCurrentDirectory(MediaFileDirectory currentDirectory) { + private void setCurrentDirectory(MediaFileDirectory currentDirectory) { this.currentDirectory = currentDirectory; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java index f2c07e16a..d6ea88678 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java @@ -80,6 +80,9 @@ public void myPrepare() { PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); + // The ping target always comes from the stored ping targets, by id. + pingTarget = null; + // load selected ping target, if possible if(getPingTargetId() != null) { try { @@ -250,7 +253,7 @@ public PingTarget getPingTarget() { return pingTarget; } - public void setPingTarget(PingTarget pingTarget) { + private void setPingTarget(PingTarget pingTarget) { this.pingTarget = pingTarget; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java index cb50e00bd..049d28a58 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/StylesheetEdit.java @@ -366,7 +366,7 @@ public WeblogTemplate getTemplate() { * Sets the template. * @param template the new template */ - public void setTemplate(WeblogTemplate template) { + private void setTemplate(WeblogTemplate template) { this.template = template; } diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/TemplateEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/TemplateEdit.java index a1f95cebb..fea372e0d 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/TemplateEdit.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/TemplateEdit.java @@ -196,7 +196,7 @@ public WeblogTemplate getTemplate() { return template; } - public void setTemplate(WeblogTemplate template) { + private void setTemplate(WeblogTemplate template) { this.template = template; } } \ No newline at end of file diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptor.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptor.java index 08b65f4bb..9eb500957 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptor.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptor.java @@ -63,27 +63,26 @@ public String doIntercept(ActionInvocation invocation) throws Exception { UIAction theAction = (UIAction) action; - // extract the authenticated user and set it + // Set the authenticated user from the session. Always overwrite, + // so a value bound from request parameters cannot survive. RollerSession rses = RollerSession.getRollerSession(request); - if (rses != null) { - theAction.setAuthenticatedUser(rses.getAuthenticatedUser()); - } + theAction.setAuthenticatedUser(rses != null ? rses.getAuthenticatedUser() : null); - // extract the work weblog and set it + // Set the action weblog by looking up the weblog handle. Always + // overwrite (to null when no weblog resolves), so a value bound + // from request parameters cannot survive. + Weblog weblog = null; String weblogHandle = theAction.getWeblog(); if (!StringUtils.isEmpty(weblogHandle)) { - Weblog weblog = null; try { weblog = WebloggerFactory.getWeblogger().getWeblogManager() .getWeblogByHandle(weblogHandle); - if (weblog != null) { - theAction.setActionWeblog(weblog); - } } catch (Exception e) { log.error("Error looking up action weblog - " + weblogHandle, e); } } + theAction.setActionWeblog(weblog); } return invocation.invoke(); diff --git a/app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java b/app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java new file mode 100644 index 000000000..1f037e786 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java @@ -0,0 +1,224 @@ +/* + * 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.business.pings; + +import com.sun.net.httpserver.HttpServer; +import org.apache.roller.weblogger.business.pings.WeblogUpdatePinger.PingResult; +import org.apache.xmlrpc.XmlRpcException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests for {@link WeblogUpdatePinger}, covering how it builds the + * {@code weblogUpdates.ping} request and reads the response. + * + *

The response reader extracts only the {@code flerror} and {@code message} + * fields the protocol defines, reads every scalar as text, ignores response + * content it does not use, and refuses document type declarations. + */ +public class WeblogUpdatePingerTest { + + /** Set true if {@link Probe} is ever reconstructed from bytes. */ + private static volatile boolean probeReconstructed; + + @BeforeEach + public void reset() { + probeReconstructed = false; + } + + /** A serializable object whose reconstruction from bytes is observable. */ + static final class Probe implements Serializable { + private static final long serialVersionUID = 1L; + + private void readObject(ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { + in.defaultReadObject(); + probeReconstructed = true; + } + } + + private static byte[] serialize(Serializable object) throws Exception { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream out = new ObjectOutputStream(bytes)) { + out.writeObject(object); + } + return bytes.toByteArray(); + } + + @Test + public void readsFlerrorAndMessageFromNormalResponse() throws Exception { + String response = "" + + "" + + "flerror0" + + "messageThanks for the ping." + + ""; + + PingResult result = WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8)); + assertFalse(result.isError()); + assertEquals("Thanks for the ping.", result.getMessage()); + } + + @Test + public void readsErrorFlagFromResponse() throws Exception { + String response = "" + + "" + + "flerror1" + + "messageUnknown blog." + + ""; + + PingResult result = WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8)); + assertTrue(result.isError()); + assertEquals("Unknown blog.", result.getMessage()); + } + + @Test + public void faultResponseBecomesException() throws Exception { + String response = "" + + "" + + "faultCode4" + + "faultStringboom" + + ""; + + XmlRpcException thrown = assertThrows(XmlRpcException.class, + () -> WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8))); + assertEquals(4, thrown.code); + assertEquals("boom", thrown.getMessage()); + } + + @Test + public void controlProbeIsReconstructedByAnObjectStream() throws Exception { + // Control: reading the probe through an ObjectInputStream reconstructs it. + byte[] serialized = serialize(new Probe()); + try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(serialized))) { + in.readObject(); + } + assertTrue(probeReconstructed); + } + + @Test + public void base64ResponseMemberIsReadAsTextOnly() throws Exception { + // A base64 member is read as text; the reader does not turn it into an object. + String embedded = Base64.getEncoder().encodeToString(serialize(new Probe())); + String response = "" + + "" + + "faultCode4" + + "faultStringboom" + + "payload" + embedded + "" + + ""; + + assertThrows(XmlRpcException.class, + () -> WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8))); + assertFalse(probeReconstructed, "a base64 response member must be read as text, not reconstructed"); + } + + @Test + public void doctypeInResponseIsRefused() { + // A response that declares a DTD is refused, and nothing it names is resolved. + String response = "" + + "]>" + + "" + + "flerror0" + + "message&x;" + + ""; + + PingResult result = parse(response); + assertFalse(result.isError()); + } + + @Test + public void buildsWellFormedEscapedRequest() { + String request = WeblogUpdatePinger.buildRequest(Arrays.asList("A & B ", "http://example.test/")); + assertTrue(request.contains("weblogUpdates.ping")); + assertTrue(request.contains("A & B <blog>"), "parameters must be XML-escaped"); + assertTrue(request.contains("http://example.test/")); + assertFalse(request.contains(""), "raw markup must not leak into the request"); + } + + @Test + public void nonSuccessHttpStatusThrows() throws Exception { + // A non-2xx response is a transport failure, not a successful ping. + HttpServer server = startServer(500, "error"); + try { + String url = "http://127.0.0.1:" + server.getAddress().getPort() + "/"; + assertThrows(IOException.class, () -> WeblogUpdatePinger.exchange(url, "")); + } finally { + server.stop(0); + } + } + + @Test + public void successHttpStatusReturnsBody() throws Exception { + String body = "" + + "ok"; + HttpServer server = startServer(200, body); + try { + String url = "http://127.0.0.1:" + server.getAddress().getPort() + "/"; + byte[] bytes = WeblogUpdatePinger.exchange(url, ""); + assertEquals(body, new String(bytes, StandardCharsets.UTF_8)); + } finally { + server.stop(0); + } + } + + @Test + public void nonHttpTargetThrowsIoException() { + // A file:/ftp: target must not escape as a ClassCastException. + assertThrows(IOException.class, + () -> WeblogUpdatePinger.exchange("file:///no/such/file", "")); + } + + private static HttpServer startServer(int status, String body) throws IOException { + HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + server.createContext("/", exchange -> { + exchange.getRequestBody().readAllBytes(); + byte[] out = body.getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(status, out.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(out); + } + }); + server.start(); + return server; + } + + private static PingResult parse(String response) { + try { + return WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8)); + } catch (Exception ex) { + throw new AssertionError("parsing should not throw for this response", ex); + } + } +} diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java new file mode 100644 index 000000000..bb58838f7 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * 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. + */ +package org.apache.roller.weblogger.ui.struts2.editor; + +import java.util.function.Function; +import java.util.function.Supplier; + +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import org.apache.roller.weblogger.ui.struts2.admin.PingTargets; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.function.Executable; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * No editor or ping-target action lets a request parameter create the domain + * object it works on. Those objects are loaded only by the action, from the + * weblog, by id. + */ +class EditorActionTargetBindingTest { + + private ValueStack stack; + + @BeforeEach + void setUp() { + ConfigurationManager configurationManager = new ConfigurationManager("xwork"); + configurationManager.addContainerProvider(new XWorkConfigurationProvider()); + Container container = configurationManager.getConfiguration().getContainer(); + stack = container.getInstance(ValueStackFactory.class).createValueStack(); + } + + private void tryBind(Object action, String name, String value) { + stack.push(action); + ReflectionContextState.setCreatingNullObjects(stack.getContext(), true); + ReflectionContextState.setDenyMethodExecution(stack.getContext(), true); + try { + stack.setParameter(name, value); + } catch (RuntimeException expectedForUnwritableProperty) { + // A non-bindable property may raise; that is the point. + } finally { + ReflectionContextState.setCreatingNullObjects(stack.getContext(), false); + stack.pop(); + } + } + + private static class Case { + final String label; + final Supplier make; + final String param; + final Function read; + Case(String label, Supplier make, String param, Function read) { + this.label = label; this.make = make; this.param = param; this.read = read; + } + } + + private List cases() { + return Arrays.asList( + new Case("Pings.pingTarget", Pings::new, "pingTarget.pingUrl", + a -> ((Pings) a).getPingTarget()), + new Case("PingTargets.pingTarget", PingTargets::new, "pingTarget.pingUrl", + a -> ((PingTargets) a).getPingTarget()), + new Case("CategoryRemove.category", CategoryRemove::new, "category.name", + a -> ((CategoryRemove) a).getCategory()), + new Case("EntryRemove.removeEntry", EntryRemove::new, "removeEntry.anchor", + a -> ((EntryRemove) a).getRemoveEntry()), + new Case("EntryEdit.entry", EntryEdit::new, "entry.anchor", + a -> ((EntryEdit) a).getEntry()), + new Case("MediaFileEdit.directory", MediaFileEdit::new, "directory.name", + a -> ((MediaFileEdit) a).getDirectory()), + new Case("MediaFileAdd.directory", MediaFileAdd::new, "directory.name", + a -> ((MediaFileAdd) a).getDirectory()), + new Case("MediaFileView.currentDirectory", MediaFileView::new, "currentDirectory.name", + a -> ((MediaFileView) a).getCurrentDirectory()), + new Case("MediaFileImageChooser.currentDirectory", MediaFileImageChooser::new, "currentDirectory.name", + a -> ((MediaFileImageChooser) a).getCurrentDirectory()), + new Case("StylesheetEdit.template", StylesheetEdit::new, "template.name", + a -> ((StylesheetEdit) a).getTemplate()), + new Case("TemplateEdit.template", TemplateEdit::new, "template.name", + a -> ((TemplateEdit) a).getTemplate()), + new Case("Bookmarks.folder", Bookmarks::new, "folder.name", + a -> ((Bookmarks) a).getFolder()) + ); + } + + @TestFactory + Iterable requestCannotCreateTheTargetObject() { + return cases().stream().map(c -> DynamicTest.dynamicTest(c.label, (Executable) () -> { + Object action = c.make.get(); + tryBind(action, c.param, "http://attacker.example/"); + assertNull(c.read.apply(action), c.label + " must stay null after a bind attempt"); + })).collect(java.util.stream.Collectors.toList()); + } +} diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java new file mode 100644 index 000000000..edfda4901 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * 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. + */ +package org.apache.roller.weblogger.ui.struts2.editor; + +import java.util.Collections; + +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.config.providers.XWorkConfigurationProvider; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import org.apache.roller.weblogger.business.WebloggerFactory; +import org.apache.roller.weblogger.business.Weblogger; +import org.apache.roller.weblogger.business.pings.PingTargetManager; +import org.apache.roller.weblogger.pojos.PingTarget; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * The ping target that the Pings action works on must be one of the stored + * ping targets, selected by id. + */ +class PingsTargetBindingTest { + + private MockedStatic factory; + private PingTargetManager pingTargets; + private ValueStack stack; + + @BeforeEach + void setUp() throws Exception { + factory = mockStatic(WebloggerFactory.class); + pingTargets = mock(PingTargetManager.class); + when(pingTargets.getCommonPingTargets()).thenReturn(Collections.emptyList()); + Weblogger weblogger = mock(Weblogger.class); + when(weblogger.getPingTargetManager()).thenReturn(pingTargets); + factory.when(WebloggerFactory::getWeblogger).thenReturn(weblogger); + + ConfigurationManager configurationManager = new ConfigurationManager("xwork"); + configurationManager.addContainerProvider(new XWorkConfigurationProvider()); + Container container = configurationManager.getConfiguration().getContainer(); + stack = container.getInstance(ValueStackFactory.class).createValueStack(); + } + + @AfterEach + void tearDown() { + factory.close(); + } + + /** Set request parameters on the action the way the params interceptor does. */ + private void bind(Pings action, String name, String value) { + stack.push(action); + ReflectionContextState.setCreatingNullObjects(stack.getContext(), true); + ReflectionContextState.setDenyMethodExecution(stack.getContext(), true); + try { + stack.setParameter(name, value); + } finally { + ReflectionContextState.setCreatingNullObjects(stack.getContext(), false); + stack.pop(); + } + } + + @Test + void pingTargetFieldsInTheRequestDoNotMakeATarget() throws Exception { + Pings action = new Pings(); + bind(action, "pingTarget.pingUrl", "http://localhost:1/"); + action.myPrepare(); + + assertNull(action.getPingTarget()); + } + + @Test + void pingTargetIdSelectsAStoredTarget() throws Exception { + PingTarget stored = new PingTarget("target-1", "Stored", "http://ping.example.com/", false); + when(pingTargets.getPingTarget("target-1")).thenReturn(stored); + + Pings action = new Pings(); + bind(action, "pingTargetId", "target-1"); + bind(action, "pingTarget.pingUrl", "http://localhost:1/"); + action.myPrepare(); + + assertSame(stored, action.getPingTarget()); + assertSame("http://ping.example.com/", action.getPingTarget().getPingUrl()); + } +} diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptorTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptorTest.java new file mode 100644 index 000000000..f0184e196 --- /dev/null +++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/UIActionInterceptorTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * 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. + */ +package org.apache.roller.weblogger.ui.struts2.util; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import org.apache.roller.weblogger.pojos.User; +import org.apache.roller.weblogger.pojos.Weblog; +import org.apache.struts2.StrutsStatics; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * The interceptor must set the authenticated user and the action weblog from + * trusted sources only, overwriting anything bound from request parameters. + */ +class UIActionInterceptorTest { + + static class TestAction extends UIAction { + TestAction() { this.actionName = "test"; } + @Override public String execute() { return SUCCESS; } + } + + @Test + void parameterBoundUserAndWeblogAreOverwrittenWhenThereIsNoSession() throws Exception { + // An attacker-controlled request with no session: getSession(false) is null, + // so RollerSession.getRollerSession returns null. + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getSession(false)).thenReturn(null); + + // The action arrives with a user and weblog already bound from parameters. + TestAction action = new TestAction(); + User forgedUser = new User(); + forgedUser.setUserName("siteadmin"); + action.setAuthenticatedUser(forgedUser); + Weblog forgedWeblog = new Weblog(); + forgedWeblog.setHandle("team"); + action.setActionWeblog(forgedWeblog); + // No weblog handle parameter, so no weblog can resolve. + action.setWeblog(null); + + Map contextMap = new HashMap<>(); + contextMap.put(StrutsStatics.HTTP_REQUEST, request); + ActionContext context = new ActionContext(contextMap); + + ActionInvocation invocation = mock(ActionInvocation.class); + when(invocation.getAction()).thenReturn(action); + when(invocation.getInvocationContext()).thenReturn(context); + when(invocation.invoke()).thenReturn(UIAction.SUCCESS); + + new UIActionInterceptor().doIntercept(invocation); + + assertNull(action.getAuthenticatedUser()); + assertNull(action.getActionWeblog()); + } +} From 7ac9cb331d0b4f50a8ff2ec3a8fe9562f2831d9c Mon Sep 17 00:00:00 2001 From: "David M. Johnson" Date: Sun, 8 Nov 2026 21:08:41 -0500 Subject: [PATCH 3/4] Remove the obsolete weblog update ping feature --- CHANGES.md | 8 + .../roller/weblogger/business/Weblogger.java | 21 - .../weblogger/business/WebloggerImpl.java | 68 --- .../business/jpa/JPAAutoPingManagerImpl.java | 133 ------ .../business/jpa/JPAPingQueueManagerImpl.java | 122 ------ .../jpa/JPAPingTargetManagerImpl.java | 156 ------- .../jpa/JPAWeblogEntryManagerImpl.java | 6 +- .../business/jpa/JPAWeblogManagerImpl.java | 33 -- .../business/jpa/JPAWebloggerImpl.java | 9 - .../business/jpa/JPAWebloggerModule.java | 6 - .../business/pings/AutoPingManager.java | 133 ------ .../business/pings/PingQueueManager.java | 86 ---- .../business/pings/PingQueueProcessor.java | 184 -------- .../business/pings/PingQueueTask.java | 171 -------- .../business/pings/PingTargetManager.java | 107 ----- .../business/pings/WeblogUpdatePinger.java | 401 ------------------ .../business/pings/package-info.java | 22 - .../business/startup/DatabaseInstaller.java | 15 +- .../roller/weblogger/config/PingConfig.java | 328 -------------- .../roller/weblogger/pojos/AutoPing.java | 149 ------- .../weblogger/pojos/PingQueueEntry.java | 206 --------- .../roller/weblogger/pojos/PingTarget.java | 236 ----------- .../apache/roller/weblogger/pojos/Weblog.java | 3 - .../ui/struts2/admin/PingTargetBean.java | 73 ---- .../ui/struts2/admin/PingTargetEdit.java | 166 -------- .../ui/struts2/admin/PingTargets.java | 214 ---------- .../ui/struts2/editor/EntryEdit.java | 5 - .../weblogger/ui/struts2/editor/Pings.java | 277 ------------ .../resources/ApplicationResources.properties | 70 --- .../ApplicationResources_de.properties | 36 -- .../ApplicationResources_es.properties | 32 -- .../ApplicationResources_fr.properties | 55 --- .../ApplicationResources_ja.properties | 53 --- .../ApplicationResources_ko.properties | 47 -- .../ApplicationResources_ru.properties | 55 --- .../ApplicationResources_zh_CN.properties | 139 ------ .../main/resources/META-INF/persistence.xml | 3 - .../roller/weblogger/config/roller.properties | 61 +-- .../weblogger/config/runtimeConfigDefs.xml | 4 - .../roller/weblogger/pojos/AutoPing.orm.xml | 56 --- .../weblogger/pojos/PingQueueEntry.orm.xml | 59 --- .../roller/weblogger/pojos/PingTarget.orm.xml | 51 --- .../weblogger/ui/struts2/admin/admin-menu.xml | 3 - .../ui/struts2/editor/editor-menu.xml | 3 - .../resources/sql/610-to-700-migration.vm | 32 ++ app/src/main/resources/sql/createdb.vm | 48 --- .../main/resources/sql/dbscripts.properties | 2 +- app/src/main/resources/struts.xml | 43 -- .../webapp/WEB-INF/jsps/admin/PingTargets.jsp | 301 ------------- .../main/webapp/WEB-INF/jsps/editor/Pings.jsp | 100 ----- app/src/main/webapp/WEB-INF/tiles.xml | 9 - .../apache/roller/weblogger/TestUtils.java | 92 ---- .../roller/weblogger/business/PingsTest.java | 355 ---------------- .../pings/WeblogUpdatePingerTest.java | 224 ---------- .../startup/DatabaseInstallerUpgradeTest.java | 134 ++++-- .../ui/struts2/admin/PingTargetEditTest.java | 94 ---- .../editor/EditorActionTargetBindingTest.java | 7 +- .../editor/PingsTargetBindingTest.java | 107 ----- .../util/StrutsParameterAnnotationTest.java | 4 - .../RollerAtomManagementTest.java | 8 + .../test/resources/roller-custom.properties | 2 +- .../test/resources/roller-jettyrun.properties | 2 +- docs/images/user-guide-23-ping.png | Bin 117336 -> 0 bytes docs/images/user-guide-30-ping.png | Bin 84737 -> 0 bytes docs/roller-install-guide.adoc | 2 +- docs/roller-user-guide.adoc | 299 +------------ docs/security-model.md | 13 +- .../roller/playwright/NewUserJourneyIT.java | 16 + .../test/resources/roller-jettyrun.properties | 2 +- 69 files changed, 195 insertions(+), 5766 deletions(-) delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAAutoPingManagerImpl.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingQueueManagerImpl.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingTargetManagerImpl.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/pings/AutoPingManager.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueManager.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueProcessor.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueTask.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/pings/PingTargetManager.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/business/pings/package-info.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/config/PingConfig.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/pojos/AutoPing.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/pojos/PingQueueEntry.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/pojos/PingTarget.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetBean.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEdit.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java delete mode 100644 app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java delete mode 100644 app/src/main/resources/org/apache/roller/weblogger/pojos/AutoPing.orm.xml delete mode 100644 app/src/main/resources/org/apache/roller/weblogger/pojos/PingQueueEntry.orm.xml delete mode 100644 app/src/main/resources/org/apache/roller/weblogger/pojos/PingTarget.orm.xml create mode 100644 app/src/main/resources/sql/610-to-700-migration.vm delete mode 100644 app/src/main/webapp/WEB-INF/jsps/admin/PingTargets.jsp delete mode 100644 app/src/main/webapp/WEB-INF/jsps/editor/Pings.jsp delete mode 100644 app/src/test/java/org/apache/roller/weblogger/business/PingsTest.java delete mode 100644 app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java delete mode 100644 app/src/test/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEditTest.java delete mode 100644 app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java delete mode 100644 docs/images/user-guide-23-ping.png delete mode 100644 docs/images/user-guide-30-ping.png diff --git a/CHANGES.md b/CHANGES.md index fa53dc883..8e6a56dcd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,6 +44,14 @@ ### Behaviour changes worth reading before upgrading +- **The obsolete weblog update ping feature is removed.** Public ping services + have largely shut down following years of ping spam. The 7.0 database upgrade + drops the three ping tables and removes their settings and task lock. Remove + `PingQueueTask` from `tasks.enabled`, and remove any `tasks.PingQueueTask.*` + and `pings.*` settings from `roller-custom.properties` before upgrading. + If the removed task class is still configured, Roller logs a task-loading + error and continues startup. + - **Roller now runs on Jakarta EE** ([ROL-2183](https://issues.apache.org/jira/browse/ROL-2183)). It needs Java 17 or later and a Servlet 6.1 container such as Tomcat 11. Tomcat 9 and other diff --git a/app/src/main/java/org/apache/roller/weblogger/business/Weblogger.java b/app/src/main/java/org/apache/roller/weblogger/business/Weblogger.java index 62f498efb..85924bebf 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/Weblogger.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/Weblogger.java @@ -22,9 +22,6 @@ import org.apache.roller.planet.business.fetcher.FeedFetcher; import org.apache.roller.weblogger.WebloggerException; import org.apache.roller.weblogger.business.plugins.PluginManager; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingQueueManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; import org.apache.roller.weblogger.business.runnable.ThreadManager; import org.apache.roller.weblogger.business.search.IndexManager; import org.apache.roller.weblogger.business.themes.ThemeManager; @@ -63,24 +60,6 @@ public interface Weblogger { WeblogEntryManager getWeblogEntryManager(); - /** - * Get the AutoPingManager associated with this Weblogger instance. - */ - AutoPingManager getAutopingManager(); - - - /** - * Get the PingTargetManager associated with this Weblogger instance. - */ - PingTargetManager getPingTargetManager(); - - - /** - * Get the PingQueueManager associated with this Weblogger instance. - */ - PingQueueManager getPingQueueManager(); - - /** * * Get PropertiesManager associated with this Weblogger instance. diff --git a/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java index a7e497735..9696295f0 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/WebloggerImpl.java @@ -23,14 +23,10 @@ import org.apache.roller.planet.business.PlanetManager; import org.apache.roller.planet.business.fetcher.FeedFetcher; import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingQueueManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; import org.apache.roller.weblogger.business.plugins.PluginManager; import org.apache.roller.weblogger.business.runnable.ThreadManager; import org.apache.roller.weblogger.business.search.IndexManager; import org.apache.roller.weblogger.business.themes.ThemeManager; -import org.apache.roller.weblogger.config.PingConfig; import org.apache.roller.weblogger.util.SecureXmlParsers; @@ -50,13 +46,10 @@ public abstract class WebloggerImpl implements Weblogger { private static final Log log = LogFactory.getLog(WebloggerImpl.class); // managers - private final AutoPingManager autoPingManager; private final BookmarkManager bookmarkManager; private final IndexManager indexManager; private final MediaFileManager mediaFileManager; private final FileContentManager fileContentManager; - private final PingQueueManager pingQueueManager; - private final PingTargetManager pingTargetManager; private final PluginManager pluginManager; private final PropertiesManager propertiesManager; private final ThemeManager themeManager; @@ -79,13 +72,10 @@ public abstract class WebloggerImpl implements Weblogger { protected WebloggerImpl( - AutoPingManager autoPingManager, BookmarkManager bookmarkManager, IndexManager indexManager, MediaFileManager mediaFileManager, FileContentManager fileContentManager, - PingQueueManager pingQueueManager, - PingTargetManager pingTargetManager, PluginManager pluginManager, PropertiesManager propertiesManager, ThemeManager themeManager, @@ -98,13 +88,10 @@ protected WebloggerImpl( org.apache.roller.planet.business.PlanetURLStrategy planetUrlStrategy, URLStrategy urlStrategy) throws WebloggerException { - this.autoPingManager = autoPingManager; this.bookmarkManager = bookmarkManager; this.indexManager = indexManager; this.mediaFileManager = mediaFileManager; this.fileContentManager = fileContentManager; - this.pingQueueManager = pingQueueManager; - this.pingTargetManager = pingTargetManager; this.pluginManager = pluginManager; this.propertiesManager = propertiesManager; this.themeManager = themeManager; @@ -240,39 +227,6 @@ public PropertiesManager getPropertiesManager() { } - /** - * - * - * @see org.apache.roller.weblogger.business.Weblogger#getPingTargetManager() - */ - @Override - public PingQueueManager getPingQueueManager() { - return pingQueueManager; - } - - - /** - * - * - * @see org.apache.roller.weblogger.business.Weblogger#getPingTargetManager() - */ - @Override - public AutoPingManager getAutopingManager() { - return autoPingManager; - } - - - /** - * - * - * @see org.apache.roller.weblogger.business.Weblogger#getPingTargetManager() - */ - @Override - public PingTargetManager getPingTargetManager() { - return pingTargetManager; - } - - /** * * @@ -313,12 +267,9 @@ public org.apache.roller.planet.business.PlanetURLStrategy getPlanetURLStrategy( @Override public void release() { try { - autoPingManager.release(); bookmarkManager.release(); mediaFileManager.release(); fileContentManager.release(); - pingTargetManager.release(); - pingQueueManager.release(); pluginManager.release(); threadManager.release(); userManager.release(); @@ -349,25 +300,6 @@ public void initialize() throws InitializationException { // configuration. If it cannot be applied, startup fails. SecureXmlParsers.installForXmlRpc(); - try { - // Initialize ping systems - // TODO: this should probably be moving inside ping manager initialize() methods? - - // Initialize common targets from the configuration - PingConfig.initializeCommonTargets(); - - // Initialize ping variants - PingConfig.initializePingVariants(); - - // Remove all autoping configurations if ping usage has been disabled. - if (PingConfig.getDisablePingUsage()) { - log.info("Ping usage has been disabled. Removing any existing auto ping configurations."); - WebloggerFactory.getWeblogger().getAutopingManager().removeAllAutoPings(); - } - } catch (Exception e) { - throw new InitializationException("Error initializing ping systems", e); - } - // we always need to do a flush after initialization because it's // possible that some changes need to be persisted try { diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAAutoPingManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAAutoPingManagerImpl.java deleted file mode 100644 index 627557be6..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAAutoPingManagerImpl.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.business.jpa; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingQueueManager; -import org.apache.roller.weblogger.config.PingConfig; -import org.apache.roller.weblogger.pojos.AutoPing; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.pojos.WeblogEntry; -import org.apache.roller.weblogger.pojos.Weblog; -import java.util.Collection; -import java.util.List; -import jakarta.persistence.Query; -import jakarta.persistence.TypedQuery; -import org.apache.roller.weblogger.business.Weblogger; - -/* - * JPAAutoPingManagerImpl.java - * - * Created on May 29, 2006, 11:29 AM - * - */ -@com.google.inject.Singleton -public class JPAAutoPingManagerImpl implements AutoPingManager { - - private final Weblogger roller; - private final JPAPersistenceStrategy strategy; - /** - * The logger instance for this class. - */ - private static final Log logger = LogFactory.getFactory().getInstance(JPAAutoPingManagerImpl.class); - - /** - * Creates a new instance of JPAAutoPingManagerImpl - */ - @com.google.inject.Inject - protected JPAAutoPingManagerImpl(Weblogger roller, JPAPersistenceStrategy strategy) { - this.roller = roller; - this.strategy = strategy; - } - - @Override - public AutoPing getAutoPing(String id) throws WebloggerException { - return (AutoPing) strategy.load(AutoPing.class, id); - } - - @Override - public void saveAutoPing(AutoPing autoPing) throws WebloggerException { - strategy.store(autoPing); - } - - @Override - public void removeAutoPing(AutoPing autoPing) throws WebloggerException { - strategy.remove(autoPing); - } - - @Override - public void removeAutoPing(PingTarget pingTarget, Weblog website) throws WebloggerException { - Query q = strategy.getNamedUpdate("AutoPing.removeByPingTarget&Website"); - q.setParameter(1, pingTarget); - q.setParameter(2, website); - q.executeUpdate(); - } - - @Override - public void removeAutoPings(Collection autopings) throws WebloggerException { - strategy.removeAll(autopings); - } - - @Override - public void removeAllAutoPings() throws WebloggerException { - TypedQuery q = strategy.getNamedQueryCommitFirst("AutoPing.getAll", AutoPing.class); - removeAutoPings(q.getResultList()); - } - - @Override - public void queueApplicableAutoPings(WeblogEntry changedWeblogEntry) throws WebloggerException { - if (PingConfig.getSuspendPingProcessing()) { - if (logger.isDebugEnabled()) { - logger.debug("Ping processing is suspended." + " No auto pings will be queued."); - } - return; - } - - PingQueueManager pingQueueMgr = roller.getPingQueueManager(); - List applicableAutopings = getApplicableAutoPings(changedWeblogEntry); - for (AutoPing autoPing : applicableAutopings) { - pingQueueMgr.addQueueEntry(autoPing); - } - } - - @Override - public List getAutoPingsByWebsite(Weblog website) throws WebloggerException { - TypedQuery q = strategy.getNamedQuery("AutoPing.getByWebsite", AutoPing.class); - q.setParameter(1, website); - return q.getResultList(); - } - - @Override - public List getAutoPingsByTarget(PingTarget pingTarget) throws WebloggerException { - TypedQuery q = strategy.getNamedQuery("AutoPing.getByPingTarget", AutoPing.class); - q.setParameter(1, pingTarget); - return q.getResultList(); - } - - @Override - public List getApplicableAutoPings(WeblogEntry changedWeblogEntry) throws WebloggerException { - return getAutoPingsByWebsite(changedWeblogEntry.getWebsite()); - } - - @Override - public void release() { - } -} \ No newline at end of file diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingQueueManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingQueueManagerImpl.java deleted file mode 100644 index fd651d840..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingQueueManagerImpl.java +++ /dev/null @@ -1,122 +0,0 @@ - -/* - * 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.business.jpa; - -import java.sql.Timestamp; -import java.util.List; -import jakarta.persistence.TypedQuery; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.pings.PingQueueManager; -import org.apache.roller.weblogger.pojos.AutoPing; -import org.apache.roller.weblogger.pojos.PingQueueEntry; - -/* - * JPAPingQueueManagerImpl.java - * - * Created on May 28, 2006, 4:11 PM - * - */ -@com.google.inject.Singleton -public class JPAPingQueueManagerImpl implements PingQueueManager { - - private static final Log log = LogFactory.getLog(JPAPingQueueManagerImpl.class); - - /** The strategy for this manager. */ - private final JPAPersistenceStrategy strategy; - - - /** - * Creates a new instance of JPAPingQueueManagerImpl - */ - @com.google.inject.Inject - protected JPAPingQueueManagerImpl(JPAPersistenceStrategy strategy) { - this.strategy = strategy; - } - - - @Override - public PingQueueEntry getQueueEntry(String id) - throws WebloggerException { - return (PingQueueEntry)strategy.load( - PingQueueEntry.class, id); - } - - @Override - public void saveQueueEntry(PingQueueEntry pingQueueEntry) - throws WebloggerException { - log.debug("Storing ping queue entry: " + pingQueueEntry); - strategy.store(pingQueueEntry); - } - - @Override - public void removeQueueEntry(PingQueueEntry pingQueueEntry) - throws WebloggerException { - log.debug("Removing ping queue entry: " + pingQueueEntry); - strategy.remove(pingQueueEntry); - } - - - @Override - public void addQueueEntry(AutoPing autoPing) throws WebloggerException { - log.debug("Creating new ping queue entry for auto ping configuration: " - + autoPing); - - // First check if there is an existing ping queue entry - // for the same target and website - if (isAlreadyQueued(autoPing)) { - log.debug("A ping queue entry is already present" + - " for this ping target and website: " + autoPing); - return; - } - - Timestamp now = new Timestamp(System.currentTimeMillis()); - PingQueueEntry pingQueueEntry = - new PingQueueEntry( - null, now, autoPing.getPingTarget(), - autoPing.getWebsite(), 0); - this.saveQueueEntry(pingQueueEntry); - } - - @Override - public List getAllQueueEntries() - throws WebloggerException { - return strategy.getNamedQuery("PingQueueEntry.getAllOrderByEntryTime", - PingQueueEntry.class).getResultList(); - } - - // private helper to determine if an has already been queued - // for the same website and ping target. - private boolean isAlreadyQueued(AutoPing autoPing) - throws WebloggerException { - // first, determine if an entry already exists - TypedQuery q = strategy.getNamedQuery("PingQueueEntry.getByPingTarget&Website", - PingQueueEntry.class); - q.setParameter(1, autoPing.getPingTarget()); - q.setParameter(2, autoPing.getWebsite()); - return !q.getResultList().isEmpty(); - } - - @Override - public void release() {} - - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingTargetManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingTargetManagerImpl.java deleted file mode 100644 index 011227871..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAPingTargetManagerImpl.java +++ /dev/null @@ -1,156 +0,0 @@ - -/* - * 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.business.jpa; - -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.List; -import jakarta.persistence.Query; -import jakarta.persistence.TypedQuery; - -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.pojos.PingTarget; - -/* - * JPAPingTargetManagerImpl.java - * - * Created on May 29, 2006, 2:24 PM - * - */ -@com.google.inject.Singleton -public class JPAPingTargetManagerImpl implements PingTargetManager { - - private final JPAPersistenceStrategy strategy; - - @com.google.inject.Inject - protected JPAPingTargetManagerImpl(JPAPersistenceStrategy strategy) { - this.strategy = strategy; - } - - - @Override - public void removePingTarget(PingTarget pingTarget) - throws WebloggerException { - // remove contents and then target - this.removePingTargetContents(pingTarget); - this.strategy.remove(pingTarget); - } - - /** - * Convenience method which removes any queued pings or auto pings that - * reference the given ping target. - */ - private void removePingTargetContents(PingTarget ping) - throws WebloggerException { - // Remove the website's ping queue entries - Query q = strategy.getNamedUpdate("PingQueueEntry.removeByPingTarget"); - q.setParameter(1, ping); - q.executeUpdate(); - - // Remove the website's auto ping configurations - q = strategy.getNamedUpdate("AutoPing.removeByPingTarget"); - q.setParameter(1, ping); - q.executeUpdate(); - } - - @Override - public void savePingTarget(PingTarget pingTarget) - throws WebloggerException { - strategy.store(pingTarget); - } - - @Override - public PingTarget getPingTarget(String id) - throws WebloggerException { - return (PingTarget)strategy.load(PingTarget.class, id); - } - - @Override - public boolean targetNameExists(String pingTargetName) - throws WebloggerException { - - // Within that set of targets, fail if there is a target - // with the same name and that target doesn't - // have the same id. - for (PingTarget pt : getCommonPingTargets()) { - if (pt.getName().equals(pingTargetName)) { - return true; - } - } - // No conflict found - return false; - } - - - @Override - public boolean isUrlWellFormed(String url) - throws WebloggerException { - - if (url == null || url.isBlank()) { - return false; - } - try { - URL parsedUrl = new URL(url); - // OK. If we get here, it parses ok. Now just check - // that the protocol is http and there is a host portion. - boolean isHttp = parsedUrl.getProtocol().equals("http"); - boolean hasHost = parsedUrl.getHost() != null && !parsedUrl.getHost().isBlank(); - return isHttp && hasHost; - } catch (MalformedURLException e) { - return false; - } - } - - - @Override - public boolean isHostnameKnown(String url) - throws WebloggerException { - if (url == null || url.isBlank()) { - return false; - } - try { - URL parsedUrl = new URL(url); - String host = parsedUrl.getHost(); - if (host == null || host.isBlank()) { - return false; - } - InetAddress addr = InetAddress.getByName(host); - return true; - } catch (MalformedURLException e) { - return false; - } catch (UnknownHostException e) { - return false; - } - } - - @Override - public List getCommonPingTargets() - throws WebloggerException { - TypedQuery q = strategy.getNamedQuery( - "PingTarget.getPingTargetsOrderByName", PingTarget.class); - return q.getResultList(); - } - - @Override - public void release() {} - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java index 93a478a9e..0d50b3bb2 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogEntryManagerImpl.java @@ -178,7 +178,7 @@ public void removeComment(WeblogEntryComment comment) throws WebloggerException /** * @inheritDoc */ - // TODO: perhaps the createAnchor() and queuePings() items should go outside this method? + // TODO: perhaps createAnchor() should go outside this method? @Override public void saveWeblogEntry(WeblogEntry entry) throws WebloggerException { @@ -246,10 +246,6 @@ public void saveWeblogEntry(WeblogEntry entry) throws WebloggerException { roller.getWeblogManager().saveWeblog(entry.getWebsite()); } - if(entry.isPublished()) { - // Queue applicable pings for this update. - roller.getAutopingManager().queueApplicableAutoPings(entry); - } } /** diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogManagerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogManagerImpl.java index 50e96eef7..0fdc38669 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogManagerImpl.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWeblogManagerImpl.java @@ -22,8 +22,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; import org.apache.roller.weblogger.config.WebloggerConfig; import jakarta.persistence.NoResultException; @@ -45,10 +43,7 @@ import org.apache.roller.weblogger.business.WeblogManager; import org.apache.roller.weblogger.business.Weblogger; import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.pojos.AutoPing; import org.apache.roller.weblogger.pojos.CustomTemplateRendition; -import org.apache.roller.weblogger.pojos.PingQueueEntry; -import org.apache.roller.weblogger.pojos.PingTarget; import org.apache.roller.weblogger.pojos.StatCount; import org.apache.roller.weblogger.pojos.StatCountCountComparator; import org.apache.roller.weblogger.pojos.TagStat; @@ -156,21 +151,6 @@ private void removeWeblogContents(Weblog weblog) removeCounts.setParameter(1, 0); removeCounts.executeUpdate(); - // Remove the weblog's ping queue entries - TypedQuery q = strategy.getNamedQuery("PingQueueEntry.getByWebsite", PingQueueEntry.class); - q.setParameter(1, weblog); - List queueEntries = q.getResultList(); - for (Object obj : queueEntries) { - this.strategy.remove(obj); - } - - // Remove the weblog's auto ping configurations - AutoPingManager autoPingMgr = roller.getAutopingManager(); - List autopings = autoPingMgr.getAutoPingsByWebsite(weblog); - for (AutoPing autoPing : autopings) { - this.strategy.remove(autoPing); - } - // remove associated templates TypedQuery templateQuery = strategy.getNamedQuery("WeblogTemplate.getByWeblog", WeblogTemplate.class); @@ -347,19 +327,6 @@ private void addWeblogContents(Weblog newWeblog) // flush so that all data up to this point can be available in db this.strategy.flush(); - - // add any auto enabled ping targets - PingTargetManager pingTargetMgr = roller.getPingTargetManager(); - AutoPingManager autoPingMgr = roller.getAutopingManager(); - - for (PingTarget pingTarget : pingTargetMgr.getCommonPingTargets()) { - if(pingTarget.isAutoEnabled()) { - AutoPing autoPing = new AutoPing( - null, pingTarget, newWeblog); - autoPingMgr.saveAutoPing(autoPing); - } - } - } @Override diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerImpl.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerImpl.java index 91597ca10..38933ec92 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerImpl.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerImpl.java @@ -22,9 +22,6 @@ import org.apache.roller.planet.business.fetcher.FeedFetcher; import org.apache.roller.weblogger.WebloggerException; import org.apache.roller.weblogger.business.*; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingQueueManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; import org.apache.roller.weblogger.business.plugins.PluginManager; import org.apache.roller.weblogger.business.runnable.ThreadManager; import org.apache.roller.weblogger.business.search.IndexManager; @@ -47,13 +44,10 @@ public class JPAWebloggerImpl extends WebloggerImpl { @com.google.inject.Inject protected JPAWebloggerImpl( JPAPersistenceStrategy strategy, - AutoPingManager autoPingManager, BookmarkManager bookmarkManager, IndexManager indexManager, MediaFileManager mediaFileManager, FileContentManager fileContentManager, - PingQueueManager pingQueueManager, - PingTargetManager pingTargetManager, PluginManager pluginManager, PropertiesManager propertiesManager, ThemeManager themeManager, @@ -67,13 +61,10 @@ protected JPAWebloggerImpl( URLStrategy urlStrategy) throws WebloggerException { super( - autoPingManager, bookmarkManager, indexManager, mediaFileManager, fileContentManager, - pingQueueManager, - pingTargetManager, pluginManager, propertiesManager, themeManager, diff --git a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerModule.java b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerModule.java index f3e392862..2906d6f63 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerModule.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/jpa/JPAWebloggerModule.java @@ -38,9 +38,6 @@ import org.apache.roller.weblogger.business.UserManager; import org.apache.roller.weblogger.business.WeblogEntryManager; import org.apache.roller.weblogger.business.WeblogManager; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingQueueManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; import org.apache.roller.weblogger.business.plugins.PluginManager; import org.apache.roller.weblogger.business.plugins.PluginManagerImpl; import org.apache.roller.weblogger.business.runnable.ThreadManager; @@ -63,10 +60,7 @@ public void configure(Binder binder) { binder.bind(JPAPersistenceStrategy.class); - binder.bind(AutoPingManager.class).to( JPAAutoPingManagerImpl.class); binder.bind(BookmarkManager.class).to( JPABookmarkManagerImpl.class); - binder.bind(PingQueueManager.class).to( JPAPingQueueManagerImpl.class); - binder.bind(PingTargetManager.class).to( JPAPingTargetManagerImpl.class); binder.bind(PropertiesManager.class).to( JPAPropertiesManagerImpl.class); binder.bind(ThreadManager.class).to( JPAThreadManagerImpl.class); binder.bind(UserManager.class).to( JPAUserManagerImpl.class); diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/AutoPingManager.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/AutoPingManager.java deleted file mode 100644 index 7a8b4312d..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/AutoPingManager.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.business.pings; - -import java.util.Collection; -import java.util.List; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.pojos.AutoPing; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.pojos.WeblogEntry; -import org.apache.roller.weblogger.pojos.Weblog; - - -/** - * Manages autoping storage/retrieval, queries and queue. - */ -public interface AutoPingManager { - - - /** - * Store an auto ping configuration. - * - * @param autoPing the auto ping configuration - * @throws WebloggerException - */ - void saveAutoPing(AutoPing autoPing) throws WebloggerException; - - - /** - * Remove the auto ping configuration with given id. - * - * @param autoPing the auto ping configuration to remove - * @throws WebloggerException - */ - void removeAutoPing(AutoPing autoPing) throws WebloggerException; - - - /** - * Remove the auto ping configuration for the given ping target and website, if one exists. Returns silently if it - * doesn't exist. - * - * @param pingTarget the ping target - * @param website the website - * @throws WebloggerException - */ - void removeAutoPing(PingTarget pingTarget, Weblog website) throws WebloggerException; - - - /** - * Remove a collection of auto ping configurations. - * - * @param autopings a Collection of AAutoPing/code> objects - * @throws WebloggerException - */ - void removeAutoPings(Collection autopings) throws WebloggerException; - - - /** - * Remove all auto ping configurations for all websites. - * - * @throws WebloggerException - */ - void removeAllAutoPings() throws WebloggerException; - - - /** - * Retrieve an auto ping configuration by id. - * - * @param id the id of the auto ping configuration to retrieve. - * @return the auto ping configuration with specified id or null if not found - * @throws WebloggerException - */ - AutoPing getAutoPing(String id) throws WebloggerException; - - - /** - * Get all of the auto ping configurations for the given website. - * - * @param website - * @return a list of auto ping configurations for the given website as AuAutoPingcode> objects. - */ - List getAutoPingsByWebsite(Weblog website) throws WebloggerException; - - - /** - * Get all of the auto ping configurations for a given target (across all websites). - * - * @param pingTarget - * @return a list of auto ping configurations for the given target as AuAutoPingcode> objects. - */ - List getAutoPingsByTarget(PingTarget pingTarget) throws WebloggerException; - - - /** - * Get the auto ping configurations that should be pinged upon creation/change of the given weblog entry. - * - * @param changedWeblogEntry the entry that has been created or changed - * @return a list of the ping configurations that should be applied due to this change - */ - List getApplicableAutoPings(WeblogEntry changedWeblogEntry) throws WebloggerException; - - - /** - * Queue the auto ping configurations that should be pinged upon change to the given weblog entry. This calls the - * {@link PingQueueManager} to queue ping requests for each ping configuration that should be applied on change to - * the given weblog entry. If ping processing is suspended, this returns without doing anything. - * - * @param changedWeblogEntry the entry that has been created or changed - */ - void queueApplicableAutoPings(WeblogEntry changedWeblogEntry) throws WebloggerException; - - /** - * Release all resources associated with Roller session. - */ - void release(); - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueManager.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueManager.java deleted file mode 100644 index 67fb4aecb..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueManager.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.business.pings; - -import java.util.List; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.pojos.AutoPing; -import org.apache.roller.weblogger.pojos.PingQueueEntry; - - -/** - * PingQueueManager. This interface describes the manager for the weblog update ping request queue. The queue is - * processed by the PingQueueProcesssor and PingQueueTask components in the application - * layer. - */ -public interface PingQueueManager { - - - /** - * Add a new persistent entry to the queue. If the queue already contains an entry for the ping target and website - * specified by this auto ping configuration, a new one will not be added. - * - * @param autoPing auto ping configuration for the ping request to be queued. - */ - void addQueueEntry(AutoPing autoPing) throws WebloggerException; - - - /** - * Store the given queue entry. - * - * @param pingQueueEntry update the given queue entry - * @throws WebloggerException - */ - void saveQueueEntry(PingQueueEntry pingQueueEntry) throws WebloggerException; - - - /** - * Remove a queue entry. - * - * @param pingQueueEntry the entry to be removed. - * @throws WebloggerException - */ - void removeQueueEntry(PingQueueEntry pingQueueEntry) throws WebloggerException; - - - /** - * Retrieve an entry from the queue. - * - * @param id the unique id of the entry. - * @return the queue entry with the specified id. - * @throws WebloggerException - */ - PingQueueEntry getQueueEntry(String id) throws WebloggerException; - - - /** - * Get all of the queue entries. - * - * @return the queue as a List of {@link PingQueueEntry objects. - * @throws WebloggerException - */ - List getAllQueueEntries() throws WebloggerException; - - - /** - * Release all resources associated with Roller session. - */ - void release(); - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueProcessor.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueProcessor.java deleted file mode 100644 index 69a08acea..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueProcessor.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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.business.pings; - -import java.util.List; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.config.PingConfig; -import org.apache.roller.weblogger.config.WebloggerRuntimeConfig; -import org.apache.roller.weblogger.pojos.PingQueueEntry; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.pojos.Weblog; - - -/** - * Ping Queue Processor. Singleton encapsulating logic for processing the weblog update ping queue. - * - * @author Anil Gangolli - */ -public final class PingQueueProcessor { - - private static final Log LOGGER = LogFactory.getLog(PingQueueProcessor.class); - - private static PingQueueProcessor theInstance; - - private PingQueueManager pingQueueMgr; - - - public static PingQueueProcessor getInstance() { - return theInstance; - } - - - private PingQueueProcessor() throws WebloggerException { - pingQueueMgr = WebloggerFactory.getWeblogger().getPingQueueManager(); - } - - - /** - * Initialize the singleton. This is called during RollerContext initialization. - * - * @throws WebloggerException - */ - public static synchronized void init() throws WebloggerException { - if (theInstance != null) { - LOGGER.warn("Ignoring duplicate initialization of PingQueueProcessor!"); - return; - } - theInstance = new PingQueueProcessor(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Ping queue processor initialized."); - } - } - - - /** - * Process the ping queue. Performs one pass through the ping queue, processing every entry once. On ping failure - * an entry is requeued for processing on subsequent passes until the configured maximum number of attempts is - * reached. - */ - public synchronized void processQueue() { - if (PingConfig.getSuspendPingProcessing()) { - LOGGER.info("Ping processing has been suspended. Skipping current round of ping queue processing."); - return; - } - - String absoluteContextUrl = WebloggerRuntimeConfig.getAbsoluteContextURL(); - if (absoluteContextUrl == null) { - LOGGER.warn("WARNING: Skipping current ping queue processing round because we cannot yet determine the site's absolute context url."); - return; - } - - // TODO: Group by ping target and ping all sites for that target? - // We're currently not taking advantage of grouping by ping target site and then sending - // all of the pings for that target at once. If it becomes an efficiency issue, we should do - // that. - - try { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Started processing ping queue."); - } - // Get all of the entries - List entries = pingQueueMgr.getAllQueueEntries(); - - // Process each entry - for (PingQueueEntry pingQueueEntry : entries) { - processQueueEntry(pingQueueEntry); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Finished processing ping queue."); - } - } catch (Exception ex) { - LOGGER.error("Unexpected exception processing ping queue! Aborting this pass of ping queue processing.", ex); - } - } - - - /** - * Process an individual ping queue entry. - * - * @param pingQueueEntry the ping queue entry - * @throws WebloggerException only if there are problems processing the queue. Exceptions from sending pings are - * handled, not thrown. - */ - private void processQueueEntry(PingQueueEntry pingQueueEntry) throws WebloggerException { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Processing ping queue entry: " + pingQueueEntry); - } - - PingTarget pingTarget = pingQueueEntry.getPingTarget(); - Weblog website = pingQueueEntry.getWebsite(); - boolean pingSucceeded = false; - if (PingConfig.getLogPingsOnly()) { - // Just log the ping and pretend it succeeded. - LOGGER.info("Logging simulated ping for ping queue entry " + pingQueueEntry); - pingSucceeded = true; - } else { - // Actually process the ping - try { - // Send the ping - WeblogUpdatePinger.sendPing(pingTarget, website); - // Consider successful ping transmission if we didn't get an exception. We don't care here - // about the result of the ping if it was transmitted. - pingSucceeded = true; - } catch (Exception ex) { - // Handle the ping error, either removing or requeuing the ping queue entry. - handlePingError(pingQueueEntry, ex); - } - } - // We do this outside of the previous try-catch because we don't want an exception here to be considered a ping error. - if (pingSucceeded) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Processed ping: " + pingQueueEntry); - } - pingQueueMgr.removeQueueEntry(pingQueueEntry); - } - } - - - /** - * Handle any ping error. - * - * @param pingQueueEntry the ping queue entry - * @param ex the exception that occurred on the ping attempt - * @throws WebloggerException - */ - private void handlePingError(PingQueueEntry pingQueueEntry, Exception ex) - throws WebloggerException { - - if ((pingQueueEntry.incrementAttempts() < PingConfig.getMaxPingAttempts()) && WeblogUpdatePinger.shouldRetry(ex)) { - // We have attempts remaining, and it looks like we should retry, - // so requeue the entry for processing on subsequent rounds - LOGGER.debug("Error on ping attempt (" + pingQueueEntry.getAttempts() + ") for " + pingQueueEntry + ": [" + ex.getMessage() + "]. Will re-queue for later attempts."); - LOGGER.debug("Error on last ping attempt was: ", ex); - pingQueueMgr.saveQueueEntry(pingQueueEntry); - } else { - // Remove the entry - LOGGER.warn("Error on ping attempt (" + pingQueueEntry.getAttempts() + ") for " + pingQueueEntry + ": [" + ex.getMessage() + "]. Entry will be REMOVED from ping queue."); - LOGGER.debug("Error on last ping attempt was: ", ex); - pingQueueMgr.removeQueueEntry(pingQueueEntry); - // TODO: mark ping target invalid? - } - } - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueTask.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueTask.java deleted file mode 100644 index 58ae0f1dc..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingQueueTask.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.business.pings; - -import java.util.Date; -import java.util.Properties; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.runnable.RollerTaskWithLeasing; -import org.apache.roller.weblogger.business.WebloggerFactory; - - -/** - * Task for processing the ping queue at fixed intervals. This is set up during context initialization by {@link - * org.apache.roller.weblogger.ui.core.RollerContext}. The queue processing interval is currently set from the configuration {@link - * org.apache.roller.weblogger.config.PingConfig} at startup time only. - * - * @author Anil Gangolli - */ -public class PingQueueTask extends RollerTaskWithLeasing { - private static Log log = LogFactory.getLog(PingQueueTask.class); - - public static final String NAME = "PingQueueTask"; - - // a unique id for this specific task instance - // this is meant to be unique for each client in a clustered environment - private String clientId = null; - - // a String description of when to start this task - private String startTimeDesc = "immediate"; - - // interval at which the task is run, default is 5 minutes - private int interval = 5; - - // lease time given to task lock - private int leaseTime = RollerTaskWithLeasing.DEFAULT_LEASE_MINS; - - - @Override - public String getClientId() { - return clientId; - } - - @Override - public Date getStartTime(Date currentTime) { - return getAdjustedTime(currentTime, startTimeDesc); - } - - @Override - public String getStartTimeDesc() { - return startTimeDesc; - } - - @Override - public int getInterval() { - return this.interval; - } - - @Override - public int getLeaseTime() { - return this.leaseTime; - } - - - public void init() throws WebloggerException { - this.init(PingQueueTask.NAME); - } - - @Override - public void init(String name) throws WebloggerException { - super.init(name); - - // get relevant props - Properties props = this.getTaskProperties(); - - // extract clientId - String client = props.getProperty("clientId"); - if(client != null) { - this.clientId = client; - } - - // extract start time - String startTimeStr = props.getProperty("startTime"); - if(startTimeStr != null) { - this.startTimeDesc = startTimeStr; - } - - // extract interval - String intervalStr = props.getProperty("interval"); - if(intervalStr != null) { - try { - this.interval = Integer.parseInt(intervalStr); - } catch (NumberFormatException ex) { - log.warn("Invalid interval: "+intervalStr); - } - } - - // extract lease time - String leaseTimeStr = props.getProperty("leaseTime"); - if(leaseTimeStr != null) { - try { - this.leaseTime = Integer.parseInt(leaseTimeStr); - } catch (NumberFormatException ex) { - log.warn("Invalid leaseTime: "+leaseTimeStr); - } - } - - // initialize queue processor - PingQueueProcessor.init(); - } - - - /** - * Run the task once. - */ - @Override - public void runTask() { - - try { - log.debug("task started"); - - PingQueueProcessor.getInstance().processQueue(); - WebloggerFactory.getWeblogger().flush(); - - log.debug("task completed"); - - } catch (WebloggerException e) { - log.error("Error while processing ping queue", e); - } catch (Exception ee) { - log.error("unexpected exception", ee); - } finally { - // always release - WebloggerFactory.getWeblogger().release(); - } - - } - - - /** - * Main method so that this task may be run from outside the webapp. - */ - public static void main(String[] args) throws Exception { - try { - PingQueueTask task = new PingQueueTask(); - task.init(); - task.run(); - System.exit(0); - } catch (WebloggerException ex) { - ex.printStackTrace(); - System.exit(-1); - } - } - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingTargetManager.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/PingTargetManager.java deleted file mode 100644 index 8438f87e9..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/PingTargetManager.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.business.pings; - -import java.util.List; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.pojos.PingTarget; - - -/** - * Manages ping targets. - * - * @author Anil Gangolli - */ -public interface PingTargetManager { - - - /** - * Store a ping target. - * - * @param pingTarget ping target data object. - * @throws WebloggerException - */ - void savePingTarget(PingTarget pingTarget) throws WebloggerException; - - - /** - * Remove a ping target. - * - * @throws WebloggerException - */ - void removePingTarget(PingTarget pingTarget) throws WebloggerException; - - - /** - * Retrieve a specific ping target by id. - * - * @param id id of the ping target to be retrieved. - * @return the ping target whose id is specified. - * @throws WebloggerException - */ - PingTarget getPingTarget(String id) throws WebloggerException; - - - /** - * Get a list of the common (shared) ping targets. - * - * @return the list of common ping targets as a List of {@link PingTarget objects - * @throws WebloggerException - */ - List getCommonPingTargets() throws WebloggerException; - - /** - * Check if the ping target name already exists in Roller. - * - * @param pingTargetName ping target name to check - * @return true if there is already a ping target with this name, false otherwise - * @throws WebloggerException - */ - boolean targetNameExists(String pingTargetName) throws WebloggerException; - - /** - * Check if the url of a ping target is well-formed. For this test, it must parse as a java.net.URL, - * with protocol http and a non-empty host portion. - * - * @param pingTargetUrl url to check. - * @return true if the pingTargetUrl property of the ping target is a well-formed url. - * @throws WebloggerException - */ - boolean isUrlWellFormed(String pingTargetUrl) throws WebloggerException; - - - /** - * Check if the host portion of the url of a ping target is known, meaning it is either a well-formed IP address - * or a hostname that resolves from the server. The ping target url must parse as a java.net.URL in - * order for the hostname to be extracted for this test. This will return false if that parsing fails. - * - * @param pingTargetUrl url to check. - * @return true if the pingTargetUrl (is well-formed and) the host portion of the url of the - * ping target is a valid IP address or a hostname that can be resolved on the server. - * @throws WebloggerException - */ - boolean isHostnameKnown(String pingTargetUrl) throws WebloggerException; - - - /** - * Release all resources associated with Roller session. - */ - void release(); - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java deleted file mode 100644 index 98919792a..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePinger.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * 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.business.pings; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.config.PingConfig; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.pojos.Weblog; -import org.apache.roller.weblogger.util.SecureXmlParsers; -import org.apache.xmlrpc.XmlRpcException; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import javax.xml.parsers.SAXParser; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Utility for sending a weblog update ping. - * - *

This implements the weblogUpdates.ping XML-RPC call described - * at www.xmlrpc.com, as well as - * some variants required to interoperate with certain buggy but popular ping - * targets. - * - *

Roller builds the request and reads the response itself rather than through - * an XML-RPC client library. The response is parsed with {@link SecureXmlParsers}, - * which refuses document type declarations and resolves nothing the document - * names, and only the flerror and message fields the - * protocol defines are read; any other content is ignored. - * - * @author Anil Gangolli - * @author llavandowska (for code refactored from the now-defunct RollerXmlRpcClient) - */ -public final class WeblogUpdatePinger { - public static final Log LOGGER = LogFactory.getLog(WeblogUpdatePinger.class); - - private static final String PING_METHOD_NAME = "weblogUpdates.ping"; - - /** How long to wait to open a connection to a ping target, in milliseconds. */ - private static final int CONNECT_TIMEOUT_MS = 15000; - - /** How long to wait for a ping target's response, in milliseconds. */ - private static final int READ_TIMEOUT_MS = 30000; - - /** Upper bound on the response we will read from a ping target, in bytes. */ - private static final int MAX_RESPONSE_BYTES = 1024 * 1024; - - /** - * Conveys a ping result. - */ - public static class PingResult { - boolean error; - String message; - - public PingResult(Boolean error, String message) { - this.error = error != null && error; - this.message = message != null ? message : ""; - } - - public boolean isError() { - return error; - } - - public void setError(boolean error) { - this.error = error; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - @Override - public String toString() { - return "PingResult{" + "error=" + error + ", message='" + message + "'" + "}"; - } - } - - // Inhibit construction - private WeblogUpdatePinger() { - } - - /** - * Send a weblog update ping. - * - * @param pingTarget the target site to ping - * @param website the website that changed (from which the ping originates) - * @return the result message string sent by the server. - * @throws IOException if an IOException occurs during the ping - * @throws XmlRpcException if the ping target reports a fault - */ - public static PingResult sendPing(PingTarget pingTarget, Weblog website) throws IOException, XmlRpcException { - String websiteUrl = website.getAbsoluteURL(); - String pingTargetUrl = pingTarget.getPingUrl(); - Set variantOptions = PingConfig.getVariantOptions(pingTargetUrl); - - // Set up the ping parameters. - List params = new ArrayList<>(); - if (!variantOptions.contains("noname")) { - // ping variant for icerocket and anyone with similar bug, where we must omit the blog name. - params.add(website.getName()); - } - params.add(websiteUrl); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing ping to '" + pingTargetUrl + "' for website '" + websiteUrl + "' (" + website.getName() + ")" + (variantOptions.isEmpty() ? "" : " with variant options " + variantOptions)); - } - - // Send the ping and read the response. - byte[] response = exchange(pingTargetUrl, buildRequest(params)); - PingResult pingResult = parseResponse(response); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Ping result is: " + pingResult); - } - return pingResult; - } - - /** - * Build the XML-RPC weblogUpdates.ping request body for the - * given string parameters. - */ - static String buildRequest(List params) { - StringBuilder sb = new StringBuilder(256); - sb.append(""); - sb.append("").append(PING_METHOD_NAME).append(""); - sb.append(""); - for (String param : params) { - sb.append(""); - xmlEscape(sb, param); - sb.append(""); - } - sb.append(""); - return sb.toString(); - } - - /** - * POST the request body to the ping target and return the response bytes, - * capped at {@link #MAX_RESPONSE_BYTES}. - */ - static byte[] exchange(String pingTargetUrl, String requestBody) throws IOException { - URL url = new URL(pingTargetUrl); - URLConnection connection = url.openConnection(); - if (!(connection instanceof HttpURLConnection)) { - // Ping targets are HTTP(S) XML-RPC endpoints. Refuse anything else - // (for example file: or ftp:) with an IOException rather than failing - // with a ClassCastException that callers do not handle. - throw new IOException("Ping target URL is not an HTTP URL: " + pingTargetUrl); - } - HttpURLConnection conn = (HttpURLConnection) connection; - try { - conn.setRequestMethod("POST"); - conn.setConnectTimeout(CONNECT_TIMEOUT_MS); - conn.setReadTimeout(READ_TIMEOUT_MS); - conn.setDoOutput(true); - conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8"); - conn.setRequestProperty("User-Agent", "Apache Roller"); - - byte[] body = requestBody.getBytes(StandardCharsets.UTF_8); - conn.setFixedLengthStreamingMode(body.length); - try (OutputStream out = conn.getOutputStream()) { - out.write(body); - } - - int status = conn.getResponseCode(); - if (status < 200 || status >= 300) { - // A non-2xx status is a transport-level failure, not a ping - // result. Surface it so the ping is treated as failed (and - // retried by the queue processor), as the old client did. - throw new IOException("Ping target returned HTTP status " + status); - } - return readCapped(conn.getInputStream()); - } finally { - conn.disconnect(); - } - } - - /** Read at most {@link #MAX_RESPONSE_BYTES} bytes from the stream. */ - private static byte[] readCapped(InputStream in) throws IOException { - if (in == null) { - return new byte[0]; - } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - byte[] chunk = new byte[8192]; - int total = 0; - int read; - while ((read = in.read(chunk)) != -1) { - total += read; - if (total > MAX_RESPONSE_BYTES) { - throw new IOException("Ping target response exceeded " + MAX_RESPONSE_BYTES + " bytes"); - } - buffer.write(chunk, 0, read); - } - return buffer.toByteArray(); - } - - /** - * Parse an XML-RPC ping response. Only flerror and - * message are read from a normal response; a fault is turned - * into an {@link XmlRpcException}. - * - * @throws XmlRpcException if the response is a fault - */ - static PingResult parseResponse(byte[] response) throws IOException, XmlRpcException { - if (response == null || response.length == 0) { - return new PingResult(null, null); - } - PingResponseHandler handler = new PingResponseHandler(); - try { - SAXParser parser = SecureXmlParsers.newSAXParserFactory().newSAXParser(); - parser.parse(new InputSource(new ByteArrayInputStream(response)), handler); - } catch (SAXException | javax.xml.parsers.ParserConfigurationException ex) { - // The target responded with something that is not a well-formed, safe - // XML-RPC response. Treat it as a non-error transmission, mirroring the - // prior lenient handling of buggy targets, without exposing the body. - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Could not parse ping response as XML-RPC; proceeding.", ex); - } - return new PingResult(null, null); - } - - if (handler.isFault()) { - throw new XmlRpcException(handler.getFaultCode(), handler.getFaultString()); - } - return new PingResult(handler.getFlerror(), handler.getMessage()); - } - - private static void xmlEscape(StringBuilder sb, String value) { - if (value == null) { - return; - } - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - switch (c) { - case '&': sb.append("&"); break; - case '<': sb.append("<"); break; - case '>': sb.append(">"); break; - case '"': sb.append("""); break; - case '\'': sb.append("'"); break; - default: sb.append(c); - } - } - } - - /** - * Collects just the fields Roller needs from an XML-RPC ping response. - * Scalar values, including any {@code }, are read only as text. - */ - private static final class PingResponseHandler extends DefaultHandler { - private final StringBuilder text = new StringBuilder(); - private final Map members = new HashMap<>(); - private boolean inFault; - private boolean inStruct; - private String memberName; - private String scalar; - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) { - text.setLength(0); - if ("fault".equals(qName)) { - inFault = true; - } else if ("struct".equals(qName)) { - inStruct = true; - } else if ("member".equals(qName)) { - memberName = null; - scalar = null; - } - } - - @Override - public void characters(char[] ch, int start, int length) { - text.append(ch, start, length); - } - - @Override - public void endElement(String uri, String localName, String qName) { - switch (qName) { - case "name": - memberName = text.toString().trim(); - break; - case "string": - case "boolean": - case "int": - case "i4": - case "double": - case "dateTime.iso8601": - case "base64": - // Record the raw text only; base64 is not decoded. - scalar = text.toString(); - break; - case "value": - if (scalar == null) { - // An XML-RPC with no type child is a string. - scalar = text.toString(); - } - break; - case "member": - if (memberName != null && inStruct) { - members.put(memberName, scalar != null ? scalar : ""); - } - scalar = null; - break; - case "struct": - inStruct = false; - break; - default: - break; - } - } - - boolean isFault() { - return inFault; - } - - int getFaultCode() { - String code = members.get("faultCode"); - if (code != null) { - try { - return Integer.parseInt(code.trim()); - } catch (NumberFormatException ignored) { - // fall through to default - } - } - return 0; - } - - String getFaultString() { - return members.get("faultString"); - } - - Boolean getFlerror() { - String value = members.get("flerror"); - if (value == null) { - return null; - } - value = value.trim(); - return "1".equals(value) || "true".equalsIgnoreCase(value); - } - - String getMessage() { - return members.get("message"); - } - } - - /** - * Decide if the given exception appears to warrant later retrial attempts. - * - * @param ex an exception thrown by the sendPing operation - * @return true if the error warrants retrial - */ - public static boolean shouldRetry(Exception ex) { - // Determine if error appears transient (warranting retrial) - // We give most errors the "benefit of the doubt" by considering them transient - // This picks out a few that we consider non-transient - if (ex instanceof UnknownHostException) { - // User probably mistyped the url in the custom target. - return false; - } else if (ex instanceof MalformedURLException) { - // This should never happen due to validations but if we get here, retrial won't fix it. - return false; - } - return true; - } - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/business/pings/package-info.java b/app/src/main/java/org/apache/roller/weblogger/business/pings/package-info.java deleted file mode 100644 index 3c7e54733..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/business/pings/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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. - */ - -/** - * Ping-queue manager and processor. - */ -package org.apache.roller.weblogger.business.pings; \ No newline at end of file diff --git a/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java b/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java index 7b2a71765..391899f9e 100644 --- a/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java +++ b/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java @@ -49,7 +49,7 @@ public class DatabaseInstaller { private static final String DBVERSION_PROP = "roller.database.version"; // Update this when adding a database or data migration step below. - private static final int LATEST_DATABASE_UPGRADE_VERSION = 610; + private static final int LATEST_DATABASE_UPGRADE_VERSION = 700; public DatabaseInstaller(DatabaseProvider dbProvider, DatabaseScriptProvider scriptProvider) { @@ -268,6 +268,12 @@ public void upgradeDatabase(boolean runScripts) throws StartupException { schemaUpgraded = true; } + if (dbversion < 700) { + upgradeTo700(con, runScripts); + dbversion = 700; + schemaUpgraded = true; + } + // make sure the database version is the exact version // we are upgrading too. updateDatabaseVersion(con, myVersion); @@ -758,6 +764,13 @@ private void upgradeTo610(Connection con, boolean runScripts) throws StartupExce simpleUpgrade(con, 520, 610, runScripts); } + /** + * Upgrade database to Roller 7.0. + */ + private void upgradeTo700(Connection con, boolean runScripts) throws StartupException { + simpleUpgrade(con, 610, 700, runScripts); + } + /** * Simple upgrade using single SQL migration script. */ diff --git a/app/src/main/java/org/apache/roller/weblogger/config/PingConfig.java b/app/src/main/java/org/apache/roller/weblogger/config/PingConfig.java deleted file mode 100644 index efbc443b9..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/config/PingConfig.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * 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.config; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.pojos.PingTarget; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -// This may need to move to a different package, but it seems appropriate here in the current structure. -// Previous placement in the presentation.pings package introduced the undesirable dependency of the -// business package on the presentation package. - -/** - * Thin wrapper around WebloggerConfig and WebloggerRuntimeConfig for centralizing access to the many configurable - * settings for pings. - * - * @author Anil Gangolli - */ -public final class PingConfig { - private static final Log LOGGER = LogFactory.getLog(PingConfig.class); - - - // Config property for maximum ping attempts. - static final String MAX_PING_ATTEMPTS_PROP = "pings.maxPingAttempts"; - private static final int MAX_PING_ATTEMPTS_DEFAULT = 3; - private static final int MAX_PING_ATTEMPTS_MIN = 1; - private static final int MAX_PING_ATTEMPTS_MAX = 10; - - // Config property for queue processing interval - private static final String QUEUE_PROCESSING_INTERVAL_PROP = "pings.queueProcessingIntervalMins"; - private static final int QUEUE_PROCESSING_INTERVAL_DEFAULT = 5; - private static final int QUEUE_PROCESSING_INTERVAL_MIN = 0; - private static final int QUEUE_PROCESSING_INTERVAL_MAX = 120; - - // PingConfig property for logging pings (not actually performing them). Used for debugging. - private static final String PINGS_LOG_ONLY_PROP = "pings.logOnly"; - private static final boolean PINGS_LOG_ONLY_DEFAULT = false; - - // PingConfig property for controlling whether or not to allow usage of pings - // ("Weblog:Pings" page and actions). If absent, this defaults to false - // NOTE: If this property name is changed, editor-menu.xml must also be adjusted. - private static final String PINGS_DISABLE_PING_USAGE_PROP = "pings.disablePingUsage"; - private static final boolean PINGS_DISABLE_PING_USAGE_DEFAULT = false; - - // PingConfig property for controlling suspending the processing of pings. If true, - // new auto ping requests are not queued, any existing queued requests are not processed, - // and sending a manual ping results in a message saying pings have been disabled. - // NOTE: This is a "runtime" property settable on the Admin:PingConfig page, default is false. - private static final String PINGS_SUSPEND_PING_PROCESSING_PROP = "pings.suspendPingProcessing"; - - // PingConfig property determining the initial common ping targets. If the list of common - // ping targets is empty on startup, the value of this property is used to populate initial values. - // The value takes the form of comma-separated ping targets where each ping target is specified in - // the form {{name}{pingurl}}. If an administrator wants to disable this initialization, in order to - // maintain an empty list of common targets, the administrator can disable the initialization by - // commenting out this property in the config file. - private static final String PINGS_INITIAL_COMMON_TARGETS_PROP = "pings.initialCommonTargets"; - - // PingConfig property determining the known WeblogUpdates.ping variants/bugs - // in popular ping targets, which we are used when invoking pings on those targets. - // The value takes the form of a comma separated list of ping target urls and - // variant options, where each one is in the form {{pingurl}{option[[,option]...]}}. - private static final String PINGS_VARIANT_OPTIONS_PROP = "pings.variantOptions"; - - // Map of configured ping variants. Maps a ping target hostname to a set of - // Strings representing variant options to be used when pinging this target. - // This was introduced in order to support certain buggy (but popular) ping - // targets that implement minor variants of the WeblogUpdates.ping call. - // This is initialized once at startup, and referenced when pings are made. - private static final Map> CONFIGURED_VARIANTS = new HashMap<>(); - - // Pattern used to parse common ping targets as well as ping variants. - // Each initial commmon ping target is specified in the format {{name}{url}} - // Ping variants are also specified in a nested brace format {{url}{options}} - private static final Pattern NESTED_BRACE_PAIR = Pattern.compile("\\{\\{(.*?)\\}\\{(.*?)\\}\\}"); - - - // Inhibit construction - private PingConfig() { - } - - /** - * Get the maximum number of ping attempts that should be made for each ping queue entry before we give up. If we - * get apparently transient failures while trying to perform the ping, the entry is requeued for processing on later - * passes through the queue until this number of attempts has been reached. - * - * @return the configured (or default) maximum number of ping attempts - */ - public static int getMaxPingAttempts() { - return getIntegerProperty(MAX_PING_ATTEMPTS_PROP, MAX_PING_ATTEMPTS_DEFAULT, - MAX_PING_ATTEMPTS_MIN, MAX_PING_ATTEMPTS_MAX); - } - - /** - * Get the ping queue processing interval in minutes. - * - * @return the configured (or default) queue processing interval in minutes. - */ - public static int getQueueProcessingIntervalMins() { - return getIntegerProperty(QUEUE_PROCESSING_INTERVAL_PROP, QUEUE_PROCESSING_INTERVAL_DEFAULT, - QUEUE_PROCESSING_INTERVAL_MIN, QUEUE_PROCESSING_INTERVAL_MAX); - } - - - /** - * Get the logs only setting. Get configuration value determining whether pings are to be logged only (not sent). - * This configuration setting is used for development and debugging. - * - * @return the configured (or default) value of the logs only setting. - */ - public static boolean getLogPingsOnly() { - return getBooleanProperty(PINGS_LOG_ONLY_PROP, PINGS_LOG_ONLY_DEFAULT); - } - - /** - * Determine whether the configuration disables ping usage (configuration of auto pings and sending of manual - * pings). If this is true, all auto ping configus are removed at startup, the Weblog:Pings UI and the associated - * actions are disabled. - * - * @return the configured (or default) value of the enable ping usage setting. - */ - public static boolean getDisablePingUsage() { - return getBooleanProperty(PINGS_DISABLE_PING_USAGE_PROP, PINGS_DISABLE_PING_USAGE_DEFAULT); - } - - /** - * Determine whether ping processing is suspended. If this is true, new auto ping requests are not - * queued, any existing queued requests are not processed, and sending a manual ping results in a message saying - * pings have been disabled. - * - * @return the configured (or default) value of the suspend ping processing setting. - */ - public static boolean getSuspendPingProcessing() { - return WebloggerRuntimeConfig.getBooleanProperty(PINGS_SUSPEND_PING_PROCESSING_PROP); - } - - /** - * Initialize the common ping targets from the configuration properties. If the current list of common ping targets - * is empty, and the PINGS_INITIAL_COMMON_TARGETS_PROP property is present in the configuration then, - * this method will use that value to initialize the common targets. This is called on each server startup. - *

- * Note: this is expected to be called during initialization with transaction demarcation being handled by the - * caller. - * - * @see org.apache.roller.weblogger.ui.core.RollerContext#contextInitialized(javax.servlet.ServletContextEvent) - */ - public static void initializeCommonTargets() throws WebloggerException { - String configuredVal = WebloggerConfig.getProperty(PINGS_INITIAL_COMMON_TARGETS_PROP); - if (configuredVal == null || configuredVal.isBlank()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("No (or empty) value of " + PINGS_INITIAL_COMMON_TARGETS_PROP + " present in the configuration. Skipping initialization of commmon targets."); - } - return; - } - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - if (!pingTargetMgr.getCommonPingTargets().isEmpty()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Some common ping targets are present in the database already. Skipping initialization."); - } - return; - } - - String[] configuredTargets = configuredVal.trim().split(","); - for (int i = 0; i < configuredTargets.length; i++) { - // Trim space around the target spec - String thisTarget = configuredTargets[i].trim(); - // skip empty ones - if (thisTarget.length() == 0) { - continue; - } - // parse the ith target and store it - Matcher m = NESTED_BRACE_PAIR.matcher(thisTarget); - if (m.matches() && m.groupCount() == 2) { - String name = m.group(1).trim(); - String url = m.group(2).trim(); - LOGGER.info("Creating common ping target '" + name + "' from configuration properties."); - PingTarget pingTarget = new PingTarget(null, name, url, false); - pingTargetMgr.savePingTarget(pingTarget); - } else { - LOGGER.error("Unable to parse configured initial ping target '" + thisTarget + "'. Skipping this target. Check your setting of the property " + PINGS_INITIAL_COMMON_TARGETS_PROP); - } - } - } - - /** - * Initialize known ping variants from the configuration. - */ - public static void initializePingVariants() { - String configuredVal = WebloggerConfig.getProperty(PINGS_VARIANT_OPTIONS_PROP); - if (configuredVal == null || configuredVal.isBlank()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("No (or empty) value of " + PINGS_VARIANT_OPTIONS_PROP + " present in the configuration. Skipping initialization of ping variants."); - } - return; - } - String[] variants = configuredVal.trim().split(","); - for (int i = 0; i < variants.length; i++) { - String thisVariant = variants[i].trim(); - if (thisVariant.length() == 0) { - continue; - } - Matcher m = NESTED_BRACE_PAIR.matcher(thisVariant); - if (m.matches() && m.groupCount() == 2) { - String url = m.group(1).trim(); - String optionsList = m.group(2).trim(); - Set variantOptions = new HashSet<>(); - String[] options = optionsList.split(","); - for (int j = 0; j < options.length; j++) { - String option = options[j].trim().toLowerCase(); - if (option.length() > 0) { - variantOptions.add(option); - } - } - if (!variantOptions.isEmpty()) { - CONFIGURED_VARIANTS.put(url, variantOptions); - } else { - LOGGER.warn("Ping variant entry for url '" + url - + "' has an empty variant options list. Ignored."); - } - } else { - LOGGER.error("Unable to parse configured ping variant '" - + thisVariant + "'. Skipping this variant. Check your setting of the property " - + PINGS_VARIANT_OPTIONS_PROP); - } - } - } - - /** - * Get the set of variant options configured for the given ping target url. - * - * @param pingTargetUrl - * @return the set of variant options configured for the given ping target url, or - * the empty set if there are no variants configured. - */ - public static Set getVariantOptions(String pingTargetUrl) { - Set variantOptions = CONFIGURED_VARIANTS.get(pingTargetUrl); - if (variantOptions == null) { - variantOptions = Collections.emptySet(); - } - return variantOptions; - } - - - // TODO: Refactor functionality below to WebloggerConfig? - - /** - * Get the value of an integer configuration property. - * - * @param propName the property name - * @param defaultValue the default value if the property is not present - * @param min the minimum allowed value - * @param max the maximum allowed value - * @return the value as an integer; the default value if no configured value is present or if the configured value - * is out of the specified range. - */ - private static int getIntegerProperty(String propName, int defaultValue, int min, int max) { - String configuredVal = WebloggerConfig.getProperty(propName); - if (configuredVal == null) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("PingConfig property '" + propName - + "' is not present in the configuration. Using default value: " + defaultValue); - } - return defaultValue; - } - - int val; - try { - val = Integer.parseInt(configuredVal); - } catch (NumberFormatException ex) { - LOGGER.error("ERROR: PingConfig property '" + propName - + "' is not an integer value. Using default value: " + defaultValue); - return defaultValue; - } - - if (val < min || val > max) { - LOGGER.error("ERROR: PingConfig property '" + propName - + "' is outside the required range (" - + min + ", " + max + "). Using default value: " + defaultValue); - return defaultValue; - } - - return val; - } - - /** - * Get the value of a boolean property with specified default. - * - * @param propName the property name - * @param defaultValue the default value if the property is not present - * @return the configured value or the default if it the configured value is not present. - */ - private static boolean getBooleanProperty(String propName, boolean defaultValue) { - String configuredVal = WebloggerConfig.getProperty(propName); - if (configuredVal == null) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("PingConfig property '" + propName - + "' is not present in the configuration. Using default value: " + defaultValue); - } - return defaultValue; - } - return Boolean.valueOf(configuredVal); - } - - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/pojos/AutoPing.java b/app/src/main/java/org/apache/roller/weblogger/pojos/AutoPing.java deleted file mode 100644 index aded88d12..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/pojos/AutoPing.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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.pojos; - -import java.io.Serializable; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.roller.util.UUIDGenerator; - - -/** - * Automatic ping configuration. An instance of this class relates a website - * and ping target; it indicates that the specified ping target should be pinged - * when the corresponding website is changed. - * - * @author Anil Gangolli - */ -public class AutoPing implements Serializable { - - private String id = UUIDGenerator.generateUUID(); - private PingTarget pingTarget = null; - private Weblog website = null; - - public static final long serialVersionUID = -9105985454111986435L; - - - /** - * Default constructor leaves all fields null. Required for bean compliance. - */ - public AutoPing() { - } - - /** - * Constructor. - * @param id unique id (primary key) for this instance - * @param pingtarget ping target that should be pinged - * @param website website to which this configuration applies - */ - public AutoPing(String id, PingTarget pingtarget, Weblog website) { - //this.id = id; - this.website = website; - this.pingTarget = pingtarget; - } - - /** - * Get the unique id (primary key) of this object. - * @return the unique id of this object. - */ - public String getId() { - return id; - } - - /** - * Set the unique id (primary key) of this object - * @param id - */ - public void setId(String id) { - // Form bean workaround: empty string is never a valid id - if (id != null && id.isBlank()) { - return; - } - this.id = id; - } - - /** - * Get the website. Get the website whose changes should result in a ping - * to the ping target specified by this object. - * @return the website. - */ - public Weblog getWebsite() { - return website; - } - - /** - * Set the website. Set the website whose changes should result in a ping - * to the ping target specified by this object. - * @param website the website. - */ - public void setWebsite(Weblog website) { - this.website = website; - } - - /** - * Get the ping target. Get the target to be pinged when the - * corresponding website changes. - * @return the target to be pinged. - */ - public PingTarget getPingTarget() { - return pingTarget; - } - - /** - * Set the ping target. Set the target to be pinged when the - * corresponding website changes. - * @param pingtarget the target to be pinged. - */ - public void setPingTarget(PingTarget pingtarget) { - this.pingTarget = pingtarget; - } - - //------------------------------------------------------- Good citizenship - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("{"); - buf.append(getId()); - buf.append("}"); - return buf.toString(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof AutoPing)) { - return false; - } - AutoPing o = (AutoPing)other; - return new EqualsBuilder() - .append(getId(), o.getId()) - .append(getPingTarget(), o.getPingTarget()) - .append(getWebsite(), o.getWebsite()) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder().append(getId()).toHashCode(); - } - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/pojos/PingQueueEntry.java b/app/src/main/java/org/apache/roller/weblogger/pojos/PingQueueEntry.java deleted file mode 100644 index 309c0bfc3..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/pojos/PingQueueEntry.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.pojos; - -import java.io.Serializable; -import java.sql.Timestamp; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.roller.util.UUIDGenerator; - - -/** - * Ping queue entry. Each instance of this class represents an entry on the ping queue. The entry indicates when it was - * added to the queue, which configuration to apply for the ping, and the number of ping attempts that have been made - * for this entry so far. - * - * @author Anil Gangolli - */ -public class PingQueueEntry implements Serializable { - - private String id = UUIDGenerator.generateUUID(); - private Timestamp entryTime = null; - private PingTarget pingTarget = null; - private Weblog website = null; - private int attempts = 0; - - public static final long serialVersionUID = -1468021030819538243L; - - - /** - * Default constructor. Leaves all fields at Java-specified default values. - */ - public PingQueueEntry() { - } - - /** - * Construct with all members - * - * @param id unique id of this entry - * @param entryTime timestamp of first entry onto queue - * @param pingTarget target site to ping - * @param website website originating the ping - * @param attempts number of prior ping attempts - */ - public PingQueueEntry(String id, Timestamp entryTime, PingTarget pingTarget, Weblog website, int attempts) { - //this.id = id; - this.entryTime = entryTime; - this.pingTarget = pingTarget; - this.website = website; - this.attempts = attempts; - } - - /** - * Get the unique id (primary key) of this object. - * - * @return the unique id of this object. - */ - public String getId() { - return id; - } - - /** - * Set the unique id (primary key) of this object. - * - * @param id - */ - public void setId(String id) { - this.id = id; - } - - /** - * Get the entry time. Get the time this entry was first added to the queue. - * - * @return the time the entry was first added to the queue. - */ - public Timestamp getEntryTime() { - return entryTime; - } - - /** - * Set the entry time. - * - * @param entryTime the time the entry was first added to the queue. - */ - public void setEntryTime(Timestamp entryTime) { - this.entryTime = entryTime; - } - - /** - * Get the ping target. Get the target to ping. - * - * @return the ping target to ping. - */ - public PingTarget getPingTarget() { - return pingTarget; - } - - /** - * Set the ping target. - * - * @param pingTarget target to ping. - */ - public void setPingTarget(PingTarget pingTarget) { - this.pingTarget = pingTarget; - } - - /** - * Get the website originating the ping. - * - * @return the website originating the ping. - */ - public Weblog getWebsite() { - return website; - } - - /** - * Set the website originating the ping. - * - * @param website the website originating the ping. - */ - public void setWebsite(Weblog website) { - this.website = website; - } - - /** - * Get the number of ping attempts that have been made for this queue entry. - * - * @return the number of ping attempts that have been made for this queue entry. - */ - public int getAttempts() { - return attempts; - } - - /** - * Set the number of failures that have occurred for this queue entry. - * - * @param attempts - */ - public void setAttempts(int attempts) { - this.attempts = attempts; - } - - /** - * Increment the number of failures for this queue entry. - * - * @return the new value. - */ - public int incrementAttempts() { - int newAttempts = getAttempts() + 1; - setAttempts(newAttempts); - return newAttempts; - } - - //------------------------------------------------------- Good citizenship - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("{"); - buf.append(getId()); - buf.append(", ").append(getEntryTime()); - buf.append(", ").append(getAttempts()); - buf.append("}"); - return buf.toString(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof PingQueueEntry)) { - return false; - } - PingQueueEntry o = (PingQueueEntry)other; - return new EqualsBuilder() - .append(getEntryTime(), o.getEntryTime()) - .append(getWebsite(), o.getWebsite()) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder() - .append(getEntryTime()) - .append(getWebsite()) - .toHashCode(); - } - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/pojos/PingTarget.java b/app/src/main/java/org/apache/roller/weblogger/pojos/PingTarget.java deleted file mode 100644 index af2b1c3ef..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/pojos/PingTarget.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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.pojos; - -import java.io.Serializable; -import java.sql.Timestamp; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.roller.util.UUIDGenerator; - - -/** - * Ping target. Each instance represents a possible target of a weblog update ping that we send. - * - * @author Anil Gangolli - */ -public class PingTarget implements Serializable { - - public static final long serialVersionUID = -6354583200913127874L; - - // last use (after possible retrials) was successful - public static final int CONDITION_OK = 0; - // last use failed after retrials - public static final int CONDITION_FAILING = 1; - // disabled by failure policy after failures - editing resets - public static final int CONDITION_DISABLED = 2; - - private String id = UUIDGenerator.generateUUID(); - private String name = null; - private String pingUrl = null; - private int conditionCode = -1; - private Timestamp lastSuccess = null; - private boolean autoEnabled = false; - - - /** - * Default empty constructor. - */ - public PingTarget() { - } - - - /** - * Constructor. - * - * @param id the id (primary key) of this target - * @param name the descriptive name of this target - * @param pingUrl the URL to which to send the ping - * @param autoEnable if true, pings sent to target by default - */ - public PingTarget(String id, String name, String pingUrl, boolean autoEnable) { - //this.id = id; - this.name = name; - this.pingUrl = pingUrl; - this.conditionCode = CONDITION_OK; - this.lastSuccess = null; - this.autoEnabled = autoEnable; - } - - - /** - * Get the unique id of this ping target. - * - * @return the unique id of this ping target. - */ - public String getId() { - return this.id; - } - - - /** - * Set the unique id of this ping target - * - * @param id - */ - public void setId(String id) { - this.id = id; - } - - - /** - * get the name of this ping target. This is a name assigned by the administrator or a user (for custom) targets. - * It is descriptive and is not necessarily unique. - * - * @return the name of this ping target - */ - public String getName() { - return this.name; - } - - - /** - * Set the name of this ping target. - * - * @param name the name of this ping target - */ - public void setName(String name) { - this.name = name; - } - - - /** - * Get the URL to ping. - * - * @return the URL to ping. - */ - public String getPingUrl() { - return pingUrl; - } - - - /** - * Set the URL to ping. - * - * @param pingUrl - */ - public void setPingUrl(String pingUrl) { - this.pingUrl = pingUrl; - } - - /** - * Get the condition code value. This code, in combination with the last success timestamp, provides a status - * indicator on the ping target based on its usage by the ping queue processor. It can be used to implement a - * failure-based disabling policy. - * - * @return one of the condition codes {@link #CONDITION_OK}, {@link #CONDITION_FAILING}, {@link - * #CONDITION_DISABLED}. - */ - public int getConditionCode() { - return conditionCode; - } - - /** - * Set the condition code value. - * - * @param conditionCode the condition code value to set - */ - public void setConditionCode(int conditionCode) { - this.conditionCode = conditionCode; - } - - - /** - * Get the timestamp of the last successful ping (UTC/GMT). - * - * @return the timestamp of the last successful ping; null if the target has not yet been used. - */ - public Timestamp getLastSuccess() { - return lastSuccess; - } - - - /** - * Set the timestamp of the last successful ping. - * - * @param lastSuccess the timestamp of the last successful ping. - */ - public void setLastSuccess(Timestamp lastSuccess) { - this.lastSuccess = lastSuccess; - } - - - /** - * Is this ping target enabled by default for new weblogs? - * - * @return true if ping target is auto enabled. false otherwise. - */ - public boolean isAutoEnabled() { - return autoEnabled; - } - - - /** - * Set the auto enabled status for this ping target. This field only - * applies for common ping targets. - * - * @param autoEnabled true if the ping target should be auto enabled. - */ - public void setAutoEnabled(boolean autoEnabled) { - this.autoEnabled = autoEnabled; - } - - - //------------------------------------------------------- Good citizenship - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("{"); - buf.append(getId()); - buf.append(", ").append(getName()); - buf.append(", ").append(getPingUrl()); - buf.append(", ").append(getLastSuccess()); - buf.append(", ").append(isAutoEnabled()); - buf.append("}"); - return buf.toString(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (!(other instanceof PingTarget)) { - return false; - } - PingTarget o = (PingTarget)other; - return new EqualsBuilder() - .append(getId(), o.getId()) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder() - .append(getId()) - .toHashCode(); - } - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/pojos/Weblog.java b/app/src/main/java/org/apache/roller/weblogger/pojos/Weblog.java index 46904f1b7..b19957fd7 100644 --- a/app/src/main/java/org/apache/roller/weblogger/pojos/Weblog.java +++ b/app/src/main/java/org/apache/roller/weblogger/pojos/Weblog.java @@ -497,9 +497,6 @@ public void setCommentModerationRequired(boolean modRequired) {} * This includes a change to weblog settings, entries, themes, templates, * comments, categories, bookmarks, folders, etc. * - * Pings are explicitly not included because pings do not - * affect visible changes to a weblog. - * */ public Date getLastModified() { return lastModified; diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetBean.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetBean.java deleted file mode 100644 index ee89d3751..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetBean.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.struts2.admin; - -import org.apache.roller.weblogger.pojos.PingTarget; - - -/** - * Form bean used by ping target actions. - */ -public class PingTargetBean { - - private String id = null; - private String name = null; - private String pingUrl = null; - - - public String getId() { - return this.id; - } - - public void setId( String id ) { - this.id = id; - } - - public String getName() { - return this.name; - } - - public void setName( String name ) { - this.name = name; - } - - public String getPingUrl() { - return this.pingUrl; - } - - public void setPingUrl( String pingUrl ) { - this.pingUrl = pingUrl; - } - - - public void copyTo(PingTarget dataHolder) { - - dataHolder.setName(this.name); - dataHolder.setPingUrl(this.pingUrl); - } - - - public void copyFrom(PingTarget dataHolder) { - - this.id = dataHolder.getId(); - this.name = dataHolder.getName(); - this.pingUrl = dataHolder.getPingUrl(); - } - -} diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEdit.java deleted file mode 100644 index f267bcf28..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEdit.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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.struts2.admin; - -import jakarta.servlet.http.HttpServletRequest; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.ui.struts2.util.UIAction; -import org.apache.struts2.convention.annotation.AllowedMethods; -import org.apache.struts2.interceptor.parameter.StrutsParameter; - -/** - * Add or modify a common ping target. - */ -// TODO: make this work @AllowedMethods({"execute","save"}) -public class PingTargetEdit extends UIAction { - - private static Log log = LogFactory.getLog(PingTargetEdit.class); - - // a bean for managing submitted data - private PingTargetBean bean = new PingTargetBean(); - - // ping target we are working on, if any - private PingTarget pingTarget = null; - - private String httpMethod = "GET"; - - public PingTargetEdit() { - this.desiredMenu = "admin"; - } - - @Override - public void setPageTitle(String pageTitle) { - this.pageTitle = pageTitle; - } - - // no weblog required - @Override - public boolean isWeblogRequired() { - return false; - } - - @Override - public void myPrepare() { - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - - if (!StringUtils.isEmpty(getBean().getId())) { - // edit case - try { - pingTarget = pingTargetMgr.getPingTarget(getBean().getId()); - } catch (WebloggerException ex) { - log.error("Error looking up ping target - " + getBean().getId()); - } - if (pingTarget == null) { - addError("pingTarget.notFound", getBean().getId()); - } - } else { - // add case - pingTarget = new PingTarget(); - pingTarget.setConditionCode(PingTarget.CONDITION_OK); - pingTarget.setAutoEnabled(false); - } - } - - /** - * Save ping target. - */ - @Override - public String execute() { - if (!"POST".equals(httpMethod)) { - return INPUT; - } - - myValidate(); - - if (!hasActionErrors()) { - try { - // copy data from form into ping target - getBean().copyTo(pingTarget); - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - pingTargetMgr.savePingTarget(pingTarget); - WebloggerFactory.getWeblogger().flush(); - - addMessage(isAdd() ? "pingTarget.created" : "pingTarget.updated", pingTarget.getName()); - - return SUCCESS; - } catch (WebloggerException ex) { - log.error("Error adding/editing ping target", ex); - addError("generic.error.check.logs"); - } - } - - return INPUT; - } - - /** - * Private helper to validate a ping target. - */ - protected void myValidate() { - try { - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - if (StringUtils.isEmpty(bean.getName())) { - addError("pingTarget.nameMissing"); - } else { - if (isAdd() || !pingTarget.getName().equals(bean.getName())) { - if (pingTargetMgr.targetNameExists(bean.getName())) { - addError("pingTarget.nameNotUnique"); - } - } - } - if (StringUtils.isEmpty(bean.getPingUrl())) { - addError("pingTarget.pingUrlMissing"); - } else { - if (!pingTargetMgr.isUrlWellFormed(bean.getPingUrl())) { - addError("pingTarget.malformedUrl"); - } else if (!pingTargetMgr.isHostnameKnown(bean.getPingUrl())) { - addError("pingTarget.unknownHost"); - } - } - } catch (WebloggerException ex) { - log.error("Error validating ping target", ex); - addError("generic.error.check.logs"); - } - } - - private boolean isAdd() { - return actionName.equals("commonPingTargetAdd"); - } - - @StrutsParameter(depth = 1) - public PingTargetBean getBean() { - return bean; - } - - public void setBean(PingTargetBean bean) { - this.bean = bean; - } - - @Override - public void withServletRequest(HttpServletRequest req) { - super.withServletRequest(req); - httpMethod = req.getMethod(); - } -} diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java deleted file mode 100644 index 574845df5..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargets.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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.struts2.admin; - -import java.util.Collections; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.ui.struts2.util.UIAction; -import org.apache.struts2.convention.annotation.AllowedMethods; -import org.apache.struts2.interceptor.parameter.StrutsParameter; - -/** - * Admin action for managing global ping targets. - */ -// TODO: make this work @AllowedMethods({"execute","enable","disable","delete","deleteConfirm"}) -public class PingTargets extends UIAction { - - private static Log log = LogFactory.getLog(PingTargets.class); - - public PingTargets() { - this.actionName = "commonPingTargets"; - this.desiredMenu = "admin"; - this.pageTitle = "commonPingTargets.commonPingTargets"; - } - - // list of available ping targets - private List pingTargets = Collections.emptyList(); - - // ping target we are working on, if any - private PingTarget pingTarget = null; - - // id of the ping target to work on - private String pingTargetId = null; - - // no weblog required - @Override - public boolean isWeblogRequired() { - return false; - } - - public void loadPingTargets() { - try { - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - setPingTargets(pingTargetMgr.getCommonPingTargets()); - } catch (WebloggerException ex) { - log.error("Error loading common ping targets", ex); - addError("commonPingTargets.error.loading"); - } - } - - // prepare method needs to set ping targets list - @Override - public void myPrepare() { - - // load list of ping targets - loadPingTargets(); - - // The ping target always comes from the stored targets, by id. - pingTarget = null; - - // load specified ping target if possible - if(!StringUtils.isEmpty(getPingTargetId())) { - try { - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - setPingTarget(pingTargetMgr.getPingTarget(getPingTargetId())); - } catch (WebloggerException ex) { - log.error("Error looking up ping target - " + getPingTargetId(), ex); - } - } - } - - /** - * Display the ping targets. - */ - @Override - public String execute() { - return LIST; - } - - /** - * Set a ping target auto enabled to true. - */ - public String enable() { - - if(getPingTarget() != null) { - try { - getPingTarget().setAutoEnabled(true); - - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - pingTargetMgr.savePingTarget(getPingTarget()); - WebloggerFactory.getWeblogger().flush(); - } catch (Exception ex) { - log.error("Error saving ping target", ex); - addError("commonPingTargets.error.saving"); - } - } else { - addError("commonPingTargets.error.enabling"); - } - - return DONE; - } - - - /** - * Set a ping target auto-enable to false. - */ - public String disable() { - - if(getPingTarget() != null) { - try { - getPingTarget().setAutoEnabled(false); - - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - pingTargetMgr.savePingTarget(getPingTarget()); - WebloggerFactory.getWeblogger().flush(); - } catch (Exception ex) { - log.error("Error saving ping target", ex); - addError("commonPingTargets.error.saving"); - } - } else { - addError("commonPingTargets.error.disabling"); - } - - return DONE; - } - - /** - * Delete a ping target (load delete confirmation view). - */ - public String deleteConfirm() { - - if(getPingTarget() != null) { - setPageTitle("pingTarget.confirmRemoveTitle"); - - return "confirm"; - } else { - addError("pingTarget.notFound",getPingTargetId()); - } - - return LIST; - } - - - /** - * Delete a ping target. - */ - public String delete() { - if(getPingTarget() != null) { - try { - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - pingTargetMgr.removePingTarget(getPingTarget()); - WebloggerFactory.getWeblogger().flush(); - - // remove deleted target from list - getPingTargets().remove(getPingTarget()); - addMessage("pingTarget.deleted", getPingTarget().getName()); - } catch (WebloggerException ex) { - log.error("Error deleting ping target - " + getPingTargetId(), ex); - addError("generic.error.check.logs", getPingTargetId()); - } - } else { - addError("pingTarget.notFound", getPingTargetId()); - } - return LIST; - } - - public List getPingTargets() { - return pingTargets; - } - - public void setPingTargets(List pingTargets) { - this.pingTargets = pingTargets; - } - - public PingTarget getPingTarget() { - return pingTarget; - } - - private void setPingTarget(PingTarget pingTarget) { - this.pingTarget = pingTarget; - } - - public String getPingTargetId() { - return pingTargetId; - } - - @StrutsParameter - public void setPingTargetId(String pingTargetId) { - this.pingTargetId = pingTargetId; - } -} diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java index a3e1ae7cb..329d065ce 100644 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java +++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/EntryEdit.java @@ -314,11 +314,6 @@ String save() { // notify caches CacheManager.invalidate(weblogEntry); - // Queue applicable pings for this update. - if (weblogEntry.isPublished()) { - WebloggerFactory.getWeblogger().getAutopingManager() - .queueApplicableAutoPings(weblogEntry); - } if (weblogEntry.isPending() && MailUtil.isMailConfigured()) { MailUtil.sendPendingEntryNotice(weblogEntry); diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java deleted file mode 100644 index 15600788b..000000000 --- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/editor/Pings.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * 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.struts2.editor; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.WebloggerException; -import org.apache.roller.weblogger.config.PingConfig; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.pojos.AutoPing; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.business.pings.WeblogUpdatePinger; -import org.apache.struts2.convention.annotation.AllowedMethods; -import org.apache.struts2.interceptor.parameter.StrutsParameter; -import org.apache.xmlrpc.XmlRpcException; -import java.io.IOException; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.apache.roller.weblogger.pojos.WeblogPermission; -import org.apache.roller.weblogger.ui.struts2.util.UIAction; - - -/** - * Actions for setting up automatic ping configuration for a weblog. - */ -// TODO: make this work @AllowedMethods({"execute","enable","disable","pingNow"}) -public class Pings extends UIAction { - - private static final Log log = LogFactory.getLog(Pings.class); - - // ping target id to work on - private String pingTargetId = null; - - // ping target object we are working on, if available - private PingTarget pingTarget = null; - - // commong ping targets list - private List commonPingTargets = Collections.emptyList(); - - // track the enabled/disabled status for pings - private Map pingStatus = Collections.emptyMap(); - - - public Pings() { - this.actionName = "pings"; - this.desiredMenu = "editor"; - this.pageTitle = "pings.title"; - } - - - // admin perms required - public String requireWeblogPermissions() { - return WeblogPermission.ADMIN; - } - - - @Override - public void myPrepare() { - - PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - - // The ping target always comes from the stored ping targets, by id. - pingTarget = null; - - // load selected ping target, if possible - if(getPingTargetId() != null) { - try { - setPingTarget(pingTargetMgr.getPingTarget(getPingTargetId())); - } catch (WebloggerException ex) { - log.error("Error looking up ping target - "+getPingTargetId(), ex); - } - } - - try { - // load common ping targets list - setCommonPingTargets(pingTargetMgr.getCommonPingTargets()); - } catch (WebloggerException ex) { - log.error("Error loading ping target lists for weblog - "+getActionWeblog().getHandle(), ex); - addError("Error loading ping targets"); - } - } - - - /* - * Display the common ping targets with page - */ - @Override - public String execute() { - - // load map of enabled auto pings - buildIsEnabledMap(); - - return LIST; - } - - - /** - * Enable a ping target. - */ - public String enable() { - - if(getPingTarget() != null) { - try { - AutoPingManager autoPingMgr = WebloggerFactory.getWeblogger().getAutopingManager(); - AutoPing autoPing = new AutoPing(null, getPingTarget(), getActionWeblog()); - autoPingMgr.saveAutoPing(autoPing); - WebloggerFactory.getWeblogger().flush(); - } catch(Exception ex) { - log.error("Error saving auto ping for target - "+getPingTargetId(), ex); - addError("Error enabling auto ping"); - } - } - - return DONE; - } - - - /** - * Disable a ping target. - */ - public String disable() { - - if(getPingTarget() != null) { - try { - AutoPingManager autoPingMgr = WebloggerFactory.getWeblogger().getAutopingManager(); - autoPingMgr.removeAutoPing(getPingTarget(), getActionWeblog()); - WebloggerFactory.getWeblogger().flush(); - } catch (Exception ex) { - log.error("Error removing auto ping for target - "+getPingTargetId(), ex); - addError("Error disabling auto ping"); - } - } - - return DONE; - } - - - /** - * Ping the selected target now. - */ - public String pingNow() { - - if(getPingTarget() != null) { - try { - if (PingConfig.getSuspendPingProcessing()) { - log.debug("Ping processing is disabled."); - addError("ping.pingProcessingIsSuspended"); - } else { - WeblogUpdatePinger.PingResult pingResult = WeblogUpdatePinger.sendPing(getPingTarget(), getActionWeblog()); - if (pingResult.isError()) { - log.debug("Ping Result: " + pingResult); - if (pingResult.getMessage() != null && !pingResult.getMessage().isBlank()) { - addError("ping.transmittedButError"); - addError(pingResult.getMessage()); - } else { - addError("ping.transmissionFailed"); - } - } else { - addMessage("ping.successful"); - } - } - } catch (IOException ex) { - log.debug(ex); - addError("ping.transmissionFailed"); - addSpecificMessages(ex); - } catch (XmlRpcException ex) { - log.debug(ex); - addError("ping.transmissionFailed"); - addSpecificMessages(ex); - } - } - - return DONE; - } - - - // some extra error messaging - private void addSpecificMessages(Exception ex) { - if (ex instanceof UnknownHostException) { - addError("ping.unknownHost"); - } else if (ex instanceof SocketException) { - addError("ping.networkConnectionFailed"); - } - } - - - /** - * Private helper to build a map indexed by ping target id with values Boolean.TRUE and Boolean.FALSE - * based on whether the ping target is enabled (has a corresponding auto ping configuration). - */ - private void buildIsEnabledMap() { - - AutoPingManager autoPingMgr = WebloggerFactory.getWeblogger().getAutopingManager(); - - // Build isEnabled map (keyed by ping target id and values Boolean.TRUE/Boolean.FALSE) - Map isEnabled = new HashMap<>(); - - List autoPings = Collections.emptyList(); - try { - autoPings = autoPingMgr.getAutoPingsByWebsite(getActionWeblog()); - } catch (WebloggerException ex) { - log.error("Error looking up auto pings for weblog - "+getActionWeblog().getHandle(), ex); - } - - // Add the enabled auto ping configs with TRUE - for (AutoPing autoPing : autoPings) { - isEnabled.put(autoPing.getPingTarget().getId(), Boolean.TRUE); - } - - // Add disabled ping targets ones with FALSE - for (PingTarget inPingTarget : getCommonPingTargets()) { - if (isEnabled.get(inPingTarget.getId()) == null) { - isEnabled.put(inPingTarget.getId(), Boolean.FALSE); - } - } - - if (!isEnabled.isEmpty()) { - setPingStatus(isEnabled); - } - } - - - public String getPingTargetId() { - return pingTargetId; - } - - @StrutsParameter - public void setPingTargetId(String pingTargetId) { - this.pingTargetId = pingTargetId; - } - - public PingTarget getPingTarget() { - return pingTarget; - } - - private void setPingTarget(PingTarget pingTarget) { - this.pingTarget = pingTarget; - } - - public List getCommonPingTargets() { - return commonPingTargets; - } - - public void setCommonPingTargets(List commonPingTargets) { - this.commonPingTargets = commonPingTargets; - } - - public Map getPingStatus() { - return pingStatus; - } - - public void setPingStatus(Map pingStatus) { - this.pingStatus = pingStatus; - } -} diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties index 888f3f87f..d2b681ea8 100644 --- a/app/src/main/resources/ApplicationResources.properties +++ b/app/src/main/resources/ApplicationResources.properties @@ -338,7 +338,6 @@ configForm.frontpageWeblogHandle=Weblog to serve as frontpage blog configForm.frontpageWeblogAggregated=Enable aggregated site-wide frontpage configForm.siteAdminEmail=Site Administrator''s email address configForm.absoluteUrl=Absolute URL to site (if required) -configForm.suspendPingProcessing=Suspend all ping processing? configForm.allowUserWeblogCreation=Allow users to create new weblogs? configForm.none=None specified @@ -1100,73 +1099,6 @@ Template.error.descriptionSize=Description cannot be more than 255 characters Template.error.actionNull=The Action is a required field Template.error.actionNotAvailable=A {0} template cannot be added to this weblog -# ---------------------------------------------------- Ping Target Admin/Editing - - - -commonPingTargets.subtitle=Ping target management -commonPingTargets.commonPingTargets=Weblog Ping Targets -commonPingTargets.explanation=These target sites are available to all weblogs \ -for weblog update pings. -commonPingTargets.error.loading=Error loading common ping targets -commonPingTargets.error.saving=Error saving common ping targets -commonPingTargets.error.enabling=Error enabling ping target -commonPingTargets.error.disabling=Error disabling ping target - -pings.title=Configure Automatic Weblog Pings -pings.subtitle=Setup automatic pings for weblog {0} -pings.explanation=Pings allow you to notify sites that your weblog has changed \ -so that the sites can read your feed to retrieve updates. \ -You can enable automatic pings for sites that you wish to notify whenever \ -your weblog changes. You can also trigger pings manually \ -to specific sites from this page. Note: Normally, you are \ -expected to register your weblog with a site before starting \ -to send that site pings. -pings.commonPingTargets=Common Ping Targets -pings.commonPingTargetsExplanation=These ping targets have been configured by the \ -site administrator for use by all users. \ -You can send pings to any of these well-known sites. - -ping.successful=The ping was successful. -ping.transmittedButError=The ping was transmitted but the server \ -responded with the following error message. -ping.transmissionFailed=The ping transmission failed. Check to make sure the \ -ping target URL is correct. -ping.unknownHost=The hostname in the ping target URL is unknown. -ping.networkConnectionFailed=There were network connection problems reaching the \ -ping target site. -ping.pingProcessingIsSuspended=Ping processing has been suspended temporarily by \ -a site administrator. - -pingTarget.addTarget=Add Ping Target -pingTarget.editTarget=Edit Ping Target - -pingTargetEdit.subtitle=Edit ping target -pingTargetAdd.subtitle=Add ping target - -pingTarget.pingUrl=Ping URL -pingTarget.autoEnabled=Auto Ping? -pingTarget.remove=Remove -pingTarget.confirmRemoveTitle=Remove Ping Target Confirmation -pingTarget.confirmCommonRemove=Removing this target will also remove all user''s \ -automatic ping configurations that use this target. Are you sure? -pingTarget.enable=Enable -pingTarget.enabled=ON -pingTarget.disable=Disable -pingTarget.disabled=OFF -pingTarget.auto=Automatic -pingTarget.manual=Manual -pingTarget.sendPingNow=Send Ping Now -pingTarget.updated=Ping target "{0}" updated -pingTarget.created=Ping target "{0}" created -pingTarget.deleted=Ping target "{0}" deleted - -pingTarget.nameNotUnique=Target name already exists, please choose another. -pingTarget.nameMissing=Name is required -pingTarget.pingUrlMissing=Ping URL is required -pingTarget.malformedUrl=The URL is not properly formed. -pingTarget.unknownHost=The hostname in this URL doesn''t seem to exist. -pingTarget.notFound=Ping target id {0} was not found # --------------------------------------------------------------- Planet Roller: config @@ -1342,7 +1274,6 @@ tabbedmenu.website.themes=Theme tabbedmenu.website.pages=Templates tabbedmenu.website.members=Members tabbedmenu.website.maintenance=Maintenance -tabbedmenu.weblog.pingSetup=Pings tabbedmenu.weblog.mediaFiles=Media Files @@ -1350,7 +1281,6 @@ tabbedmenu.admin=Server Admin tabbedmenu.admin.config=Configuration tabbedmenu.admin.userAdmin=User Admin tabbedmenu.admin.commentManagement=Comments -tabbedmenu.admin.pingTargets=Ping Targets tabbedmenu.admin.cacheInfo=Cache Info tabbedmenu.planet=Planet Admin diff --git a/app/src/main/resources/ApplicationResources_de.properties b/app/src/main/resources/ApplicationResources_de.properties index 35d9ca673..05c3c67ed 100644 --- a/app/src/main/resources/ApplicationResources_de.properties +++ b/app/src/main/resources/ApplicationResources_de.properties @@ -238,12 +238,6 @@ comments.htmlenabled=Erlaubt comments.mathAuthenticatorQuestion=Bitte beantworten Sie diese kleine Mathematik-Frage # ------------------------------------------------------------- comments.jsp comments.title=Kommentar -# -------------------------------------------------------------- Ping Target Admin/Editing -commonPingTargets.commonPingTargets=Allgemeine Weblog Ping Ziele -commonPingTargets.explanation=Diese Zielwebsites sind in allen Weblogs verf\u00FCgbar f\u00FCr Update Pings. -# ---------------------------------------------------- Ping Target Admin/Editing -commonPingTargets.subtitle=Management der allgemeinen Ping Ziele f\u00FCr alle Weblogs. -# ---------------------------------------------------- Ping Target Admin/Editing configForm.absoluteUrl=Absolute URL zur Site (falls notwendig) configForm.requireEmailActivation=Neue Benutzer m\u00FCssen Ihren Zugang per E-Mail aktivieren configForm.allowNewUsers=Erlaube das Anlegen neuer Benutzer? @@ -275,7 +269,6 @@ configForm.siteName=Name der Website (f\u00FCr den allgemeinen RSS Feed) configForm.siteSettings=Website Einstellungen configForm.styledFeeds=Zeige formatierte Newsfeeds f\u00FCr Webbrowser configForm.subtitle=\u00C4ndere Konfigurationseinstellungen f\u00FCr die gesamte Website -configForm.suspendPingProcessing=Alle Ping Prozesse stoppen? configForm.themeSettings=Themeneinstellungen # --------------------------------------------------------- Configuration configForm.title=Roller Konfiguration @@ -544,34 +537,7 @@ pagesForm.subtitle=Schablonen f\u00FCr den Weblog {0} verwalten. pagesForm.themesReminder=Erinnerung: Sie verwenden gegenw\u00E4rtig das {0} gemeinsame Thema. Daher k\u00F6nnten manche Schablonenanpassungen nicht das Aussehen Ihres Blogs ver\u00E4ndern. pagesForm.tip=Sie k\u00F6nnen die Schablonen, welche das Layout, die Farben und die Schriftart Ihres Weblogs definiert, anpassen. Sie k\u00F6nnen auch neue Schablonen erstellen, um komplett neue Seiten zu definieren. Dies sollten Sie jedoch nur tun, wenn Sie sich mit HTML auskennen. Ansonsten sollten Sie die Schablonen nicht ver\u00E4ndern. pagesForm.title=Seiten Schablonen -ping.networkConnectionFailed=Es gab Netzwerk Verbindungsprobleme beim Versuch die Ping Ziel Site zu erreichen. # ------------------------------------------------------------ Page management -ping.pingProcessingIsSuspended=Das versenden von Pings wurde vor\u00FCbergehend vom Administrator unterdr\u00FCckt. -ping.successful=Der Ping war erfogreich. -ping.transmissionFailed=Die Ping \u00DCbertragung ist fehlgeschlagen. Bitte pr\u00FCfen Sie, ob die Ping Ziel URL korrekt ist. -ping.transmittedButError=Der Ping wurde \u00FCbertragen, jedoch antwortete der Server mit der folgenden Fehlermeldung. -ping.unknownHost=Der Hostname in der Ping Ziel URL ist unbekannt. -pingTarget.auto=Automatisch -pingTarget.autoEnabled=Auto Ping? -pingTarget.confirmCommonRemove=Das L\u00F6schen dieses Ziels l\u00F6scht auch alle automatischen Benutzer Ping Konfigurationen, die dies Ziel benutzen. Sind Sie sicher? -pingTarget.confirmRemoveTitle=L\u00F6sche Ping Ziel Best\u00E4tigung -pingTarget.disable=Ausschalten -pingTarget.disabled=AUS -pingTarget.enable=Einschalten -pingTarget.enabled=AN -pingTarget.malformedUrl=Die URL ist nicht korrekt aufgebaut. -pingTarget.manual=Manuell -pingTarget.nameNotUnique=Der Name dieses Ziels steht in Konflikt mit einem anderen Namen in der gleichen Zielmenge. -pingTarget.pingUrl=Ping URL -pingTarget.remove=L\u00F6schen -pingTarget.updated=Ping-Ziel "{0}" gespeichert -pingTarget.sendPingNow=Sende jetzt ein Ping -pingTarget.unknownHost=Der Hostname in der URL scheint nicht zu existieren. -pings.commonPingTargets=Allgemeine Ping Ziele -pings.commonPingTargetsExplanation=Diese Ping Ziele wurden vom Administrator f\u00FCr alle Benutzer vorkonfiguriert. Sie k\u00F6nnen Pings zu diesen bekannten Sites senden. -pings.explanation=Pings erlauben es Ihnen Websites zu informieren, dass Ihr Weblog sich ge\u00E4ndert hat. Dadurch kann die Seite Ihren Newsfeed lesen um Updates bekanntzugeben. Sie k\u00F6nnen automatische Pings einschalten f\u00FCr Sites die informiert werden sollen, wenn sich Ihr Weblog \u00E4ndert. Ebenso k\u00F6nnen Pings manuell angesto\u00DFen werden. Hinweis: Normalerweise m\u00FCssen Sie zuerst Ihren Weblog auf eine Site registrieren lassen, bevor Sie ein ihr Pings senden k\u00F6nnen. -pings.subtitle=Automatische Pings erstellen f\u00FCr den Weblog {0} -pings.title=Konfiguriere Automatische Weblog Pings planetConfig.prompt=konfiguriere Planet Roller planetConfig.subtitle=Konfiguration des integrierten Newsfeed-Aggregators von Roller. planetConfig.title=Titel @@ -627,7 +593,6 @@ tabbedmenu.admin=Administration tabbedmenu.admin.cacheInfo=Cache Info tabbedmenu.admin.commentManagement=Kommentare tabbedmenu.admin.config=Konfiguration -tabbedmenu.admin.pingTargets=Ping Ziele tabbedmenu.admin.planetConfig=Konfiguration tabbedmenu.admin.planetGroups=Benutzerdefinierte Gruppen tabbedmenu.admin.planetGroupSubs=Abonnements @@ -640,7 +605,6 @@ tabbedmenu.weblog=Weblog tabbedmenu.weblog.archives=Archiv tabbedmenu.weblog.categories=Kategorien tabbedmenu.weblog.newEntry=Neuer Eintrag -tabbedmenu.weblog.pingSetup=Pings tabbedmenu.website=Webseite tabbedmenu.website.maintenance=Wartung tabbedmenu.website.members=Mitglieder diff --git a/app/src/main/resources/ApplicationResources_es.properties b/app/src/main/resources/ApplicationResources_es.properties index e8e795526..5093202f0 100644 --- a/app/src/main/resources/ApplicationResources_es.properties +++ b/app/src/main/resources/ApplicationResources_es.properties @@ -132,7 +132,6 @@ configForm.shortName=Nombre corto (mostrado en la pancarta del sitio) configForm.siteDescription=Descripci\u00F3n del sitio (para la p\u00E1gina principal y las suscripciones RSS) configForm.siteAdminEmail=Correo electr\u00F3nico del administrador del sitio configForm.absoluteUrl=URL absoluta del sitio (en caso necesario) -configForm.suspendPingProcessing=\u00BFSuspender todo el procesamiento ping? configForm.userSettings=Configuraci\u00F3n de usuario configForm.allowNewUsers=\u00BFPermitir nuevos usuarios? configForm.registrationUrl=URL de registro externo @@ -302,35 +301,6 @@ pageRemove.subtitle=Confirmar la eliminaci\u00F3n de la p\u00E1gina. pageRemove.youSure=\u00BFEst\u00E1 seguro de que desea eliminar esta p\u00E1gina? pageRemove.pageId=ID de p\u00E1gina pageRemove.pageName=Nombre de p\u00E1gina -commonPingTargets.subtitle=Administrar objetivos ping comunes para todos los weblogs. -commonPingTargets.commonPingTargets=Objetivos ping comunes -commonPingTargets.explanation=Estos objetivos est\u00E1n disponibles para los pings de actualizaci\u00F3n de todos los weblogs. -pings.title=Configurar pings autom\u00E1ticos -pings.subtitle=Configurar pings atom\u00E1ticos para el weblog {0} -pings.explanation=Los ping le permiten anunciar a otros sitios que su weblog ha cambiado, de modo que esos sitios puedan obtener las actualizaciones. Puede habilitar pings autom\u00E1ticos para sitios a los que desee informar cuando su weblog cambie. Puede tambi\u00E9n lanzar pings manualmente a sitios espec\u00EDficos de esta p\u00E1gina. Nota\: Normalmente, se espera que registre su weblog con un sitio antes de empezar a enviar los pings. -pings.commonPingTargets=Objetivos ping comunes -pings.commonPingTargetsExplanation=Estos objetivos ping han sido configurados por el administrador del sitio para uso de todos los usuarios. Puede enviar pings a cualquiera de estos sitios. -ping.successful=El ping se hizo con \u00E9xito. -ping.transmittedButError=El ping se transmiti\u00F3 pero el servidor respondi\u00F3 con el siguiente mensaje de error. -ping.transmissionFailed=La transmisi\u00F3n del ping fall\u00F3. Compruebe que la URL del objetivo ping es correcta. -ping.unknownHost=El nombre del host del objetivo ping es desconocido. -ping.networkConnectionFailed=Hay problemas de conexi\u00F3n para alcanzar el sitio del objetivo ping. -ping.pingProcessingIsSuspended=El proceso ping se ha suspendido temporalmente por un administrador de sitio. -pingTarget.pingUrl=URL para ping -pingTarget.remove=Eliminar -pingTarget.confirmRemoveTitle=Eliminar confirmaci\u00F3n de objetivo ping -pingTarget.confirmCommonRemove=Eliminando este objetivo tambi\u00E9n eliminar\u00E1 la configuraci\u00F3n de pings atom\u00E1ticos de todos los usuarios que usan este objetivo. \u00BFEst\u00E1 seguro? -pingTarget.enable=Habilitar -pingTarget.enabled=ACTIVADO -pingTarget.disable=Deshabilitar -pingTarget.disabled=DESACTIVADO -pingTarget.auto=Autom\u00E1tico -pingTarget.manual=Manual -pingTarget.sendPingNow=Enviar ping ahora -pingTarget.updated=Objetivo ping "{0}" guardado. -pingTarget.nameNotUnique=El nombre de este objetivo est\u00E1 en conflicto con otro en el mismo conjunto de objetivos. -pingTarget.malformedUrl=La URL no est\u00E1 formada correctamente. -pingTarget.unknownHost=El nombre de host de esta URL no parece existir. planetConfig.subtitle=Configurar agregador de noticias de Roller planetConfig.prompt=Establecer el t\u00EDtulo, direcci\u00F3n de email y URL del sitio que se incluir\u00E1n en la suscripci\u00F3n de noticias poducida por el agregador. Puede tambi\u00E9n configurar un proxy si es necesario para su red. planetConfig.title=Configuraci\u00F3n de Planet @@ -381,12 +351,10 @@ tabbedmenu.website.themes=Tema tabbedmenu.website.pages=Plantilla tabbedmenu.website.members=Miembros tabbedmenu.website.maintenance=Mantenimiento -tabbedmenu.weblog.pingSetup=Pings tabbedmenu.admin=Administraci\u00F3n global tabbedmenu.admin.config=Configuraci\u00F3n tabbedmenu.admin.userAdmin=Administraci\u00F3n de usuarios tabbedmenu.admin.commentManagement=Comentarios -tabbedmenu.admin.pingTargets=Objetivos ping tabbedmenu.admin.cacheInfo=Informaci\u00F3n de cach\u00E9 tabbedmenu.planet=Administraci\u00F3n de Planet tabbedmenu.admin.planetConfig=Configuraci\u00F3n diff --git a/app/src/main/resources/ApplicationResources_fr.properties b/app/src/main/resources/ApplicationResources_fr.properties index 8c7f4c362..f9fca8499 100644 --- a/app/src/main/resources/ApplicationResources_fr.properties +++ b/app/src/main/resources/ApplicationResources_fr.properties @@ -253,7 +253,6 @@ configForm.frontpageWeblogHandle=Identifiant du blog utilisé pour la page princ configForm.frontpageWeblogAggregated=Enable aggregated site-wide frontpage configForm.siteAdminEmail=Courriel de l'administrateur du site configForm.absoluteUrl=URL absolue du site (si nécessaire) -configForm.suspendPingProcessing=Suspendre les notifications configForm.userSettings=Paramètres utilisateur configForm.requireEmailActivation=Les nouveaux utilisateurs doivent activer leur compte par courriel @@ -626,58 +625,6 @@ pageRemove.youSure=Etes vous certain de vouloir effacer cette page ? pageRemove.pageId=Identifiant de page pageRemove.pageName=Nom de page -# ---------------------------------------------------- Ping Target Admin/Editing - -commonPingTargets.subtitle=Gestion des adresses de notification communes à tous les blogs. -commonPingTargets.commonPingTargets=Adresses de notification communes - -pings.title=Notifications automatiques -pings.subtitle=Gestion des notifications automatiques pour le blog {0} -pings.explanation=Les notifications vous permettent d'avertir d'autres sites \ -que le contenu de votre blog a changé de façon à ce que ceux-ci puissent lire \ -vos fils d'information et recevoir des mises à jour. \ -Vous pouvez autoriser les notifications automatiques pour autant de sites que vous le désirez, \ -et aussi notifier manuellement les sites spécifiés sur cette page. \ -Notez qu'habituellement vous devez enregistrer votre blog auprés de ces sites \ -avant de pouvoir leur envoyer vos notifications. - -pings.commonPingTargets=Adresses de notification communes -pings.commonPingTargetsExplanation=Ces adresses de notification ont été configurées \ -par l'administrateur de ce site pour tous les utilisateurs. Vous pouvez envoyer \ -des notifications à tous ces sites. - -ping.successful=La notification a réussie. -ping.transmittedButError=La notification à été envoyée au site distant mais celui-ci a renvoyé \ -le message d'erreur suivant : - -ping.transmissionFailed=La transmission de la notification a échouée. \ -Veuillez vous assurer que l'adresse URL que vous utilisez est correcte. - -ping.unknownHost=Le serveur specifié par cette adresse URL est introuvable. -ping.networkConnectionFailed=Incident réseau durant la notification de ce site. \ -Le site distant est injoignable. -ping.pingProcessingIsSuspended=Les notifications ont été provisoirement suspendues par \ -l'administrateur de ce site. - -pingTarget.pingUrl=Adresse URL -pingTarget.autoEnabled=Ping Automatique -pingTarget.remove=Effacer -pingTarget.confirmRemoveTitle=Confirmation d'effacement de l'adresse de notification -pingTarget.confirmCommonRemove=Effacer cette adresse affectera tous les blogs l'utilisant. \ -Etes vous certain de vouloir effacer celle-ci ? - -pingTarget.enable=Activer -pingTarget.enabled=Actif -pingTarget.disable=Désactivé -pingTarget.disabled=Inactif -pingTarget.auto=Automatique -pingTarget.manual=Manuel -pingTarget.sendPingNow=Notifier -pingTarget.updated=Adresse de notification "{0}" enregistrée. - -pingTarget.nameNotUnique=Le nom utilisé existe déjà ou fait partie d'un nom existant. -pingTarget.malformedUrl=L'URL utilisée est incorrecte. -pingTarget.unknownHost=Le serveur specifié par cette adresse URL est introuvable. # --------------------------------------------------------------- Planet Roller @@ -785,13 +732,11 @@ tabbedmenu.website.themes=Thème tabbedmenu.website.pages=Modèles tabbedmenu.website.members=Membres tabbedmenu.website.maintenance=Maintenance -tabbedmenu.weblog.pingSetup=Notifications tabbedmenu.admin=Admin. Serveur tabbedmenu.admin.config=Configuration tabbedmenu.admin.userAdmin=Admin. Utilisateur tabbedmenu.admin.commentManagement=Commentaires -tabbedmenu.admin.pingTargets=Notifications tabbedmenu.admin.cacheInfo=Cache tabbedmenu.planet=Admin. Planet diff --git a/app/src/main/resources/ApplicationResources_ja.properties b/app/src/main/resources/ApplicationResources_ja.properties index f6ca6b556..1400360ac 100644 --- a/app/src/main/resources/ApplicationResources_ja.properties +++ b/app/src/main/resources/ApplicationResources_ja.properties @@ -202,7 +202,6 @@ configForm.shortName=\u77ED\u7E2E\u540D(\u30B5\u30A4\u30C8\u30D0\u30CA\u30FC\u30 configForm.siteDescription=\u30B5\u30A4\u30C8\u8A73\u7D30(\u30E1\u30A4\u30F3\u30DA\u30FC\u30B8\u3068RSS\u30D5\u30A3\u30FC\u30C9\u306B\u53CD\u6620\u3055\u308C\u307E\u3059) configForm.siteAdminEmail=\u7BA1\u7406\u8005\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9 configForm.absoluteUrl=\u30B5\u30A4\u30C8\u306E\u7D76\u5BFEURL\uFF08\u5FC5\u8981\u306A\u5834\u5408\uFF09 -configForm.suspendPingProcessing=Ping\u51E6\u7406\u3092\u4E00\u6642\u7684\u306B\u505C\u6B62\u3059\u308B configForm.userSettings=\u30E6\u30FC\u30B6\u8A2D\u5B9A configForm.allowNewUsers=\u65B0\u898F\u30E6\u30FC\u30B6\u3092\u8A31\u53EF @@ -502,41 +501,6 @@ pageRemove.youSure=\u3053\u306E\u30DA\u30FC\u30B8\u3092\u524A\u9664\u3057\u307E\ pageRemove.pageId=\u30DA\u30FC\u30B8ID pageRemove.pageName=\u30DA\u30FC\u30B8\u540D -# ---------------------------------------------------- Ping Target Admin/Editing - -commonPingTargets.subtitle=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u7BA1\u7406 -commonPingTargets.commonPingTargets=\u30D6\u30ED\u30B0Ping\u30BF\u30FC\u30B2\u30C3\u30C8 -commonPingTargets.explanation=\u3053\u308C\u3089\u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u30FB\u30B5\u30A4\u30C8\u306F\u3001Ping\u9001\u4FE1\u306E\u305F\u3081\u306B\u5168\u3066\u306E\u30D6\u30ED\u30B0\u304B\u3089\u5229\u7528\u3067\u304D\u307E\u3059\u3002 - -pings.title=Ping\u8A2D\u5B9A -pings.subtitle={0}\u306EPing\u8A2D\u5B9A\u3092\u884C\u3044\u307E\u3059 - -pings.explanation=Ping\u306F\u3001\u3042\u306A\u305F\u306E\u30D6\u30ED\u30B0\u304C\u66F4\u65B0\u3055\u308C\u305F\u3053\u3068\u3092\u4ED6\u306E\u30B5\u30A4\u30C8\u306B\u901A\u77E5\u3057\u3001\u4ED6\u306E\u30B5\u30A4\u30C8\u304C\u3042\u306A\u305F\u306E\u30D6\u30ED\u30B0\u306E\u66F4\u65B0\u5185\u5BB9\u3092\u30D5\u30A3\u30FC\u30C9\u3092\u7D4C\u7531\u3057\u3066\u53D7\u4FE1\u3067\u304D\u308B\u3088\u3046\u306B\u3057\u307E\u3059\u3002\u30D6\u30ED\u30B0\u3092\u66F4\u65B0\u3059\u308B\u5EA6\u306B\u4ED6\u306E\u30B5\u30A4\u30C8\u306B\u5BFE\u3057\u3066\u81EA\u52D5\u7684\u306BPing\u3092\u9001\u4FE1\u3059\u308B\u3088\u3046\u306B\u8A2D\u5B9A\u3067\u304D\u307E\u3059\u3002\u3053\u306E\u30DA\u30FC\u30B8\u304B\u3089\u624B\u52D5\u3067\u7279\u5B9A\u306E\u30B5\u30A4\u30C8\u306B\u5BFE\u3057\u3066Ping\u3092\u9001\u4FE1\u3059\u308B\u3053\u3068\u3082\u3067\u304D\u307E\u3059\u3002\u30E1\u30E2: \u901A\u5E38\u3001Ping\u9001\u4FE1\u3092\u59CB\u3081\u308B\u524D\u306B\u9001\u4FE1\u5148\u30B5\u30A4\u30C8\u306B\u3042\u306A\u305F\u306E\u30D6\u30ED\u30B0\u3092\u767B\u9332\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002 - -pings.commonPingTargets=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u30EA\u30B9\u30C8 -pings.commonPingTargetsExplanation=\u4EE5\u4E0B\u306EPing\u30BF\u30FC\u30B2\u30C3\u30C8\u306F\u5168\u3066\u306E\u30E6\u30FC\u30B6\u304B\u3089\u4F7F\u3048\u308B\u3088\u3046\u306B\u30B5\u30A4\u30C8\u7BA1\u7406\u8005\u306B\u3088\u3063\u3066\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u3053\u308C\u3089\u306E\u5468\u77E5\u306E\u30B5\u30A4\u30C8\u306E\u4E2D\u304B\u3089\u597D\u304D\u306A\u30B5\u30A4\u30C8\u306BPing\u3092\u9001\u4FE1\u3067\u304D\u307E\u3059\u3002 - -ping.successful=Ping\u306F\u6B63\u5E38\u306B\u767A\u4FE1\u3055\u308C\u307E\u3057\u305F -ping.transmittedButError=Ping\u767A\u4FE1\u5148\u304B\u3089\u4EE5\u4E0B\u306E\u30A8\u30E9\u30FC\u304C\u8FD4\u3063\u3066\u304D\u307E\u3057\u305F -ping.transmissionFailed=Ping\u304C\u6307\u5B9A\u3055\u308C\u305FURL\u306B\u767A\u4FE1\u3067\u304D\u307E\u305B\u3093\u3002URL\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002 -ping.unknownHost=Ping\u30DB\u30B9\u30C8\u540D\u304C\u4E0D\u660E\u3067\u3059 -ping.networkConnectionFailed=\u5BFE\u8C61\u30DB\u30B9\u30C8\u3068\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093 -ping.pingProcessingIsSuspended=Ping\u51E6\u7406\u306F\u30B5\u30A4\u30C8\u7BA1\u7406\u8005\u306B\u3088\u3063\u3066\u4E00\u6642\u7684\u306B\u505C\u6B62\u3055\u308C\u3066\u3044\u307E\u3059\u3002 -pingTarget.pingUrl=Ping URL -pingTarget.autoEnabled=\u81EA\u52D5Ping\u8A2D\u5B9A -pingTarget.remove=\u524A\u9664 -pingTarget.confirmRemoveTitle=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u524A\u9664 -pingTarget.confirmCommonRemove=\u30BF\u30FC\u30B2\u30C3\u30C8\u3092\u524A\u9664\u3059\u308B\u3068\u3001\u3053\u306E\u30BF\u30FC\u30B2\u30C3\u30C8\u306B\u5BFE\u3059\u308B\u5168\u3066\u306E\u30E6\u30FC\u30B6\u306E\u81EA\u52D5Ping\u8A2D\u5B9A\u3082\u524A\u9664\u3055\u308C\u307E\u3059\u3002\u3088\u308D\u3057\u3044\u3067\u3059\u304B\uFF1F -pingTarget.enable=\u30AA\u30F3\u306B\u3059\u308B -pingTarget.enabled=\u30AA\u30F3 -pingTarget.disable=\u30AA\u30D5\u306B\u3059\u308B -pingTarget.disabled=\u30AA\u30D5 -pingTarget.auto=\u81EA\u52D5 -pingTarget.manual=\u624B\u52D5 -pingTarget.sendPingNow=\u4ECA\u3059\u3050Ping\u3092\u6253\u3064 - -pingTarget.nameNotUnique=\u540C\u3058\u540D\u524D\u306E\u30B5\u30A4\u30C8\u540D\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u4ED6\u306E\u540D\u524D\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002 -pingTarget.unknownHost=\u3053\u306EURL\u306E\u30DB\u30B9\u30C8\u304C\u898B\u4ED8\u304B\u308A\u307E\u305B\u3093 # --------------------------------------------------------------- Planet Roller @@ -581,7 +545,6 @@ tabbedmenu.admin=\u30B5\u30FC\u30D0\u7BA1\u7406 tabbedmenu.admin.config=\u30B5\u30A4\u30C8\u8A2D\u5B9A tabbedmenu.admin.userAdmin=\u30E6\u30FC\u30B6\u7BA1\u7406 tabbedmenu.admin.commentManagement=\u30B3\u30E1\u30F3\u30C8\u7BA1\u7406 -tabbedmenu.admin.pingTargets=Ping\u30BF\u30FC\u30B2\u30C3\u30C8 tabbedmenu.admin.cacheInfo=\u30AD\u30E3\u30C3\u30B7\u30E5\u60C5\u5831 tabbedmenu.admin.planetConfig=\u8A2D\u5B9A @@ -1071,7 +1034,6 @@ userRegister.tip.screenName=\u5E0C\u671B\u3059\u308B\u30B9\u30AF\u30EA\u30FC\u30 error.activate.user.invalidActivationCode=\u30A2\u30AF\u30C6\u30A3\u30D9\u30FC\u30B7\u30E7\u30F3\u30B3\u30FC\u30C9\u304C\u4E0D\u6B63\u3067\u3059\u3002\u3042\u306A\u305F\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u306F\u3001\u3059\u3067\u306B\u6709\u52B9\u5316\u3055\u308C\u3066\u3044\u308B\u306F\u305A\u3067\u3059\u3002\u6709\u52B9\u5316\u3055\u308C\u3066\u3044\u306A\u3051\u308C\u3070\u3001\u7BA1\u7406\u8005\u306B\u9023\u7D61\u3057\u3066\u304F\u3060\u3055\u3044\u3002 stylesheetEdit.delete=\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u524A\u9664 mediaFileView.subtitle=\u30D6\u30ED\u30B0 {0} \u306E\u30E1\u30C7\u30A3\u30A2\u30FB\u30D5\u30A1\u30A4\u30EB -commonPingTargets.error.saving=\u5171\u901APing\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F planetGroups.tip.title=\u30B0\u30EB\u30FC\u30D7\u306E\u30BF\u30A4\u30C8\u30EB themeEditor.setTheme.success=\u30C6\u30FC\u30DE\u304C {0} \u306B\u8A2D\u5B9A\u3055\u308C\u307E\u3057\u305F planetGroups.existingPrompt=\u65E2\u5B58\u306E\u7DE8\u96C6\u3057\u305F\u3044\u30AB\u30B9\u30BF\u30E0\u30FB\u30A2\u30B0\u30EA\u30B2\u30FC\u30B7\u30E7\u30F3\u30FB\u30B0\u30EB\u30FC\u30D7\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002 @@ -1099,7 +1061,6 @@ mediaFileEdit.clickToView=\u30AF\u30EA\u30C3\u30AF\u3059\u308B\u3068\u65B0\u3057 mediaFileEdit.includeGalleryHelp=\u3053\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u3042\u306A\u305F\u306E\u30D6\u30ED\u30B0\u306E\u65B0\u3057\u3044\u30E1\u30C7\u30A3\u30A2\u30FB\u30D5\u30A1\u30A4\u30EBRSS/Atom\u30D5\u30A3\u30FC\u30C9\u306B\u542B\u3081\u305F\u3044\u5834\u5408\u306F\u3001\u3053\u306E\u30DC\u30C3\u30AF\u30B9\u3092\u30C1\u30A7\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002 mediaFileView.reset=\u30EA\u30BB\u30C3\u30C8 userRegister.tip.openIdUrl=Open ID\u8B58\u5225\u5B50(URL\u5F62\u5F0F)\u3002 -pingTarget.editTarget=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u3092\u7DE8\u96C6\u3059\u308B mediaFileAdd.directory=\u30D5\u30A9\u30EB\u30C0 planetGroupSubs.title=\u30B5\u30D6\u30B9\u30AF\u30EA\u30D7\u30B7\u30E7\u30F3 mediaFileView.size=\u30B5\u30A4\u30BA @@ -1131,7 +1092,6 @@ mediaFileView.sortBy=\u3053\u306E\u5C5E\u6027\u3067\u30BD\u30FC\u30C8\: MediaFile.error.nameNull=\u540D\u524D\u306F\u5FC5\u9808\u9805\u76EE\u3067\u3059 pagesForm.error.alreadyExists=\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8 {0} \u306F\u3001\u3059\u3067\u306B\u5B58\u5728\u3057\u307E\u3059\u3002 email.comment.management.link=\u30B3\u30E1\u30F3\u30C8\u7BA1\u7406\u30DA\u30FC\u30B8\u3078\u306E\u30EA\u30F3\u30AF -pingTarget.nameMissing=\u540D\u524D\u306F\u5FC5\u9808\u9805\u76EE\u3067\u3059 mediaFileEdit.fileDimensions=\u753B\u50CF\: {0} X {1} \u30D4\u30AF\u30BB\u30EB mediaFileEdit.originalPath=\u30AA\u30EA\u30B8\u30CA\u30EB\u30FB\u30D1\u30B9 (\u30D5\u30A1\u30A4\u30EB\u3078\u306E\u4EE3\u66FF\u30D5\u30EB\u30D1\u30B9) mediaFileAdd.upload=\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9 @@ -1143,11 +1103,9 @@ planetGroupSubs.prompt.add=\u65B0\u3057\u3044\u30B5\u30D6\u30B9\u30AF\u30EA\u30D MediaFile.error.view=\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u8868\u793A\u3059\u308B\u969B\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002 mediaFileAdd.multipleNames=(\u8907\u6570\u306E\u540D\u524D) macro.bookmark.urlFeed=\u30B5\u30A4\u30C8\u306ERSS\u30D5\u30A3\u30FC\u30C9\u306EURL -commonPingTargets.error.enabling=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u6709\u52B9\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F inviteMember.notificationContent=\u3042\u306A\u305F\u306F\u3001\u30D6\u30ED\u30B0 "{0}" ({1}) \u3078\u306E\u53C2\u52A0\u3092\u62DB\u5F85\u3055\u308C\u307E\u3057\u305F\u3002\u4EE5\u4E0B\u306E\u30EA\u30F3\u30AF\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u30E6\u30FC\u30B6 [{2}] \u3068\u3057\u3066\u30ED\u30B0\u30A4\u30F3\u3057\u3001\u3053\u306E\u62DB\u5F85 <{3}> \u3092\u627F\u8AFE\u3059\u308B\u304B\u62D2\u5426\u3057\u3066\u304F\u3060\u3055\u3044\u3002 mediaFileView.addDirectory=\u65B0\u3057\u3044\u30D5\u30A9\u30EB\u30C0\u3092\u8FFD\u52A0 installer.whatHappened=\u4F55\u304C\u8D77\u3053\u3063\u305F\u306E\u3067\u3057\u3087\u3046? -pingTargetEdit.subtitle=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u7DE8\u96C6 installer.errorUpgradingTables=\u30C6\u30FC\u30D6\u30EB\u306E\u30A2\u30C3\u30D7\u30B0\u30EC\u30FC\u30C9\u306B\u5931\u6557 folderForm.updated=\u30D5\u30A9\u30EB\u30C0\u540D\u304C\u66F4\u65B0\u3055\u308C\u307E\u3057\u305F frontpageConfig.frontpageAggregated=\u30B5\u30A4\u30C8\u5168\u4F53\u304C\u96C6\u7D04\u3055\u308C\u305F\u30D5\u30ED\u30F3\u30C8\u30FB\u30DA\u30FC\u30B8\u3092\u6709\u52B9\u306B\u3059\u308B @@ -1183,9 +1141,7 @@ error.commentPostFailedEmailAddress=\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u ConfigForm.title=Planet \u30BF\u30A4\u30C8\u30EB\t mediaFileView.viewFolder=\u3053\u306E\u30D5\u30A9\u30EB\u30C0\u3092\u8868\u793A\: mediaFile.move.confirm=\u9078\u629E\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u3092\u79FB\u52D5\u3057\u3066\u3088\u308D\u3057\u3044\u3067\u3059\u304B? -pingTarget.updated=Ping\u30BF\u30FC\u30B2\u30C3\u30C8 "{0}" \u304C\u66F4\u65B0\u3055\u308C\u307E\u3057\u305F generic.name=\u540D\u524D -pingTarget.malformedUrl=URL\u304C\u4E0D\u6B63\u306A\u5F62\u5F0F\u3067\u3059\u3002 mediaFileView.filesTagged=\u30BF\u30B0\u304C {0} \u306E\u30D5\u30A1\u30A4\u30EB weblogEdit.hasComments=\u30B3\u30E1\u30F3\u30C8 [{1}] categoryDeleteOK.title=\u30AB\u30C6\u30B4\u30EA\u306E\u524A\u9664 @@ -1271,7 +1227,6 @@ Entry.error.titleNull=\u30BF\u30A4\u30C8\u30EB\u306F\u5FC5\u9808\u9805\u76EE\u30 planetGroupSubs.subtitle.addMain=\u30CB\u30E5\u30FC\u30B9\u30D5\u30A3\u30FC\u30C9\u30FB\u30B5\u30D6\u30B9\u30AF\u30EA\u30D7\u30B7\u30E7\u30F3\u306E\u30E1\u30A4\u30F3\u30FB\u30EA\u30B9\u30C8\u3092\u7BA1\u7406\u3057\u307E\u3059\u3002 mediaFile.move.errors=\u30E1\u30C7\u30A3\u30A2\u30FB\u30D5\u30A1\u30A4\u30EB\u3092\u79FB\u52D5\u3059\u308B\u969B\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002 mediaFileSuccess.addAnother=\u4ED6\u306E\u30E1\u30C7\u30A3\u30A2\u30FB\u30D5\u30A1\u30A4\u30EB\u3092\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3059\u308B -pingTarget.pingUrlMissing=Ping URL\u306F\u5FC5\u9808\u9805\u76EE\u3067\u3059 mediaFileEdit.permalink=\u30EA\u30F3\u30AF(URL) stylesheetEdit.save.success=\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u304C\u6B63\u5E38\u306B\u66F4\u65B0\u3055\u308C\u307E\u3057\u305F\u3002 bookmarksForm.selectOneLabel=\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF {0} \u3092\u9078\u629E @@ -1302,10 +1257,8 @@ statCount.weblogCommentCountType=\u30D6\u30ED\u30B0\u306E\u30B3\u30E1\u30F3\u30C mediaFileSuccess.subtitle=\u65B0\u3057\u3044\u30A8\u30F3\u30C8\u30EA\u3092\u4F5C\u6210\u3057\u307E\u3059\u304B? categoriesForm.noresults=\u73FE\u5728\u3001\u30AB\u30C6\u30B4\u30EA\u306F\u3042\u308A\u307E\u305B\u3093 installer.errorCreatingTablesExplanation=\u30C6\u30FC\u30D6\u30EB\u306E\u4F5C\u6210\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u4F5C\u6210\u30B9\u30AF\u30EA\u30D7\u30C8\u306B\u554F\u984C\u304C\u3042\u308B\u304B\u3001\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u306A\u3044\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u4F7F\u7528\u3055\u308C\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u3042\u306A\u305F\u306F\u3001\u3053\u306E\u554F\u984C\u3092\u89E3\u6C7A\u3057\u3066Roller\u3092\u518D\u8D77\u52D5\u307E\u305F\u306F\u518D\u30C7\u30D7\u30ED\u30A4\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002\u4F5C\u6210\u51E6\u7406\u4E2D\u306E\u6210\u529F/\u30A8\u30E9\u30FC\u30E1\u30C3\u30BB\u30FC\u30B8\u306F\u4EE5\u4E0B\u3067\u3059\: -commonPingTargets.error.loading=\u5171\u901APing\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F mediaFileImageChooser.dirPageTip=\u753B\u50CF\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u9078\u629E\u3059\u308B\u304B\u3001\u30D5\u30A9\u30EB\u30C0\u30FB\u30C9\u30ED\u30C3\u30D7\u30C0\u30A6\u30F3\u3078\u306E\u30B9\u30A4\u30C3\u30C1\u3092\u4F7F\u3063\u3066\u4ED6\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u79FB\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002 mediaFileView.others=\u305D\u306E\u4ED6 -commonPingTargets.error.disabling=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u7121\u52B9\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F stylesheetEdit.revert=\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u5FA9\u5143 weblogEdit.submitForReview=\u30EC\u30D3\u30E5\u30FC\u4F9D\u983C\u3092\u9001\u4FE1 mediaFileView.filesOfType=\u30BF\u30A4\u30D7\u304C {0} \u306E\u30D5\u30A1\u30A4\u30EB @@ -1328,14 +1281,11 @@ websiteSettings.error.processingBannedwordslist=\u30D6\u30E9\u30C3\u30AF\u30EA\u mediaFileView.ge=>\= stylesheetEdit.revert.success=\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u306F\u6B63\u5E38\u306B\u5FA9\u5143\u3055\u308C\u307E\u3057\u305F\u3002 mediaFileImageChooser.rootPageTip=\u753B\u50CF\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u9078\u629E\u3059\u308B\u304B\u3001\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u9078\u629E\u3057\u3066\u5185\u5BB9\u3092\u8868\u793A\u3057\u3066\u304F\u3060\u3055\u3044 -pingTarget.notFound=Ping\u30BF\u30FC\u30B2\u30C3\u30C8ID {0} \u306F\u3001\u5B58\u5728\u3057\u307E\u305B\u3093 mediaFileSuccess.noThanks=\u65B0\u3057\u3044\u30A8\u30F3\u30C8\u30EA\u306E\u4F5C\u6210\u306F\u5FC5\u8981\u3042\u308A\u307E\u305B\u3093\u304B? \u3053\u3061\u3089\u304B\u3089\u95A2\u9023\u3059\u308B\u4ED6\u306E\u64CD\u4F5C\u304C\u3067\u304D\u307E\u3059\: -pingTarget.addTarget=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u8FFD\u52A0 configForm.webAnalytics=Web\u30A2\u30CA\u30EA\u30C6\u30A3\u30AF\u30B9 pageForm.outputContentType=Content-Type folderForm.created=\u30D5\u30A9\u30EB\u30C0\u304C\u4F5C\u6210\u3055\u308C\u307E\u3057\u305F weblogEdit.insertMediaFile=\u30E1\u30C7\u30A3\u30A2\u30FB\u30D5\u30A1\u30A4\u30EB\u306E\u633F\u5165 -pingTarget.deleted=Ping\u30BF\u30FC\u30B2\u30C3\u30C8 "{0}" \u304C\u524A\u9664\u3055\u308C\u307E\u3057\u305F configForm.webServicesSettings=Web\u30B5\u30FC\u30D3\u30B9\u8A2D\u5B9A stylesheetEdit.revertTip1=\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u3053\u3053\u3067\u30AB\u30B9\u30BF\u30DE\u30A4\u30BA\u3057\u3066\u3044\u306A\u3044\u5834\u5408\u306F\u3001\u300C\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u524A\u9664\u300D\u30DC\u30BF\u30F3\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u3001\u5171\u6709\u30C6\u30FC\u30DE\u306E\u30C7\u30D5\u30A9\u30EB\u30C8\u3092\u4F7F\u7528\u3059\u308B\u3088\u3046\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u3053\u3046\u3059\u308B\u3053\u3068\u3067\u3001\u5E38\u306B\u6700\u65B0\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u4F7F\u7528\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u3059\u3002 tabbedmenu.design.stylesheet=\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8 @@ -1346,7 +1296,6 @@ mediaFileView.directoryName=\u540D\u524D\: mediaFileSuccess.pixels=\u30D4\u30AF\u30BB\u30EB stylesheetEdit.revertTip=\u89E3\u6C7A\u3067\u304D\u306A\u3044\u554F\u984C\u304C\u8D77\u304D\u305F\u5834\u5408\u306F\u300C\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u5FA9\u5143\u300D\u30DC\u30BF\u30F3\u3092\u62BC\u3059\u3053\u3068\u3067\u3084\u308A\u76F4\u3059\u3053\u3068\u304C\u3067\u304D\u307E\u3059\u3002\u5FF5\u306E\u305F\u3081\u3001\u3042\u306A\u305F\u306E\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u3059\u308B\u3088\u3046\u306B\u3057\u3066\u304F\u3060\u3055\u3044\! planetGroups.column.title=\u30BF\u30A4\u30C8\u30EB -pingTargetAdd.subtitle=Ping\u30BF\u30FC\u30B2\u30C3\u30C8\u306E\u8FFD\u52A0 planetSubscription.feedUrl=\u30CB\u30E5\u30FC\u30B9\u30D5\u30A3\u30FC\u30C9URL mediaFileSuccess.noEnclosure=\u30A8\u30F3\u30AF\u30ED\u30FC\u30B8\u30E3\u306A\u3057 userRegister.tip.ready=\u3088\u308D\u3057\u3051\u308C\u3070\u3001\u4EE5\u4E0B\u306E\u30DC\u30BF\u30F3\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002 @@ -1376,9 +1325,7 @@ user.account.activation.mail.subject=Roller\: \u3042\u306A\u305F\u306E\u30A2\u30 generic.error.check.logs=Roller\u30B7\u30B9\u30C6\u30E0\u30FB\u30A8\u30E9\u30FC - \u7BA1\u7406\u8005\u306BRoller\u306E\u30ED\u30B0\u306E\u78BA\u8A8D\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002 weblogEdit.scheduledEntry=\u30A8\u30F3\u30C8\u30EA\u30FC\u306E\u516C\u958B\u304C {0} \u306B\u30B9\u30B1\u30B8\u30E5\u30FC\u30EB\u3055\u308C\u307E\u3057\u305F frontpageConfig.weblogs.error=\u30D6\u30ED\u30B0\u3078\u306E\u30A2\u30AF\u30BB\u30B9\u4E2D\u306B\u4E88\u671F\u305B\u306C\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -pingTarget.created=Ping\u30BF\u30FC\u30B2\u30C3\u30C8 "{0}" \u304C\u8FFD\u52A0\u3055\u308C\u307E\u3057\u305F themeEditor.yourCustomStylesheet=\u30AB\u30B9\u30BF\u30E0\u30FB\u30AA\u30FC\u30D0\u30FC\u30E9\u30A4\u30C9\u30FB\u30B9\u30BF\u30A4\u30EB\u30B7\u30FC\u30C8\u3092\u4F7F\u7528\u3057\u3066\u3044\u307E\u3059\u3002 -tabbedmenu.weblog.pingSetup=Pings mediaFileAdd.copyright=\u8457\u4F5C\u6A29\u8868\u793A bookmarksForm.folder.edit.tip=\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF\u3092\u7DE8\u96C6 installer.aboutTheException=\u4EE5\u4E0A\u306E\u60C5\u5831\u3067\u554F\u984C\u304C\u7279\u5B9A\u3067\u304D\u306A\u3044\u5834\u5408\u3001\u3082\u3046\u5C11\u3057\u8A73\u7D30\u306A\u60C5\u5831\u306F\u3053\u3061\u3089\u3067\u3059\u3002\u6839\u672C\u306E\u539F\u56E0\u306F\u3001\u3053\u306E\u30BF\u30A4\u30D7\u306E\u4F8B\u5916\u3067\u3059 diff --git a/app/src/main/resources/ApplicationResources_ko.properties b/app/src/main/resources/ApplicationResources_ko.properties index 3adb9a1a4..423e8f03b 100644 --- a/app/src/main/resources/ApplicationResources_ko.properties +++ b/app/src/main/resources/ApplicationResources_ko.properties @@ -264,7 +264,6 @@ configForm.frontpageWeblogHandle=\ud504\ub860\ud2b8\ud398\uc774\uc9c0 \ube14\ub8 configForm.frontpageWeblogAggregated=\ubcd1\ud569\ub41c \uc0ac\uc774\ud2b8 \ud504\ub860\ud2b8\ud398\uc774\uc9c0 \ud65c\uc131\ud654 configForm.siteAdminEmail=\uc0ac\uc774\ud2b8 \uad00\ub9ac\uc790 \uc804\uc790\uc6b0\ud3b8 \uc8fc\uc18c configForm.absoluteUrl=\uc0ac\uc774\ud2b8\uc758 \uc808\ub300 \uc8fc\uc18c URL (\ud544\uc694 \uc2dc) -configForm.suspendPingProcessing=\ubaa8\ub4e0 \uc5f0\uacb0\ud655\uc778(ping) \ucc98\ub9ac\ub97c \uc911\uc9c0\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? configForm.userSettings=\uc0ac\uc6a9\uc790 \ud658\uacbd \uc124\uc815 \uc815\ubcf4 configForm.requireEmailActivation=\uc0c8 \uc0ac\uc6a9\uc790\uac00 \uc804\uc790\uc6b0\ud3b8\uc744 \ud1b5\ud574 \uacc4\uc815 \ud65c\uc131\ud654 \ud544\uc694 @@ -706,50 +705,6 @@ Template.error.linkSize=\ub9c1\ud06c\ub294 255\uc790\ub97c \ucd08\uacfc\ud560 \u Template.error.descriptionSize=\uc124\uba85\uc740 255\uc790\ub97c \ucd08\uacfc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. -# ---------------------------------------------------- Ping Target Admin/Editing - -commonPingTargets.subtitle=\ubaa8\ub4e0 \uc6f9\ub85c\uadf8\ub4e4\uc5d0 \ub300\ud55c \uacf5\ud1b5 \uc6f9\ub85c\uadf8 \uc5f0\uacb0\ud655\uc778(ping) \ub300\uc0c1 \uad00\ub9ac -commonPingTargets.commonPingTargets=\uacf5\ud1b5 \uc6f9\ub85c\uadf8 \uc5f0\uacb0\ud655\uc778(ping) \ub300\uc0c1\ub4e4 -commonPingTargets.explanation=\uc774 \ub300\uc0c1 \uc0ac\uc774\ud2b8\ub294 \ubaa8\ub4e0 \uc6f9\ub85c\uadf8\ub4e4\uc5d0 \ub300\ud574 \uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc5f0\uacb0\ud655\uc778(ping) \ub300\uc0c1\uc785\ub2c8\ub2e4. - -pings.title=\uc790\ub3d9 \uc6f9\ub85c\uadf8 \uc5f0\uacb0\ud655\uc778(ping) \uc124\uc815 -pings.subtitle=\uc6f9\ub85c\uadf8, {0}\uc744(\ub97c) \uc704\ud55c \uc5f0\uacb0\ud655\uc778(ping) \uc124\uc815 -pings.explanation=\uc5f0\uacb0\ud655\uc778(ping)\ub294 \uadc0\ud558\uc758 \uc6f9\ub85c\uadf8\uac00 \ubcc0\uacbd\ub418\uc5c8\uc74c\uc744 \uc0ac\uc774\ud2b8\uc5d0 \uacf5\uc9c0\ud560 \uc218 \uc788\uac8c \ud569\ub2c8\ub2e4. \ -\ub530\ub77c\uc11c \uc0ac\uc774\ud2b8\ub4e4\uc740 \uadc0\ud558\uc758 \ud53c\ub4dc\ub85c\ubd80\ud130 \ubcc0\uacbd\uc0ac\ud56d\ub4e4\uc744 \uc77d\uc5b4\uc62c \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ -\uadc0\ud558\uc758 \uc6f9\ub85c\uadf8\uac00 \ubcc0\uacbd\ub420 \ub54c\ub9c8\ub2e4 \uc790\ub3d9\uc73c\ub85c \uc0ac\uc774\ud2b8\uc5d0 \uacf5\uc9c0\ud558\uace0\uc790 \ud55c\ub2e4\uba74 \uc790\ub3d9 \uc5f0\uacb0\ud655\uc778\uc744 \ud65c\uc131\ud654\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ -\ub610\ud55c \uc774 \ud398\uc774\uc9c0\uc5d0\uc11c \uc218\ub3d9\uc73c\ub85c \uc5f0\uacb0\ud655\uc778\uc744 \uc218\ud589\ud560 \ud2b9\uc815 \uc0ac\uc774\ud2b8\ub97c \uc124\uc815\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4. \ -\uc8fc\uc758: \uc77c\ubc18\uc801\uc73c\ub85c, \uc0ac\uc774\ud2b8 \uc5f0\uacb0\ud655\uc778\uc744 \uc2dc\uc791\ud558\uae30 \uc804\uc5d0 \ -\ud574\ub2f9 \uc0ac\uc774\ud2b8\uc5d0 \uadc0\ud558\uc758 \uc6f9\ub85c\uadf8\ub97c \ub4f1\ub85d\ud574\uc57c \ud569\ub2c8\ub2e4. -pings.commonPingTargets=\uacf5\ud1b5 \uc5f0\uacb0\ud655\uc778 \ub300\uc0c1 -pings.commonPingTargetsExplanation=\uc774 \uc5f0\uacb0\ud655\uc778 \ub300\uc0c1\ub4e4\uc740 \uc0ac\uc774\ud2b8 \uad00\ub9ac\uc790\uc5d0 \uc758\ud574 \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ -\uc774 \uc798 \uc54c\ub824\uc9c4 \uc0ac\uc774\ud2b8\ub4e4 \uc911\uc5d0 \uc5b4\ub290 \uacf3\uc774\ub4e0 \uc5f0\uacb0\ud655\uc778\uc744 \ubcf4\ub0bc \uc218 \uc788\uc2b5\ub2c8\ub2e4. - -ping.successful=\uc5f0\uacb0\ud655\uc778\uc774 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4. -ping.transmittedButError=\uc5f0\uacb0\ud655\uc778\uc774 \uc804\uc1a1\ub418\uc5c8\uc73c\ub098, \uc11c\ubc84\uac00 \ub2e4\uc74c \uc624\ub958 \uba54\uc2dc\uc9c0\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. -ping.transmissionFailed=\uc5f0\uacb0\ud655\uc778 \uc804\uc1a1\uc774 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \ -\uc5f0\uacb0\ud655\uc778 \ub300\uc0c1 URL\uc774 \ub9de\ub294\uc9c0 \ud655\uc778\ud574 \uc8fc\uc2ed\uc2dc\uc624. -ping.unknownHost=\uc5f0\uacb0\ud655\uc778 \ub300\uc0c1 URL\uc758 \ud638\uc2a4\ud2b8\uba85\uc774 \uc54c\ub824\uc9c0\uc9c0 \uc54a\uc740 \uc774\ub984\uc785\ub2c8\ub2e4. -ping.networkConnectionFailed=\uc5f0\uacb0\ud655\uc778 \ub300\uc0c1 \uc0ac\uc774\ud2b8\uc5d0 \ub3c4\ub2ec\ud558\ub294 \ub370\uc5d0 \ub124\ud2b8\uc6cc\ud06c \uc5f0\uacb0 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4. -ping.pingProcessingIsSuspended=\uc5f0\uacb0\ud655\uc778 \ucc98\ub9ac\uac00 \uc0ac\uc774\ud2b8 \uad00\ub9ac\uc790\uc5d0 \uc758\ud574 \uc77c\uc2dc\uc801\uc73c\ub85c \uc911\uc9c0\ub418\uc5c8\uc2b5\ub2c8\ub2e4. - -pingTarget.pingUrl=\uc5f0\uacb0\ud655\uc778 URL -pingTarget.autoEnabled=\uc790\ub3d9 \uc5f0\uacb0\ud655\uc778? -pingTarget.remove=\uc0ad\uc81c -pingTarget.confirmRemoveTitle=\uc5f0\uacb0\ud655\uc778 \ub300\uc0c1 \uc0ad\uc81c \ud655\uc778 -pingTarget.confirmCommonRemove=\uc774 \uc5f0\uacb0\ud655\uc778 \ub300\uc0c1\uc744 \uc0ad\uc81c\ud558\uac8c \ub418\uba74, \ -\uc774 \uc5f0\uacb0\ud655\uc778 \ub300\uc0c1\uc744 \uc0ac\uc6a9\ud558\ub294 \ubaa8\ub4e0 \uc0ac\uc6a9\uc790\ub4e4\uc758 \uc790\ub3d9 \uc5f0\uacb0\ud655\uc778 \uc124\uc815\ub4e4\ub3c4 \ud568\uaed8 \uc0ad\uc81c\ub429\ub2c8\ub2e4. -pingTarget.enable=\ud65c\uc131\ud654 -pingTarget.enabled=\ud65c\uc131 -pingTarget.disable=\ube44\ud65c\uc131\ud654 -pingTarget.disabled=\ube44\ud65c\uc131 -pingTarget.auto=\uc790\ub3d9 -pingTarget.manual=\uc218\ub3d9 -pingTarget.sendPingNow=\uc5f0\uacb0\ud655\uc778 \uc9c0\uae08 \uc804\uc1a1 - -pingTarget.nameNotUnique=\uc774 \uc5f0\uacb0\ud655\uc778 \ub300\uc0c1\uc758 \uc774\ub984\uc774 \ub2e4\ub978 \ub300\uc0c1\uc758 \uc774\ub984\uacfc \ucda9\ub3cc\ud569\ub2c8\ub2e4. -pingTarget.malformedUrl=URL\uc774 \uc801\uc808\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. -pingTarget.unknownHost=\uc774 URL\uc758 \ud638\uc2a4\ud2b8\uba85\uc774 \uc874\uc7ac\ud558\uc9c0 \uc54a\ub294 \uac83 \uac19\uc2b5\ub2c8\ub2e4. - # --------------------------------------------------------------- Planet Roller planetConfig.title=\ud50c\ub798\ub2db \ud658\uacbd \uc124\uc815 @@ -865,12 +820,10 @@ tabbedmenu.website.themes=\ud14c\ub9c8 tabbedmenu.website.pages=\ud15c\ud50c\ub9bf tabbedmenu.website.members=\uba64\ubc84 tabbedmenu.website.maintenance=\uc720\uc9c0\ubcf4\uc218 -tabbedmenu.weblog.pingSetup=\uc5f0\uacb0\ud655\uc778 tabbedmenu.admin=\uc11c\ubc84 \uad00\ub9ac\uc790 tabbedmenu.admin.config=\ud658\uacbd \uc124\uc815 tabbedmenu.admin.userAdmin=\uc0ac\uc6a9\uc790 \uad00\ub9ac\uc790 tabbedmenu.admin.commentManagement=\uc758\uacac -tabbedmenu.admin.pingTargets=\uc5f0\uacb0\ud655\uc778 \ub300\uc0c1 tabbedmenu.admin.cacheInfo=\uce90\uc2dc \uc815\ubcf4 tabbedmenu.planet=\ud50c\ub798\ub2db \uad00\ub9ac\uc790 tabbedmenu.admin.planetConfig=\ud658\uacbd\uc124\uc815 diff --git a/app/src/main/resources/ApplicationResources_ru.properties b/app/src/main/resources/ApplicationResources_ru.properties index 78aa084ca..7689a1f93 100644 --- a/app/src/main/resources/ApplicationResources_ru.properties +++ b/app/src/main/resources/ApplicationResources_ru.properties @@ -35,7 +35,6 @@ generic.save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C generic.edit=\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C generic.delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C - # ------------------------------------------------------------- BookmarkForm.jsp bookmarkForm.add.title=\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 @@ -95,7 +94,6 @@ categoryForm.add.subtitle=\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438 categoryForm.image=\u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 - # ----------------------------------------------------------- CategoriesForm.jsp categoriesForm.subtitle=\u0423\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F\u043C\u0438 \u0432 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0435 {0} @@ -151,7 +149,6 @@ configForm.shortName=\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u0438\u043C\u04 configForm.siteDescription=\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u0441\u0430\u0439\u0442\u0430 (\u0434\u043B\u044F \u0433\u043B\u0430\u0432\u043D\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0438 \u043A\u0430\u043D\u0430\u043B\u043E\u0432 RSS) configForm.siteAdminEmail=Email \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430 configForm.absoluteUrl=\u0410\u0431\u0441\u043E\u043B\u044E\u0442\u043D\u044B\u0439 URL \u0441\u0430\u0439\u0442\u0430 (\u0435\u0441\u043B\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F) -configForm.suspendPingProcessing=\u041F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0443 \u043F\u0438\u043D\u0433\u043E\u0432? configForm.userSettings=\u041D\u0430\u0441\u0440\u043E\u0439\u043A\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u0439 configForm.allowNewUsers=\u0420\u0430\u0437\u0440\u0435\u0448\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0445? @@ -235,11 +232,9 @@ error.upload.forbiddenFile=\u041C\u043E\u0436\u043D\u043E \u0437\u0430\u0433\u04 error.general=\u041E\u0448\u0438\u0431\u043A\u0430: \u0417\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u043D\u0435\u0432\u0435\u0440\u043D\u043E\u0435 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 [{0}] error.password.mismatch=\u041D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u043A\u043E\u043C\u0431\u0438\u043D\u0430\u0446\u0438\u044F \u0438\u043C\u0435\u043D\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u0438 \u043F\u0430\u0440\u043E\u043B\u044F - error.title.403=\u0414\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D (403) error.text.403=\u0423 \u0432\u0430\u0441 \u043D\u0435\u0442 \u043F\u0440\u0430\u0432 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A \u044D\u0442\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \ \u0437\u0430\u043F\u0440\u043E\u0448\u0435\u043D\u043D\u043E\u0439 \u0412\u0430\u043C\u0438 - error.title.404= \u0418\u0437\u0432\u0438\u043D\u0438\u0442\u0435! \u041C\u044B \u043D\u0435 \u043C\u043E\u0436\u0435\u043C \u043D\u0430\u0439\u0442\u0438 \u0432\u0430\u0448 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 (404) error.text.404=\u0424\u0430\u0439\u043B, \u0437\u0430\u043F\u0440\u043E\u0448\u0435\u043D\u043D\u044B\u0439 \u0412\u0430\u043C\u0438 \u043D\u0430 \u044D\u0442\u043E\u043C \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. @@ -378,7 +373,6 @@ memberPermissions.permissionHelp= \u0410\u0432\u0442\u043E\u0440 - \u043C\u043E\u0436\u0435\u0442 \u0432\u0435\u0441\u0442\u0438 \u0437\u0430\u043F\u0438\u0441\u0438, \u043D\u043E \u043D\u0435 \u0443\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u043E\u043C \u041E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u043D\u044B\u0439 - \u043C\u043E\u0436\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u0447\u0435\u0440\u043D\u043E\u0432\u044B\u0435 \u0437\u0430\u043F\u0438\u0441\u0438 - # --------------------------------------------------------- New user registation newUser.addNewUser=\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u043D\u043E\u0432\u043E\u0433\u043E \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F @@ -404,7 +398,6 @@ pagesForm.add=Add - \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C pagesForm.themesReminder=\u041D\u0430\u043F\u043E\u043C\u0438\u043D\u0430\u043D\u0438\u0435: \u0432\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 {0} \u043E\u0431\u0449\u0443\u044E \u0442\u0435\u043C\u0443, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0435\u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0448\u0430\u0431\u043B\u043E\u043D\u0430 \u043D\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043C\u0435\u0442\u043D\u044B \u0432 \u0412\u0430\u0448\u0435\u043C \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0435. editPages.title.removeOK=\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0448\u0430\u0431\u043B\u043E\u043D\u0430. - # -------------------------------------------------------------------- Page edit pageForm.link=\u0421\u0441\u044B\u043B\u043A\u0430 @@ -417,41 +410,6 @@ pageRemove.pageId=\u0418\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u pageRemove.pageName=\u0418\u043C\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B -# ---------------------------------------------------- Ping Target Admin/Editing - -commonPingTargets.subtitle=\u0423\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043E\u0431\u0449\u0438\u043C\u0438 \u0430\u0434\u0440\u0435\u0441\u0430\u043C\u0438 \u043F\u0438\u043D\u0433\u0430 \u0434\u043B\u044F \u0432\u0441\u0435\u0445 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u043E\u0432 -commonPingTargets.commonPingTargets=\u041E\u0431\u0449\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043F\u0438\u043D\u0433\u0430 -commonPingTargets.explanation=\u042D\u0442\u0438 \u0441\u0430\u0439\u0442\u044B \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B \u0441\u043E \u0432\u0441\u0435\u0445 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u043E\u0432 \u0434\u043B\u044F \u043F\u0438\u043D\u0433\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F - -pings.title=\u041D\u0430\u0441\u0442\u0440\u043E\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u043F\u0438\u043D\u0433 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 -pings.subtitle=\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u043E\u0433\u043E \u043F\u0438\u043D\u0433\u0430 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 {0} -pings.explanation=\u041F\u0438\u043D\u0433 \u043F\u043E\u0437\u0432\u043E\u043B\u044F\u0435\u0442 \u0412\u0430\u043C \u00AB\u0443\u0432\u0435\u0434\u043E\u043C\u0438\u0442\u044C\u00BB \u0441\u0430\u0439\u0442\u044B \u043E \u0442\u043E\u043C, \u0447\u0442\u043E \u0412\u0430\u0448 \u0434\u043D\u0435\u0432\u043D\u0438\u043A \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F, \u0441 \u0442\u0435\u043C, \u0447\u0442\u043E\u0431\u044B \u0441\u0430\u0439\u0442\u044B \u043C\u043E\u0433\u043B\u0438 \u043F\u0440\u043E\u0447\u0435\u0441\u0442\u044C \u0412\u0430\u0448\u0443 \u043B\u0435\u043D\u0442\u0443 \u0438 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u043F\u0438\u043D\u0433 \u0442\u0435\u0445 \u0441\u0430\u0439\u0442\u043E\u0432, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0445\u043E\u0442\u0435\u043B\u0438 \u0431\u044B \u0443\u0432\u0435\u0434\u043E\u043C\u043B\u044F\u0442\u044C \u043E \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0438 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 \u043F\u043E\u0441\u0442\u043E\u044F\u043D\u043D\u043E. \u0422\u0430\u043A\u0436\u0435 \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0432\u0440\u0443\u0447\u043D\u0443\u044E \u0432\u044B\u0437\u0432\u0430\u0442\u044C \u043F\u0438\u043D\u0433 \u0434\u043B\u044F \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u043D\u044B\u0445 \u0441\u0430\u0439\u0442\u043E\u0432 \u0441 \u044D\u0442\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B. \u041F\u0440\u0438\u043C\u0435\u0447\u0430\u043D\u0438\u0435: \u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F, \u0447\u0442\u043E \u0412\u044B \u0437\u0430\u0440\u0435\u0433\u0435\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442\u0435 \u0434\u043D\u0435\u0432\u043D\u0438\u043A \u043D\u0430 \u0441\u0430\u0439\u0442\u0435 \u043F\u0435\u0440\u0435\u0434 \u0442\u0435\u043C, \u043A\u0430\u043A \u043F\u043E\u0441\u044B\u043B\u0430\u0442\u044C \u043F\u0438\u043D\u0433. -pings.commonPingTargets=\u041E\u0431\u0449\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043F\u0438\u043D\u0433\u0430 -pings.commonPingTargetsExplanation=\u042D\u0442\u0438 \u0430\u0434\u0440\u0435\u0441\u0430 \u043F\u0438\u043D\u0433\u0430 \u0431\u044B\u043B\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u044B \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u043E\u043C \u0441\u0430\u0439\u0442\u0430 \u0434\u043B\u044F \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F\u043C\u0438. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043F\u043E\u0441\u043B\u0430\u0442\u044C \u043F\u0438\u043D\u0433 \u043D\u0430 \u043B\u044E\u0431\u043E\u0439 \u0438\u0437 \u044D\u0442\u0438\u0445 \u0437\u043D\u0430\u043A\u043E\u043C\u044B\u0445 \u0441\u0430\u0439\u0442\u043E\u0432. - -ping.successful=\u041F\u0438\u043D\u0433 \u043F\u0440\u043E\u0448\u0435\u043B \u0443\u0441\u043F\u0435\u0448\u043D\u043E -ping.transmittedButError=\u041F\u0438\u043D\u0433 \u0431\u044B\u043B \u043F\u0435\u0440\u0435\u0434\u0430\u043D, \u043D\u043E \u0441\u0435\u0440\u0432\u0435\u0440 \u043E\u0442\u0432\u0435\u0442\u0438\u043B \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431 \u043E\u0448\u0438\u0431\u043A\u0435 -ping.transmissionFailed=\u041F\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u043F\u0438\u043D\u0433\u0430 \u043F\u043E\u0442\u0435\u0440\u044F\u043D\u0430. \u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E\u0441\u0442\u044C URL \u0430\u0434\u0440\u0435\u0441\u0430 \u043F\u0438\u043D\u0433\u0430 -ping.unknownHost=\u0418\u043C\u044F \u0445\u043E\u0441\u0442\u0430 \u0432 URL \u0430\u0434\u0440\u0435\u0441\u0430 \u043F\u0438\u043D\u0433\u0430 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E -ping.networkConnectionFailed=\u041F\u0440\u0438 \u043F\u0440\u043E\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0438 \u043F\u0438\u043D\u0433\u0430 \u0432\u043E\u0437\u043D\u0438\u043A\u043B\u0438 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B \u0441\u0435\u0442\u0438 -ping.pingProcessingIsSuspended=\u0412\u043E\u0437\u043C\u043E\u0436\u043D\u043E\u0441\u0442\u044C \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043F\u0438\u043D\u0433\u0430 \u0431\u044B\u043B\u0430 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u043F\u0440\u0438\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430 \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u043E\u043C \u0441\u0430\u0439\u0442\u0430 - -pingTarget.pingUrl=URL \u043F\u0438\u043D\u0433\u0430 -pingTarget.remove=\u0423\u0434\u0430\u043B\u0438\u0442\u044C -pingTarget.confirmRemoveTitle=\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043F\u0438\u043D\u0433\u0430 -pingTarget.confirmCommonRemove=\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u044D\u0442\u043E\u0433\u043E \u0430\u0434\u0440\u0435\u0441\u0430 \u043F\u043E\u0432\u043B\u0435\u0447\u0435\u0442 \u0437\u0430 \u0441\u043E\u0431\u043E\u0439 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0432\u0441\u0435\u0445 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0445 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0445 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A \u043F\u0438\u043D\u0433\u0430, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u044E\u0449\u0438\u0445 \u044D\u0442\u043E\u0442 \u0430\u0434\u0440\u0435\u0441. \u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B? -pingTarget.enable=Enable -pingTarget.enabled=ON -pingTarget.disable=Disable -pingTarget.disabled=OFF -pingTarget.auto=\u0410\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 -pingTarget.manual=\u0412\u0440\u0443\u0447\u043D\u0443\u044E -pingTarget.sendPingNow=\u041F\u043E\u0441\u043B\u0430\u0442\u044C \u043F\u0438\u043D\u0433 - -pingTarget.nameNotUnique=\u0418\u043C\u044F \u044D\u0442\u043E\u0439 \u0446\u0435\u043B\u0438 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u0443\u0435\u0442 \u0441 \u0434\u0440\u0443\u0433\u0438\u043C \u0432 \u0442\u043E\u043C \u0436\u0435 \u043D\u0430\u0431\u043E\u0440\u0435 \u0446\u0435\u043B\u0435\u0439. -pingTarget.malformedUrl=URL \u043D\u0435\u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E \u0441\u0444\u043E\u0440\u043C\u0438\u0440\u043E\u0432\u0430\u043D. -pingTarget.unknownHost=\u0418\u043C\u044F \u0445\u043E\u0441\u0442\u0430 \u0432 \u0437\u0430\u0434\u0430\u043D\u043D\u043E\u043C URL \u043D\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, - # --------------------------------------------------------------- Planet Roller planetConfig.subtitle=\u041D\u0430\u0441\u0442\u0440\u043E\u0438\u0442\u044C Roller'\u0441\u043A\u0438\u0439 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 \u0430\u0433\u0440\u0435\u0433\u0430\u0442\u043E\u0440 \u043D\u043E\u0432\u043E\u0441\u0442\u043D\u044B\u0445 \u043B\u0435\u043D\u0442 @@ -525,17 +483,14 @@ tabbedmenu.website.themes=\u0422\u0435\u043C\u0430 tabbedmenu.website.pages=\u0428\u0430\u0431\u043B\u043E\u043D\u044B tabbedmenu.website.members=\u0423\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0438 tabbedmenu.website.maintenance=\u041E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435 -tabbedmenu.weblog.pingSetup=\u041F\u0438\u043D\u0433\u0438 tabbedmenu.admin=Global Admin tabbedmenu.admin.config=\u041A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044F tabbedmenu.admin.userAdmin=User Admin -tabbedmenu.admin.pingTargets=\u0410\u0434\u0440\u0435\u0441\u0430\u0442\u044B \u043F\u0438\u043D\u0433\u0430 tabbedmenu.planet=Planet Admin tabbedmenu.admin.planetConfig=\u041A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044F tabbedmenu.admin.planetGroupSubs=\u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 tabbedmenu.admin.planetGroups=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0435 \u0433\u0440\u0443\u043F\u043F\u044B - # ----------------------------------------------------------------------- Theme themeEditor.title=\u0422\u0435\u043C\u0430 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 @@ -666,7 +621,6 @@ weblogEdit.pending = \u041E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 weblogEdit.publishedEntries = \u041F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u0437\u0430\u043F\u0438\u0441\u044C weblogEdit.previewMode = \u041F\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 weblogEdit.post = \u041F\u043E\u0441\u0442 \u0432 \u0434\u043D\u0435\u0432\u043D\u0438\u043A -weblogEdit.ping = \u041F\u0438\u043D\u0433 \u043D\u0430 Weblogs.com weblogEdit.permaLink = \u0441\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u044C weblogEdit.published = \u041E\u043F\u0443\u0431\u043B\u0438\u043A\u043E\u0432\u0430\u043D weblogEdit.pubTime = \u0412\u0440\u0435\u043C\u044F \u043F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u0438 @@ -682,7 +636,6 @@ weblogEdit.title = \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A weblogEdit.unsaved = \u041D\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E weblogEdit.updateTime = \u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 - weblogEdit.allowComments = \u0412\u0441\u0435 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 weblogEdit.commentDays = \u0434\u043B\u044F weblogEdit.unlimitedCommentDays = \u043D\u0435\u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u043D\u044B\u0435 \u0434\u043D\u0435\u0439 @@ -693,20 +646,16 @@ weblogEdit.days30=30 \u0434\u043D\u0435\u0439 weblogEdit.days60=60 \u0434\u043D\u0435\u0439 weblogEdit.days90=90 \u0434\u043D\u0435\u0439 - weblogEdit.pluginsToApply = \u041C\u043E\u0434\u0443\u043B\u0438 \u0434\u043B\u044F \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F weblogEdit.miscSettings = \u0420\u0430\u0437\u043D\u044B\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 weblogEdit.rightToLeft = \u0422\u0435\u043A\u0441\u0442 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F \u0441\u043F\u0440\u0430\u0432\u0430 \u043D\u0430\u043B\u0435\u0432\u043E weblogEdit.pinnedToMain = \u041F\u0440\u0438\u043A\u0440\u0435\u043F\u043B\u044F\u0435\u0442\u0441\u044F \u043A \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u043C\u0443 - - weblogEdit.comment=\u041A\u043E\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 weblogEdit.error.incompleteEntry = \u0417\u0430\u043F\u0438\u0441\u044C \u0434\u043E\u043B\u0436\u043D\u0430 \u0438\u043C\u0435\u0442\u044C \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A - # ---------------------------------------------------------- Weblog Entry Remove weblogEntryRemove.removeweblogEntry = \u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0437\u0430\u043F\u0438\u0441\u0438 \u0438\u0437 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 weblogEntryRemove.yes = \u0414\u0430 @@ -834,7 +783,6 @@ websiteRemove.youSure = \u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B, websiteRemove.websiteId = \u0434\u043D\u0435\u0432\u043D\u0438\u043A ID websiteRemove.websiteName = \u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0434\u043D\u0435\u0432\u043D\u0438\u043A\u0430 - # ------------------------------------------------------------- Welcome new user welcome.title = \u0414\u043E\u0431\u0440\u043E \u043F\u043E\u0436\u0430\u043B\u043E\u0432\u0430\u0442\u044C \u0432 Roller welcome.accountCreated = \u0412\u0430\u0448\u0430 \u043D\u043E\u0432\u044B\u0430\u044F \u0443\u0447\u0435\u0442\u043D\u0430\u044F \u0437\u0430\u043F\u0438\u0441\u044C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u0431\u044B\u043B\u0430 \u0441\u043E\u0437\u0434\u0430\u043D\u0430. @@ -870,7 +818,6 @@ yourWebsites.invited = \u0412\u044B \u0431\u044B\u043B\u0438 \u043F\u0440\u0438\ yourWebsites.accept = \u043F\u0440\u0438\u043D\u044F\u0442\u044C yourWebsites.decline = \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C - yourWebsites.tableTitle = \u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A yourWebsites.tableDescription = \u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 yourWebsites.select = \u0412\u044B\u0431\u043E\u0440 @@ -889,7 +836,6 @@ yourWebsites.createWeblog = \u041D\u043E\u0432\u0430\u044F \u0437\u0430\u043F\u0 yourWebsites.createWeblog.desc = \ \u0421\u043A\u0430\u0436\u0438\u0442\u0435 \u0431\u043E\u043B\u044C\u0448\u0435? \u0412\u043E\u0437\u043C\u043E\u0436\u043D\u043E \u0434\u0440\u0443\u0433\u043E\u0439 \u0434\u043D\u0435\u0432\u043D\u0438\u043A- \u0442\u043E, \u0432 \u0447\u0435\u043C \u0412\u044B \u043D\u0443\u0436\u0434\u0430\u0435\u0442\u0435\u0441\u044C. - yourWebsites.editProfile=\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043F\u0440\u0444\u0438\u043B\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F yourWebsites.editProfile.desc=\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u043F\u0430\u0440\u043E\u043B\u044F, \u043A\u043E\u043D\u0442\u0430\u043A\u0442\u043D\u043E\u0439 \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u0438, \u0432\u044B\u0431\u043E\u0440 \u044F\u0437\u044B\u043A\u0430 @@ -900,7 +846,6 @@ yourWebsites.planetAdmin=\u041F\u043B\u0430\u043D\u0435\u0442\u0430 \u0430\u0434 yourWebsites.planetAdmin.desc=\ \u041C\u0435\u043D\u0435\u0434\u0436\u0435\u0440 \u043F\u043B\u0430\u043D\u0435\u0442\u044B \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F. \u041E\u0440\u0433\u0430\u043D\u0438\u0437\u0443\u0439\u0442\u0435 \u0433\u0440\u0443\u043F\u044B, \u0443\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 - yourWebsites.invitations=\u041F\u0440\u0438\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u044F \u0432 \u0434\u043D\u0435\u0432\u043D\u0438\u043A yourWebsites.invitationsPrompt=\u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0434\u043D\u043E \u0438 \u0431\u043E\u043B\u0435\u0435 \u043F\u0440\u0438\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u0439 \u043F\u0440\u0438\u043D\u044F\u0442\u044C \u0438\u043B\u0438 \ \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C: diff --git a/app/src/main/resources/ApplicationResources_zh_CN.properties b/app/src/main/resources/ApplicationResources_zh_CN.properties index 03792b4a7..a3a77a029 100644 --- a/app/src/main/resources/ApplicationResources_zh_CN.properties +++ b/app/src/main/resources/ApplicationResources_zh_CN.properties @@ -122,7 +122,6 @@ bookmarksImport.subtitle=\u5BFC\u5165OPML\u683C\u5F0F\u4E66\u7B7E\u5230\u535A\u5 bookmarksImport.prompt=\u4E0A\u4F20\u6807\u51C6OPML\u683C\u5F0F\u7684\u4E66\u7B7E\u6587\u4EF6\u3002\u8FD9\u4E9B\u4E66\u7B7E\u5C06\u4F1A\u5BFC\u5165\u5230\u4E00\u4E2A\u65B0\u7684\u4E66\u7B7E\u5939\uFF08\u4E66\u7B7E\u5939\u7684\u540D\u5B57\u4F7F\u7528\u9ED8\u8BA4\u7684\u65F6\u95F4\u6233\uFF09\u3002\u5BFC\u5165\u4E4B\u540E\u4F60\u53EF\u4EE5\u8FDB\u884C\u7F16\u8F91\u6216\u8005\u79FB\u52A8\u5230\u5176\u5B83\u4E66\u7B7E\u5939\u3002 - bookmarksImport.imported=\u5BFC\u5165OPML\u683C\u5F0F\u4E66\u7B7E\u5230\u4E66\u7B7E\u5939 [{0}] bookmarksImport.error=\u5BFC\u5165\u4E66\u7B7E\u9519\u8BEF: {0} bookmarksImport.import=\u5BFC\u5165 @@ -172,8 +171,6 @@ categoriesForm.rootTitle=\u5206\u7C7B categoriesForm.rootPrompt=\u8FD9\u91CC\u662F\u4F60\u7684\u9876\u7EA7\u5206\u7C7B\uFF0C\u535A\u5BA2\u81F3\u5C11\u8981\u6709\u4E00\u4E2A\u5206\u7C7B\u3002\u5206\u7C7B\u662F\u7528\u6765\u7EC4\u7EC7\u4F60\u7684\u6587\u7AE0\uFF0C\u4F60\u7684\u8BFB\u8005\u4E5F\u53EF\u4EE5\u901A\u8FC7\u9009\u62E9\u67D0\u4E00\u4E2A\u5206\u7C7B\u6765\u8BA2\u9605\u4ED6\u611F\u5174\u8DA3\u7684\u4E13\u9898\u3002 - - categoriesForm.addCategory=\u6DFB\u52A0\u5206\u7C7B categoriesForm.move=\u79FB\u52A8 categoriesForm.move.confirm=\u786E\u5B9A\u8981\u79FB\u52A8\u9009\u4E2D\u7684\u5206\u7C7B\u5417\uFF1F @@ -238,14 +235,9 @@ commentManagement.website.subtitle=\u7BA1\u7406\u535A\u5BA2 {0} \u4 commentManagement.tip=\u6709\u8BC4\u8BBA\u3002\u4F60\u53EF\u4EE5\u901A\u8FC7\u4E0B\u9762\u7684\u9009\u62E9\u6846\u6539\u53D8\u6838\u51C6\u72B6\u6001\uFF0C\u5783\u573E\u8BC4\u8BBA\u72B6\u6001\u548C\u5220\u9664\u8BC4\u8BBA\u3002\u5982\u679C\u4F60\u505A\u4E86\u4EFB\u4F55\u66F4\u6539\uFF0C\u8BB0\u5F97\u70B9\u51FB\u9876\u90E8\u7684 \u4FDD\u5B58\u66F4\u6539\u6309\u94AE\u3002 - - commentManagement.globalTip=\u4F5C\u4E3A\u8D85\u7EA7\u7BA1\u7406\u5458\uFF0C\u4F60\u53EF\u4EE5\u66F4\u6539\u5783\u573E\u8BC4\u8BBA\u72B6\u6001\uFF0C\u53EF\u4EE5\u901A\u8FC7\u9009\u4E2D\u4E0B\u9762\u7684\u9009\u62E9\u6846\u5220\u9664\u8BC4\u8BBA\u3002\u4F46\u662F\u4F60\u4E0D\u80FD\u66F4\u6539\u5904\u4E8E\u7B49\u5F85\u548C\u6838\u51C6\u72B6\u6001\u7684\u8BC4\u8BBA\uFF0C\u4E5F\u5C31\u662F\u8BF4\u4F60\u4E0D\u80FD\u4ECB\u5165\u5E72\u6270\u4EFB\u4F55\u4EBA\u6838\u51C6\u8BC4\u8BBA\u7684\u5DE5\u4F5C\u6D41\u7A0B\u3002\u5982\u679C\u4F60\u505A\u4E86\u4EFB\u4F55\u66F4\u6539\uFF0C\u4E0D\u8981\u5FD8\u4E86\u70B9\u51FB\u9876\u90E8\u7684 \u4FDD\u5B58\u66F4\u6539 \u6765\u4F7F\u5176\u751F\u6548\u3002 - - - commentManagement.nowShowing=\u663E\u793A {0} \u8BC4\u8BBA commentManagement.noCommentsFound=\u6CA1\u6709\u8BC4\u8BBA @@ -313,8 +305,6 @@ configForm.subtitle=\u66F4\u6539\u5168\u7F51\u7AD9\u8303\u56F4\u7684\u914D\u7F6E configForm.prompt=\u4F60\u5728\u8FD9\u91CC\u505A\u7684\u4FEE\u6539\u5BF9\u6240\u6709\u535A\u5BA2\u751F\u6548\u3002\u6CE8\u610F\u8FD9\u4EC5\u4EC5\u662Fruntime\u5C5E\u6027\u3002\u5177\u4F53\u64CD\u4F5C\u8BF7\u67E5\u8BE2\u7CFB\u7EDF\u6587\u6863\u5173\u4E8E\u8986\u76D6\u521D\u59CB\u5C5E\u6027\u90E8\u5206\uFF08\u5728roller.proerties\u6587\u4EF6\u4E2D\uFF09\u3002 - - configForm.siteSettings=\u7AD9\u70B9\u8BBE\u7F6E configForm.siteName=\u7AD9\u70B9\u540D\u79F0\uFF08\u7528\u4E8E\u4E3B\u9875\u548C\u65B0\u95FB\u6E90\uFF09 configForm.shortName=\u7B80\u79F0\uFF08\u663E\u793A\u5728\u7AD9\u70B9\u6A2A\u5E45\u4E2D\uFF09 @@ -323,7 +313,6 @@ configForm.frontpageWeblogHandle=\u4F5C\u4E3A\u7AD9\u70B9\u9996\u9875\u7684\u535 configForm.frontpageWeblogAggregated=\u542F\u7528\u5168\u7AD9\u805A\u5408\u9996\u9875 configForm.siteAdminEmail=\u7BA1\u7406\u5458\u7535\u5B50\u90AE\u7BB1 configForm.absoluteUrl=\u7AD9\u70B9\u7684\u7EDD\u5BF9URL\uFF08\u5982\u9700\u8BBE\u7F6E\uFF09 -configForm.suspendPingProcessing=\u662F\u5426\u6682\u505C\u6240\u6709\u63A8\u9001\u5904\u7406\uFF1F configForm.allowUserWeblogCreation=\u662F\u5426\u5141\u8BB8\u7528\u6237\u65B0\u5EFA\u535A\u5BA2\uFF1F configForm.none=\u672A\u6307\u5B9A @@ -379,7 +368,6 @@ createWebsite.tip.name=\u8FD9\u4E2A\u540D\u5B57\u662F\u4F60\u7684\u535A\u5BA2\u7 createWebsite.tip.description=\u4F60\u5BF9\u535A\u5BA2\u7684\u63CF\u8FF0\u5C06\u4F1A\u5728\u4F60\u7684\u535A\u5BA2\u4E0A\u90E8\u663E\u793A\uFF08\u53D6\u51B3\u4E8E\u4F60\u9009\u62E9\u7684\u4E3B\u9898\u662F\u5426\u652F\u6301\uFF09\uFF0C\u800C\u4E14\u8FD8\u4F1A\u5728\u4F60\u7684\u7AD9\u70B9\u65B0\u95FB\u6E90\u4E2D\u51FA\u73B0\u3002\u8FD9\u4E2A\u533A\u57DF\u4E0D\u652F\u6301\u8D85\u6587\u672C\u6807\u8BB0\u8BED\u8A00\uFF08HTML\uFF09\u3002 - createWebsite.tip.handle=\u535A\u5BA2\u4EE3\u53F7\u662F\u4E00\u4E2A\u5BF9\u4F60\u7684\u535A\u5BA2\u8FDB\u884C\u7B80\u77ED\u63CF\u8FF0\u7684\u8BCD\u3002\u5B83\u5C06\u4F1A\u4F5C\u4E3A\u4F60\u7684\u535A\u5BA2URL\uFF0C\u6240\u4EE5\u8BF7\u4F60\u5728ASCII\u5B57\u7B26\uFF08a\uFF0Dz\uFF0CA\uFF0DZ\u548C0\uFF0D9\uFF09\u4E2D\u9009\u62E9\u800C\u4E0D\u8981\u4F7F\u7528\u8D85\u6587\u672C\u6807\u8BB0\u8BED\u8A00\uFF08HTML\uFF09\u3002 @@ -392,7 +380,6 @@ createWebsite.tip.locale=\u9ED8\u8BA4\u7684\u533A\u57DF\u8BED\u8A00\u8BBE\u7F6E\ createWebsite.tip.theme=\u4E3B\u9898\u7528\u6765\u786E\u5B9A\u4F60\u7684\u535A\u5BA2\u663E\u793A\u5177\u4F53\u91C7\u7528\u54EA\u79CD\u98CE\u683C\u6765\u663E\u793A\u3002\u4EE5\u540E\uFF0C\u4F60\u4E5F\u53EF\u4EE5\u65B9\u4FBF\u7684\u4ECE\u4E00\u79CD\u4E3B\u9898\u8F6C\u6362\u4E3A\u53E6\u4E00\u79CD\u4E3B\u9898\uFF0C\u8FD8\u53EF\u4EE5\u4F7F\u7528\u5B9A\u5236\u529F\u80FD\u6765\u4E2A\u6027\u5316\u3002 - createWebsite.handle=\u4EE3\u53F7 createWebsite.emailAddress=\u7535\u5B50\u90AE\u7BB1 createWebsite.locale=\u533A\u57DF\u8BED\u8A00 @@ -514,8 +501,6 @@ index.createUserDone=\u5B8C\u6210\uFF1A\u76EE\u524D\u6709{0}\u4E2A\u7528\u6237 index.createUserHelp=\u5728\u5F00\u59CB\u4F7F\u7528\u535A\u5BA2\u524D\uFF0C\u4F60\u9700\u8981\u521B\u5EFA\u4E00\u4E2A\u65B0\u7528\u6237\u4EE5\u8FDB\u884C\u7BA1\u7406\u64CD\u4F5C\u3002\u8BF7\u6CE8\u610F\uFF1A\u4F60\u521B\u5EFA\u7684\u7B2C\u4E00\u4E2A\u7528\u6237\u5C06\u62E5\u6709\u8D85\u7EA7\u7BA1\u7406\u5458\u6743\u9650\uFF0C\u53EF\u4EE5\u7BA1\u7406\u666E\u901A\u7528\u6237\u548C\u5176\u4ED6\u7BA1\u7406\u5458\u3002 - - index.createUserBy=\u521B\u5EFA\u7B2C\u4E00\u4E2A\u7528\u6237\uFF1A index.createUserPage=\u65B0\u7528\u6237\u6CE8\u518C\u9875 @@ -524,8 +509,6 @@ index.createWeblogDone=\u5B8C\u6210\uFF1A\u76EE\u524D\u6709{0}\u4E2A\u535A\u5BA2 index.createWeblogHelp=\u5728\u5F00\u59CB\u64B0\u5199\u6587\u7AE0\u524D\uFF0C\u4F60\u9700\u8981\u521B\u5EFA\u81F3\u5C11\u4E00\u4E2A\u535A\u5BA2\u3002\u4F60\u53EF\u4EE5\u521B\u5EFA\u4EFB\u610F\u6570\u91CF\u7684\u535A\u5BA2\u3002\u6BCF\u4E2A\u7528\u6237\u53EF\u4EE5\u62E5\u6709\u591A\u4E2A\u535A\u5BA2\uFF0C\u6BCF\u4E2A\u535A\u5BA2\u53EF\u4EE5\u62E5\u6709\u591A\u4E2A\u4F5C\u8005\u3002 - - index.createWeblogBy=\u521B\u5EFA\u7B2C\u4E00\u4E2A\u535A\u5BA2\uFF1A index.createWeblogPage=\u65B0\u535A\u5BA2\u521B\u5EFA\u9875 @@ -533,8 +516,6 @@ index.setFrontpage=\u6307\u5B9A\u4E00\u4E2A\u9996\u9875\u535A\u5BA2 index.setFrontpageHelp=\u4F60\u9700\u8981\u6307\u5B9A\u4E00\u4E2A\u535A\u5BA2\u4F5C\u4E3A\u9996\u9875\u535A\u5BA2\uFF0C\u4F60\u53EF\u4EE5\u901A\u8FC7\u7CFB\u7EDF\u7BA1\u7406->\u53C2\u6570\u8BBE\u7F6E\u9875\u6307\u5B9A\u9996\u9875\u535A\u5BA2\u3002\u5982\u679C\u4F60\u5DF2\u521B\u5EFA\u81F3\u5C11\u4E00\u4E2A\u535A\u5BA2\uFF0C\u4E5F\u53EF\u4EE5\u76F4\u63A5\u5728\u6B64\u5904\u6307\u5B9A\u3002 - - frontpageConfig.frontpageBlogName=\u4F5C\u4E3A\u9996\u9875\u535A\u5BA2\u7684\u535A\u5BA2\u540D\u79F0 frontpageConfig.frontpageAggregated=\u542F\u7528\u5168\u7AD9\u5185\u5BB9\u805A\u5408\u9996\u9875 frontpageConfig.values.saved=\u5C5E\u6027\u4FDD\u5B58\u6210\u529F @@ -550,8 +531,6 @@ inviteMember.subtitle=\u9080\u8BF7\u4E00\u4E2A\u65B0\u7528\u6237\u52A0\u5165\u8F inviteMember.prompt=\u901A\u8FC7\u7535\u5B50\u90AE\u4EF6\u9080\u8BF7\u4E00\u4E2A\u7528\u6237\u52A0\u5165\u8FD9\u4E2A\u535A\u5BA2\uFF0C\u9009\u62E9\u4E00\u79CD\u8BB8\u53EF\uFF0C\u7136\u540E\u70B9\u51FB\u53D1\u51FA\u9080\u8BF7\u6309\u94AE\u3002\u5982\u679C\u7528\u6237\u672A\u5728\u4E0B\u9762\u663E\u793A\uFF0C\u53EF\u4EE5\u952E\u5165\u4E00\u4E24\u4E2A\u5B57\u6BCD\u6765\u627E\u5230\u76F8\u5339\u914D\u7684\u7528\u6237\u3002 - - inviteMember.userName=\u7528\u6237\u540D inviteMember.administrator=\u7BA1\u7406\u5458 inviteMember.author=\u4F5C\u8005 @@ -570,7 +549,6 @@ inviteMember.notificationSubject=Roller\u9080\u8BF7\u4F60\u52A0\u5165\u535A\u5BA inviteMember.notificationContent=\u4F60\u5DF2\u88AB\u9080\u8BF7\u52A0\u5165\u535A\u5BA2\u201C{0}\u201D({1})\u3002\u8BF7\u70B9\u51FB\u4E0B\u9762\u7684\u94FE\u63A5\u4EE5[{2}]\u8D26\u53F7\u767B\u5F55\uFF0C\u9009\u62E9\u63A5\u53D7\u6216\u8005\u62D2\u7EDD\u8FD9\u4E2A\u9080\u8BF7<{3}>\u3002 - inviteMember.disabled=\u9080\u8BF7\u529F\u80FD\u5DF2\u7981\u7528 # ----------------------------------------------------------------------- Login @@ -585,8 +563,6 @@ installer.whatHappened=\u53D1\u751F\u4E86\u4EC0\u4E48\uFF1F installer.whatHappenedDatabaseConnectionError=\u53D1\u751F\u4E86\u4E00\u4E2A\u6570\u636E\u5E93\u9519\u8BEF\uFF0C\u53EF\u80FD\u662F\u56E0\u4E3A\u4F60\u7684\u6570\u636E\u5E93\u8FDE\u63A5\u914D\u7F6E\u4E0D\u6B63\u786E\u3002\u4F60\u5FC5\u987B\u4FEE\u590D\u6B64\u95EE\u9898\u518D\u91CD\u65B0\u542F\u52A8\u6216\u90E8\u7F72Roller\u624D\u80FD\u7EE7\u7EED\u3002\u4EE5\u4E0B\u662F\u5F53Roller\u5C1D\u8BD5\u5EFA\u7ACB\u8FDE\u63A5\u65F6\u6240\u53D1\u751F\u7684\u95EE\u9898\u8BB0\u5F55\uFF1A - - installer.whyDidThatHappen=\u95EE\u9898\u4E3A\u4EC0\u4E48\u53D1\u751F\uFF1F installer.aboutTheException=\u5982\u679C\u4E0A\u9762\u7684\u7EBF\u7D22\u8FD8\u4E0D\u8DB3\u4EE5\u5E2E\u52A9\u4F60\u627E\u51FA\u539F\u56E0\u6240\u5728\uFF0C\u8FD9\u91CC\u8FD8\u6709\u4E00\u4E9B\u66F4\u591A\u7EC6\u8282\u3002\u6B64\u95EE\u9898\u7684\u6839\u6E90\u662F\u4E00\u4E2A\u4EE5\u4E0B\u7C7B\u578B\u7684\u5F02\u5E38 @@ -600,11 +576,6 @@ installer.unknownError=\u53D1\u751F\u4E86\u4E00\u4E2A\u672A\u77E5\u9519\u8BEF installer.whatHappenedUnknown=\u5F53 Roller \u5C1D\u8BD5\u68C0\u67E5\u6570\u636E\u5E93\u72B6\u6001\u6216\u5F15\u5BFC\u4FE1\u606F\u65F6\u53D1\u751F\u4E86\u672A\u77E5\u7684\u610F\u5916\u9519\u8BEF\u3002Roller \u65E0\u6CD5\u786E\u5B9A\u53D1\u751F\u4E86\u4EC0\u4E48\uFF0C\u6240\u4EE5\u4F60\u5FC5\u987B\u67E5\u770B\u4F60\u7684\u670D\u52A1\u5668\u7684\u65E5\u5FD7\u6587\u4EF6\u5E76\u81EA\u884C\u8BCA\u65AD\u9519\u8BEF\u3002\u8BF7\u53C2\u7167 Roller wiki \u4E0A\u7684\u6307\u5BFC\u5E76\u5728 Roller \u7528\u6237\u90AE\u4EF6\u5217\u8868 \u5BFB\u6C42\u5E2E\u52A9\u3002 - - - - - # create tables installer.database.creation.pageTitle=\u521B\u5EFA\u6570\u636E\u5E93\u8868 installer.noDatabaseTablesFound=\u672A\u627E\u5230\u6570\u636E\u5E93\u8868 @@ -622,9 +593,6 @@ installer.errorCreatingTables=\u8868\u521B\u5EFA\u5931\u8D25 installer.errorCreatingTablesExplanation=\u521B\u5EFA\u8868\u65F6\u53D1\u751F\u9519\u8BEF\uFF0C\u53EF\u80FD\u662F\u7531\u4E8E\u6570\u636E\u5E93\u521B\u5EFA\u811A\u672C\u4E2D\u7684\u9519\u8BEF\u6216\u662F\u56E0\u4E3A\u4F60\u4F7F\u7528\u4E86\u4E0D\u53D7\u652F\u6301\u7684\u6570\u636E\u5E93\u3002\u4F60\u5FC5\u987B\u4FEE\u590D\u6B64\u95EE\u9898\u7136\u540E\u91CD\u65B0\u542F\u52A8\u6216\u91CD\u65B0\u90E8\u7F72 Roller \u624D\u80FD\u7EE7\u7EED\u4E0B\u4E00\u6B65\u3002 \u4EE5\u4E0B\u662F\u5728\u521B\u5EFA\u8FC7\u7A0B\u4E2D\u4EA7\u751F\u7684\u6210\u529F/\u5931\u8D25\u6D88\u606F\uFF1A - - - # upgrade tables installer.database.upgrade.pageTitle=\u5347\u7EA7\u6570\u636E\u5E93\u8868 installer.databaseUpgradeNeeded=\u6570\u636E\u5E93\u8868\u9700\u8981\u5347\u7EA7 @@ -641,10 +609,6 @@ installer.errorUpgradingTables=\u8868\u5347\u7EA7\u51FA\u9519 installer.errorUpgradingTablesExplanation=\u5347\u7EA7\u8868\u65F6\u53D1\u751F\u9519\u8BEF\uFF0C\u53EF\u80FD\u662F\u7531\u4E8E\u6570\u636E\u5E93\u521B\u5EFA\u811A\u672C\u4E2D\u7684\u9519\u8BEF\u6216\u662F\u56E0\u4E3A\u4F60\u4F7F\u7528\u4E86\u4E0D\u53D7\u652F\u6301\u7684\u6570\u636E\u5E93\u3002\u4F60\u5FC5\u987B\u4FEE\u590D\u6B64\u95EE\u9898\u7136\u540E\u91CD\u65B0\u542F\u52A8\u6216\u91CD\u65B0\u90E8\u7F72 Roller \u624D\u80FD\u7EE7\u7EED\u4E0B\u4E00\u6B65\u3002 \u4EE5\u4E0B\u662F\u5728\u5347\u7EA7\u8FC7\u7A0B\u4E2D\u4EA7\u751F\u7684\u6210\u529F/\u5931\u8D25\u6D88\u606F\uFF1A - - - - # ----------------------------------------------------------------------- Login loginPage.title=\u6B22\u8FCE\u4F7F\u7528Roller @@ -807,7 +771,6 @@ mediaFileView.subtitle=\u535A\u5BA2 {0} \u4E2D\u7684\u5A92\u4F53\u6587\u4EF6 mediaFileView.rootPageTip=\u672C\u9875\u9762\u663E\u793A\u4F60\u6240\u4E0A\u4F20\u6216\u662F\u5728\u81EA\u5B9A\u4E49\u4E3B\u9898\u65F6\u6240\u590D\u5236\u7684\u5A92\u4F53\u6587\u4EF6\u3002\u4F60\u53EF\u4EE5\u4F7F\u7528\u76EE\u5F55\u6765\u7EC4\u7EC7\u6587\u4EF6\uFF0C\u79FB\u52A8\u548C\u91CD\u547D\u540D\u6587\u4EF6\u4E0D\u4F1A\u7834\u574F\u73B0\u6709\u7684\u6587\u4EF6\u94FE\u63A5\u3002 - mediaFileView.dirPageTip=\u8FD9\u662F\u4E00\u4E2A\u5A92\u4F53\u6587\u4EF6\u76EE\u5F55\u3002\u4F60\u53EF\u4EE5\u70B9\u51FB\u4E0A\u65B9\u7684\u5BFC\u822A\u94FE\u63A5\u8FD4\u56DE\u6839\u76EE\u5F55\u3002 mediaFileView.viewFolder=Folder @@ -891,8 +854,6 @@ memberPermissions.subtitle=\u7BA1\u7406\u535A\u5BA2\u6210\u5458\u7684\u6743\u965 memberPermissions.description=\u4F60\u53EF\u4EE5\u4F7F\u7528\u4EE5\u4E0B\u8868\u683C\u4E2D\u7684\u63A7\u4EF6\u66F4\u6539\u535A\u5BA2\u6210\u5458\u7684\u6743\u9650\uFF0C\u6216\u8005\u5F7B\u5E95\u79FB\u9664\u5176\u6210\u5458\u8D44\u683C\u3002\u6CE8\u610F\uFF0C\u4F60\u4E0D\u80FD\u66F4\u6539\u6216\u8005\u79FB\u9664\u81EA\u5DF1\u3002 - - memberPermissions.userName=\u7528\u6237\u540D memberPermissions.administrator=\u7BA1\u7406\u5458 memberPermissions.author=\u4F5C\u8005 @@ -916,8 +877,6 @@ memberPermissions.permissionsHelpTitle=\u4EC0\u4E48\u662F\u6743\u9650\uFF1F memberPermissions.permissionHelp=\u7BA1\u7406\u5458\u6743\u9650\u5141\u8BB8\u7528\u6237\u53D1\u5E03\u6587\u7AE0\u3001\u7BA1\u7406\u535A\u5BA2\u53CA\u5176\u6210\u5458\u7528\u6237
\u4F5C\u8005\u6743\u9650\u5141\u8BB8\u7528\u6237\u53D1\u5E03\u6587\u7AE0\uFF0C\u4F46\u4E0D\u80FD\u7BA1\u7406\u535A\u5BA2
\u53D7\u9650\u6743\u9650\u53EA\u5141\u8BB8\u7528\u6237\u4FDD\u5B58\u6587\u7AE0\u8349\u7A3F - - # --------------------------------------------------------- New user registation newUser.addNewUser=\u65B0\u7528\u6237\u6CE8\u518C @@ -937,8 +896,6 @@ pagesForm.subtitle=\u7BA1\u7406\u535A\u5BA2\u7684\u9875\u9762\u6A21\u677F pagesForm.tip=\u4F60\u53EF\u4EE5\u7F16\u8F91\u6A21\u677F\u6765\u5B9A\u4E49\u535A\u5BA2\u9875\u9762\u7684\u5E03\u5C40\u3001\u8272\u5F69\u548C\u5B57\u4F53\u3002\u4F60\u4E5F\u53EF\u4EE5\u65B0\u5EFA\u6A21\u677F\u6765\u5B9A\u4E49\u65B0\u7684\u9875\u9762\u3002\u6B64\u529F\u80FD\u662F\u9488\u5BF9\u9AD8\u7EA7\u7528\u6237\u7684\uFF0C\u5982\u679C\u4F60\u4E0D\u719F\u6089HTML\uFF0C\u5219\u65E0\u987B\u5173\u6CE8\u8FD9\u4E9B\u5185\u5BB9\u3002\u7F16\u8F91\u6A21\u677F\u524D\u8BF7\u6CE8\u610F\u5907\u4EFD\uFF01 - - pageForm.advancedSettings=\u9AD8\u7EA7\u8BBE\u7F6E pagesForm.action=\u64CD\u4F5C pagesForm.remove=\u79FB\u9664 @@ -962,14 +919,6 @@ pageForm.subtitle=\u7F16\u8F91\u6A21\u677F {0} (\u6240\u5C5E\u535A\ pageForm.tip=\u4F60\u53EF\u4EE5\u7F16\u8F91\u6B64\u6A21\u677F\u4EE5\u6539\u53D8\u5176\u751F\u6210\u7684\u9875\u9762\u5916\u89C2\u3002\u76F8\u5173\u7684\u6307\u4EE4\u4EE3\u7801\u8BF7\u53C2\u770B\u6A21\u677F\u7F16\u8F91\u6307\u5357\u3002\u6B64\u529F\u80FD\u662F\u9488\u5BF9\u9AD8\u7EA7\u7528\u6237\u7684\uFF0C\u5982\u679C\u4F60\u4E0D\u719F\u6089HTML\uFF0C\u5219\u65E0\u987B\u5173\u6CE8\u8FD9\u4E9B\u5185\u5BB9\u3002 - - - - - - - - pageForm.link=\u94FE\u63A5 pageForm.resultingUrlWillBe=\u7ED3\u679CURL\u5C06\u4E3A: pageForm.noUrl=\u6CA1\u6709\u94FE\u63A5\uFF01\u5C06\u65E0\u6CD5\u901A\u8FC7\u7F51\u7EDC\u8BBF\u95EE\u8FD9\u4E2A\u65B0\u9875\u9762 @@ -997,73 +946,6 @@ Template.error.linkSize=\u94FE\u63A5\u4E0D\u80FD\u8D85\u8FC7255\u4E2A\u5B57\u7B2 Template.error.descriptionSize=\u63CF\u8FF0\u4E0D\u80FD\u8D85\u8FC7255\u4E2A\u5B57\u7B26 Template.error.actionNull=\u529F\u80FD\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A -# ---------------------------------------------------- Ping Target Admin/Editing - - - -commonPingTargets.subtitle=\u4E3A\u6240\u6709\u535A\u5BA2\u7BA1\u7406\u901A\u7528\u5185\u5BB9\u63A8\u9001\u76EE\u6807\u3002 -commonPingTargets.commonPingTargets=\u901A\u7528\u5185\u5BB9\u63A8\u9001\u76EE\u6807 -commonPingTargets.explanation=\u8FD9\u4E9B\u76EE\u6807\u7AD9\u70B9\u5BF9\u6240\u6709\u535A\u5BA2\u7684\u5185\u5BB9\u66F4\u65B0\u9002\u7528\u3002 - -commonPingTargets.error.loading=\u8F7D\u5165\u901A\u7528\u63A8\u9001\u76EE\u6807\u65F6\u51FA\u9519 -commonPingTargets.error.saving=\u4FDD\u5B58\u901A\u7528\u63A8\u9001\u76EE\u6807\u65F6\u51FA\u9519 -commonPingTargets.error.enabling=\u542F\u7528\u63A8\u9001\u76EE\u6807\u65F6\u51FA\u9519 -commonPingTargets.error.disabling=\u7981\u7528\u63A8\u9001\u76EE\u6807\u65F6\u51FA\u9519 - -pings.title=\u914D\u7F6E\u81EA\u52A8\u535A\u5BA2\u63A8\u9001 -pings.subtitle=\u4E3A\u535A\u5BA2\u914D\u7F6E\u81EA\u52A8\u5185\u5BB9\u63A8\u9001 {0} -pings.explanation=\u63A8\u9001(Pings)\u5141\u8BB8\u4F60\u7684\u535A\u5BA2\u5728\u66F4\u65B0\u65F6\u901A\u77E5\u76EE\u6807\u7AD9\u70B9\u4EE5\u4FBF\u5BF9\u65B9\u8BFB\u53D6\u66F4\u65B0\u7684\u5185\u5BB9\u3002\u4F60\u53EF\u4EE5\u4E3A\u60F3\u8981\u901A\u77E5\u7684\u7AD9\u70B9\u542F\u7528\u535A\u5BA2\u66F4\u65B0\u7684\u81EA\u52A8\u63A8\u9001\u3002\u4F60\u4E5F\u53EF\u4EE5\u624B\u52A8\u5411\u6307\u5B9A\u7684\u7AD9\u70B9\u53D1\u51FA\u63A8\u9001\u901A\u77E5\u3002\u6CE8\u610F\uFF1A\u4F60\u901A\u5E38\u9700\u8981\u5728\u76EE\u6807\u7AD9\u70B9\u767B\u8BB0\u4F60\u7684\u535A\u5BA2\u624D\u80FD\u5411\u5176\u63A8\u9001\u5185\u5BB9\u3002 - - - - - - -pings.commonPingTargets=\u901A\u7528\u63A8\u9001\u76EE\u6807 -pings.commonPingTargetsExplanation=\u8FD9\u4E9B\u63A8\u9001\u76EE\u6807\u662F\u7531\u7CFB\u7EDF\u7BA1\u7406\u5458\u9884\u5148\u914D\u7F6E\u4F9B\u6240\u6709\u7528\u6237\u4F7F\u7528\u7684\u3002\u4F60\u53EF\u4EE5\u4EFB\u610F\u5411\u8FD9\u4E9B\u77E5\u540D\u7AD9\u70B9\u63A8\u9001\u5185\u5BB9\u3002 - - - -ping.successful=\u63A8\u9001\u6210\u529F\uFF01 -ping.transmittedButError=\u63A8\u9001\u901A\u77E5\u5DF2\u53D1\u51FA\uFF0C\u4F46\u5BF9\u65B9\u670D\u52A1\u5668\u8FD4\u56DE\u4E86\u4EE5\u4E0B\u9519\u8BEF\u4FE1\u606F\u3002 - -ping.transmissionFailed=\u63A8\u9001\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u63A8\u9001\u76EE\u6807URL\u662F\u5426\u6B63\u786E\u3002 - -ping.unknownHost=\u63A8\u9001\u76EE\u6807URL\u7684\u4E3B\u673A\u540D\u672A\u77E5\u3002 -ping.networkConnectionFailed=\u901A\u5411\u63A8\u9001\u76EE\u6807\u7AD9\u70B9\u7684\u7F51\u7EDC\u8FDE\u63A5\u51FA\u73B0\u95EE\u9898\u3002 - -ping.pingProcessingIsSuspended=\u63A8\u9001\u5904\u7406\u88AB\u7CFB\u7EDF\u7BA1\u7406\u5458\u4E34\u65F6\u4E2D\u6B62\u3002 -pingResult.OK=\u6210\u529F - -pingTarget.addTarget=\u6DFB\u52A0\u63A8\u9001\u76EE\u6807 -pingTarget.editTarget=\u7F16\u8F91\u63A8\u9001\u76EE\u6807 - -pingTargetEdit.subtitle=\u7F16\u8F91\u63A8\u9001\u76EE\u6807 -pingTargetAdd.subtitle=\u6DFB\u52A0\u63A8\u9001\u76EE\u6807 - -pingTarget.pingUrl=\u63A8\u9001URL -pingTarget.autoEnabled=\u81EA\u52A8\u63A8\u9001\uFF1F -pingTarget.remove=\u79FB\u9664 -pingTarget.confirmRemoveTitle=\u786E\u8BA4\u79FB\u9664\u63A8\u9001\u76EE\u6807 -pingTarget.confirmCommonRemove=\u79FB\u9664\u6B64\u76EE\u6807\u5C06\u540C\u65F6\u64A4\u9500\u6240\u6709\u7528\u6237\u5BF9\u6B64\u76EE\u6807\u7684\u81EA\u52A8\u63A8\u9001\u914D\u7F6E\u3002\u4F60\u786E\u5B9A\u5417\uFF1F - -pingTarget.enable=\u542F\u7528 -pingTarget.enabled=\u5F00\u542F -pingTarget.disable=\u7981\u7528 -pingTarget.disabled=\u5173\u95ED -pingTarget.auto=\u81EA\u52A8 -pingTarget.manual=\u624B\u52A8 -pingTarget.sendPingNow=\u7ACB\u5373\u63A8\u9001 -pingTarget.updated=\u63A8\u9001\u76EE\u6807 "{0}" \u5DF2\u66F4\u65B0 -pingTarget.created=\u63A8\u9001\u76EE\u6807 "{0}" \u5DF2\u521B\u5EFA -pingTarget.deleted=\u63A8\u9001\u76EE\u6807 "{0}" \u5DF2\u5220\u9664 - -pingTarget.nameNotUnique=\u8FD9\u4E2A\u63A8\u9001\u76EE\u6807\u7684\u540D\u5B57\u4E0E\u5176\u4ED6\u76EE\u6807\u7684\u540D\u5B57\u5B58\u5728\u51B2\u7A81\u3002 -pingTarget.nameMissing=\u8981\u6C42\u8F93\u5165\u540D\u5B57 -pingTarget.pingUrlMissing=\u8981\u6C42\u8F93\u5165\u63A8\u9001URL -pingTarget.malformedUrl=\u63A8\u9001URL\u7684\u683C\u5F0F\u4E0D\u6B63\u786E\u3002 -pingTarget.unknownHost=\u63A8\u9001URL\u6307\u5B9A\u7684\u4E3B\u673A\u4E0D\u5B58\u5728\u3002 -pingTarget.notFound=\u672A\u627E\u5230\u63A8\u9001\u76EE\u6807ID {0} # --------------------------------------------------------------- Planet Roller: config @@ -1238,7 +1120,6 @@ tabbedmenu.website.themes=\u4E3B\u9898 tabbedmenu.website.pages=\u6A21\u677F tabbedmenu.website.members=\u6210\u5458 tabbedmenu.website.maintenance=\u7EF4\u62A4 -tabbedmenu.weblog.pingSetup=\u63A8\u9001(Ping) tabbedmenu.weblog.mediaFiles=\u5A92\u4F53\u6587\u4EF6 @@ -1246,7 +1127,6 @@ tabbedmenu.admin=\u7CFB\u7EDF\u7BA1\u7406 tabbedmenu.admin.config=\u7BA1\u7406\u914D\u7F6E tabbedmenu.admin.userAdmin=\u7528\u6237\u7BA1\u7406 tabbedmenu.admin.commentManagement=\u8BC4\u8BBA\u7BA1\u7406 -tabbedmenu.admin.pingTargets=\u63A8\u9001\u76EE\u6807 tabbedmenu.admin.cacheInfo=\u7F13\u5B58\u4FE1\u606F tabbedmenu.planet=\u793E\u533A\u7BA1\u7406 @@ -1270,12 +1150,10 @@ themeEditor.sharedTheme=\u5171\u4EAB\u4E3B\u9898 themeEditor.sharedThemeDescription=\u4F7F\u7528\u9884\u5B9A\u4E49\u7684\u5171\u4EAB\u4E3B\u9898\uFF0C\u6B64\u9009\u9879\u9002\u5408\u4E0D\u60F3\u989D\u5916\u8D39\u5FC3\u8BBE\u8BA1\u535A\u5BA2\u5916\u89C2\u7684\u7528\u6237\u3002 - themeEditor.customTheme=\u81EA\u5B9A\u4E49\u4E3B\u9898 themeEditor.customThemeDescription=\u6B64\u9009\u9879\u9488\u5BF9\u60F3\u8981\u81EA\u5DF2\u8BBE\u8BA1\u535A\u5BA2\u5916\u89C2\u7684\u7528\u6237\u3002\u6CE8\u610F\uFF1A\u81EA\u5B9A\u4E49\u4E3B\u9898\u9700\u8981\u989D\u5916\u82B1\u8D39\u4E00\u4E9B\u5FC3\u529B\u3002 - themeEditor.yourCurrentTheme=\u4F60\u5F53\u524D\u4F7F\u7528\u7684\u4E3B\u9898\u662F themeEditor.yourCustomStylesheet=\u4F7F\u7528\u81EA\u5B9A\u4E49\u6837\u5F0F\u8868\u3002 themeEditor.yourThemeStyleSheet=\u4F7F\u7528\u9ED8\u8BA4\u7684\u5171\u4EAB\u4E3B\u9898\u6837\u5F0F\u8868\u3002 @@ -1350,7 +1228,6 @@ userAdmin.subtitle.searchUser=\u9009\u62E9\u8981\u7BA1\u7406\u7684\u7528\u6237 userAdmin.prompt.searchUser=\u901A\u8FC7\u952E\u5165\u7528\u6237\u540D\u7B2C\u4E00\u4E2A\u5B57\u6BCD\u67E5\u627E\u7528\u6237\u3002\u9009\u5B9A\u7528\u6237\u540E\u70B9\u51FB\u7F16\u8F91\u6309\u94AE\u4EE5\u7F16\u8F91\u7528\u6237\u4FE1\u606F\u548C\u7528\u6237\u7684\u535A\u5BA2\u3002 - userAdmin.title.editUser=\u7F16\u8F91\u7528\u6237\u4FE1\u606F userAdmin.subtitle.editUser=\u7F16\u8F91\u7528\u6237\u4FE1\u606F\u548C\u6743\u9650 {0} @@ -1398,11 +1275,9 @@ userAdmin.error.userNotFound=\u627E\u4E0D\u5230\u6307\u5B9A\u7528\u6237 userRegister.prompt=\u6B22\u8FCE\uFF01Roller\u9080\u8BF7\u4F60\u521B\u5EFA\u4E00\u4E2A\u7528\u6237\u8D26\u53F7\u3002\u8BF7\u4F7F\u7528\u6B64\u8868\u5355\u6CE8\u518C\u7528\u6237\u8D26\u53F7\u3002\u4EE5\u4E0B\u5185\u5BB9\u5747\u4E3A\u5FC5\u586B\u9879\u3002 - Register.disabled=\u7CFB\u7EDF\u7BA1\u7406\u5458\u76EE\u524D\u505C\u7528\u4E86\u7528\u6237\u6CE8\u518C\u3002 - userRegister.heading.identification=\u4F60\u8981\u4F7F\u7528\u7684\u7528\u6237\u540D\uFF1F userRegister.tip.identification=\u8BF7\u8F93\u5165\u4F60\u8981\u4F7F\u7528\u7684\u7528\u6237\u540D\u548C\u7535\u5B50\u90AE\u7BB1\u4EE5\u6FC0\u6D3B\u4F60\u7684\u8D26\u53F7\u3002 @@ -1410,8 +1285,6 @@ userRegister.tip.identification=\u8BF7\u8F93\u5165\u4F60\u8981\u4F7F\u7528\u7684 userRegister.tip.userName=\u4F5C\u4E3A\u7528\u6237\u8D26\u53F7\u7684\u7B80\u77ED\u540D\u5B57\u3002\u8BF7\u4F7F\u7528\u6807\u51C6ASCII\u5B57\u7B26(a-z\uFF0CA-Z\uFF0C0-9)\u5E76\u4E14\u4E0D\u8981\u4F7F\u7528HTML\u3002\u7528\u6237\u540D\u4E0D\u80FD\u4FEE\u6539\uFF0C\u5E76\u4E14\u53EF\u4EE5\u9009\u62E9\u4E0D\u516C\u5F00\u663E\u793A\u3002 - - userRegister.tip.screenName=\u4F60\u8981\u4F7F\u7528\u7684\u6635\u79F0(\u4E0D\u53EF\u4F7F\u7528HTML)\u3002\u6635\u79F0\u5C06\u5728\u4F60\u7684\u535A\u5BA2\u4E3B\u9875\u548C\u6587\u7AE0\u9875\u9762\u4E2D\u663E\u793A\u3002 @@ -1429,11 +1302,9 @@ userRegister.tip.openid.disabled=\u8BF7\u8F93\u5165\u4F60\u8981\u4F7F\u7528\u768 userRegister.tip.openid.hybrid=\u4F60\u53EF\u4EE5\u9009\u62E9\u4F7F\u7528\u672C\u7AD9\u8D26\u53F7\u6216OpenID\u767B\u5F55\u3002\u6709\u5173OpenID\u7684\u8BE6\u60C5\u53EF\u53C2\u770Bhttp://openid.net\u3002 - userRegister.tip.openid.only=\u672C\u7AD9\u53EA\u5141\u8BB8\u4F7F\u7528OpenID\u767B\u5F55\uFF0C\u8BF7\u6307\u5B9A\u4F60\u7684OpenID\u6807\u8BC6\u3002\u6709\u5173OpenID\u7684\u8BE6\u60C5\u53EF\u53C2\u770Bhttp://openid.net\u3002 - userRegister.tip.password=\u4F60\u7684\u5BC6\u7801\u3002 userSettings.tip.password=\u4F60\u7684\u5BC6\u7801\u3002 \u4EC5\u5728\u60F3\u8981\u4FEE\u6539\u5BC6\u7801\u65F6\u624D\u9700\u586B\u5199\u3002 userRegister.tip.passwordConfirm=\u786E\u8BA4\u4F60\u7684\u5BC6\u7801\u3002 @@ -1444,7 +1315,6 @@ userRegister.heading.locale=\u4F60\u8981\u9009\u62E9\u7684\u533A\u57DF\u8BED\u8A userRegister.tip.localeAndTimeZone=\u6211\u4EEC\u5C1D\u8BD5\u4E3A\u4F60\u9884\u8BBE\u4E86\u533A\u57DF\u8BED\u8A00\u548C\u65F6\u533A\u8BBE\u7F6E\u3002\u4F60\u4E5F\u53EF\u4EE5\u53E6\u884C\u4FEE\u6539\uFF0C\u8FD9\u4E9B\u8BBE\u7F6E\u5C06\u5E94\u7528\u4E8E\u4F60\u7684\u535A\u5BA2\u6587\u7AE0\u3002 - userRegister.tip.timeZone=\u4F60\u6240\u5728\u7684\u65F6\u533A\u3002 userRegister.tip.locale=\u4F60\u9009\u7528\u7684\u533A\u57DF\u8BED\u8A00\u3002 @@ -1487,10 +1357,6 @@ weblogEntry.pendingEntrySubject=Roller\uFF1A\u5BA1\u6838\u5F85\u53D1\u5E03\u7684 weblogEntry.pendingEntryContent=\u7528\u6237[{0}]\u6240\u63D0\u4EA4\u7684\u6587\u7AE0\u6B63\u7B49\u5F85\u4F60\u7684\u5BA1\u6838\u3002\u8BF7\u9009\u62E9\u53D1\u5E03\u6216\u662F\u4FDD\u5B58\u4E3A\u8349\u7A3F\u4F9B\u7528\u6237[{1}]\u7EE7\u7EED\u4FEE\u6539\u3002\u94FE\u63A5\u5230\u5F85\u53D1\u5E03\u6587\u7AE0\u7F16\u8F91\u9875\uFF1A<{2}> - - - - weblogEntry.notFound=\u627E\u4E0D\u5230\u6307\u5B9A\u7684\u535A\u5BA2\u6587\u7AE0 weblogEdit.summary=\u6458\u8981\uFF08\u53EF\u9009\uFF09 @@ -1607,7 +1473,6 @@ weblogEntryQuery.subtitle=\u5728\u535A\u5BA2{0}\u4E2D\u67E5\u627E\u weblogEntryQuery.tip=\u4F60\u53EF\u4EE5\u5728\u4E0B\u9762\u7684\u5217\u8868\u4E2D\u9009\u62E9\u6587\u7AE0\u8FDB\u884C\u7F16\u8F91\uFF0C\u6216\u8005\u4F7F\u7528\u7B5B\u9009\u63A7\u4EF6\u6765\u67E5\u627E\u7279\u5B9A\u5206\u7C7B\u3001\u65E5\u671F\u6216\u72B6\u6001\u7684\u6587\u7AE0\u3002 - weblogEntryQuery.sidebarTitle=\u6587\u7AE0\u7B5B\u9009 weblogEntryQuery.sidebarDescription=\u6309\u5206\u7C7B\u3001\u65E5\u671F\u548C\u72B6\u6001\u6765\u7B5B\u9009\u535A\u5BA2\u6587\u7AE0\u3002 @@ -1722,8 +1587,6 @@ websiteSettings.removeWebsite=\u4F60\u662F\u6B64\u535A\u5BA2\u7684\u6700\u540E\u websiteSettings.removeWebsiteWarning=\u8B66\u544A\uFF1A\u79FB\u9664\u535A\u5BA2\u5C06\u4F1A\u5220\u6389\u5176\u4E2D\u7684\u6240\u6709\u6587\u7AE0\u3001
\u8BC4\u8BBA\u3001\u6536\u85CF\u548C\u8BBE\u7F6E\u4FE1\u606F\u3002\u79FB\u9664\u535A\u5BA2\u64CD\u4F5C\u662F\u4E0D\u53EF\u6062\u590D\u7684\u3002. - - websiteRemove.title=\u786E\u8BA4\u535A\u5BA2\u79FB\u9664 websiteRemove.subtitle=\u79FB\u9664\u535A\u5BA2\u64CD\u4F5C\u662F\u4E0D\u53EF\u6062\u590D\u7684 websiteRemove.youSure=\u4F60\u786E\u5B9A\u8981\u5220\u9664\u535A\u5BA2[{0}]\u5417\uFF1F @@ -1793,7 +1656,6 @@ yourWebsites.planetAdmin=\u793E\u533A\u7BA1\u7406 yourWebsites.planetAdmin.desc=\u7BA1\u7406\u793E\u533A\u5185\u5BB9\u805A\u5408\u3002\u6DFB\u52A0\u65B0\u95FB\u6E90\uFF0C\u7BA1\u7406\u7FA4\u7EC4\u548C\u8BBE\u7F6E\u53C2\u6570\u3002 - yourWebsites.invitations=\u535A\u5BA2\u9080\u8BF7 yourWebsites.invitationsPrompt=\u4F60\u6709\u535A\u5BA2\u9080\u8BF7\u7B49\u5F85\u63A5\u53D7\u6216\u62D2\u7EDD: @@ -1814,6 +1676,5 @@ error.activate.user.missingActivationCode=\u6FC0\u6D3B\u7801\u4E22\u5931\u3002 error.activate.user.invalidActivationCode=\u6FC0\u6D3B\u7801\u65E0\u6548\u3002\u4F60\u5E94\u8BE5\u5DF2\u7ECF\u6FC0\u6D3B\u8FC7\u4F60\u7684\u8D26\u53F7\u4E86\u3002\u5982\u679C\u5C1A\u672A\u6FC0\u6D3B\u8D26\u53F7\uFF0C\u8BF7\u5411Roller\u7CFB\u7EDF\u7BA1\u7406\u5458\u5BFB\u6C42\u5E2E\u52A9\u3002 - user.account.activation.mail.subject=Roller\uFF1A\u4F60\u7684\u8D26\u53F7\u6FC0\u6D3B\u7801 user.account.activation.mail.content=

\u8981\u6FC0\u6D3B\u4F60\u7684Roller\u8D26\u6237[{1}]\uFF0C\u8BF7\u70B9\u51FB\u4EE5\u4E0B\u94FE\u63A5\uFF1A

{2}

diff --git a/app/src/main/resources/META-INF/persistence.xml b/app/src/main/resources/META-INF/persistence.xml index 2af30b8c5..b4f17e728 100644 --- a/app/src/main/resources/META-INF/persistence.xml +++ b/app/src/main/resources/META-INF/persistence.xml @@ -33,8 +33,6 @@ org/apache/roller/weblogger/pojos/WeblogEntryAttribute.orm.xml org/apache/roller/weblogger/pojos/WeblogBookmarkFolder.orm.xml org/apache/roller/weblogger/pojos/WeblogHitCount.orm.xml - org/apache/roller/weblogger/pojos/PingQueueEntry.orm.xml - org/apache/roller/weblogger/pojos/PingTarget.orm.xml org/apache/roller/weblogger/pojos/UserRole.orm.xml org/apache/roller/weblogger/pojos/RuntimeConfigProperty.orm.xml org/apache/roller/weblogger/pojos/TaskLock.orm.xml @@ -50,7 +48,6 @@ org/apache/roller/weblogger/pojos/MediaFile.orm.xml org/apache/roller/weblogger/pojos/MediaFileTag.orm.xml org/apache/roller/weblogger/pojos/MediaFileDirectory.orm.xml - org/apache/roller/weblogger/pojos/AutoPing.orm.xml org/apache/roller/weblogger/pojos/CustomTemplateRendition.orm.xml org/apache/roller/planet/pojos/Planet.orm.xml diff --git a/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties b/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties index 5a758a879..0e157f05f 100644 --- a/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties +++ b/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties @@ -52,7 +52,6 @@ # -- Cache configuration # -- User management and security settings # -- Rendering system -# -- Weblog ping system # -- Pluggable backend, page and editor plugins # -- Log4j2 logging settings # -- Other settings @@ -227,7 +226,7 @@ cache.dir=${user.home}/roller_data/planet-cache # The *enabled* tasks are defined by tasks.enabled=[,] # Tasks which are enabled. Only tasks listed here will be run. -tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,PingQueueTask +tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask # client identifier. should be unique for each instance in a cluster. tasks.clientId=defaultClientId @@ -244,11 +243,6 @@ tasks.ResetHitCountsTask.startTime=startOfDay tasks.ResetHitCountsTask.interval=1440 tasks.ResetHitCountsTask.leaseTime=30 -# Ping processor, does sending of pings -tasks.PingQueueTask.class=org.apache.roller.weblogger.business.pings.PingQueueTask -tasks.PingQueueTask.startTime=immediate -tasks.PingQueueTask.interval=5 -tasks.PingQueueTask.leaseTime=30 # Sync Roller weblogs with planet tasks.SyncWebsitesTask.class=org.apache.roller.weblogger.planet.tasks.SyncWebsitesTask @@ -509,59 +503,6 @@ org.apache.roller.weblogger.ui.rendering.model.PlanetModel # Velocity settings velocity.properties=/WEB-INF/velocity.properties -#----------------------------------------------------------------------------- -# Weblog ping system -#----------------------------------------------------------------------------- - -# The number of attempts to try to reach a ping target before refusing to -# requeue it for further retrials. If absent, this defaults to 3. -pings.maxPingAttempts=3 - -# The interval between ping queue processing runs in minutes. Must be between -# 0 and 120. If set to 0, ping queue processing is disabled on this server; -# this is for clustered environments. Make sure it is nonzero on one host in -# a cluster. Don't use the value 0 here to disable ping functionality, you -# will instead get an infinitely growing ping queue. See the documentation on -# the properties below to disable ping functionality if you need to. -# If absent, this defaults to 5. -pings.queueProcessingIntervalMins=5 - -# The set of initial common ping targets. This is used to initialize the -# database if there are no common ping targets at startup. Ping targets are -# specified as a comma-separated list, each target in the form {{name}{url}}. -# To disable initialization of common ping targets, comment this out, or set it -# to the empty value. Common targets can be edited in the UI; this is just -# used to set up some typical ones. -pings.initialCommonTargets=\ -{{Technorati}{http://rpc.technorati.com/rpc/ping}}\ -,{{Weblogs.com}{http://rpc.weblogs.com/RPC2}}\ -,{{blo.gs}{http://ping.blo.gs/}}\ -,{{IceRocket}{http://rpc.icerocket.com:10080/}} - -# Specify variant options for known buggy ping targets. -pings.variantOptions={{http://rpc.icerocket.com:10080/}{noname}} - -# This controls whether the Weblog:Pings menu item and its associated actions -# are enabled. Set this to false to disallow users from configuring autopings -# and doing manual pings. If absent, this defaults to true. -# NOTE: There is a separate runtime property (configurable from the -# Admin:Configuration page, that can be used to suspend ping processing without -# disabling the UI. -# CAUTION: Setting this to true will cause the server to remove all users' -# existing autoping configurations on startup. Leave this false or commented -# for normal behavior. -pings.disablePingUsage=false - -# Setting both pings.disallowCustomTarget=true and pings.disablePingUsage=true -# will effectively disable the ping functionality. - -# This is used for debugging the ping mechanism in Roller. If this is set -# to true, pings that would normally be sent will cause log messages to be sent -# but will NOT actually result in real pings being sent. Leave this false or -# commented for normal behavior. -pings.logOnly=false - - #----------------------------------------------------------------------------- # Pluggable backend, page and editor plugins #----------------------------------------------------------------------------- diff --git a/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml b/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml index 5ac86d35a..bbfed7cad 100644 --- a/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml +++ b/app/src/main/resources/org/apache/roller/weblogger/config/runtimeConfigDefs.xml @@ -78,10 +78,6 @@ string - - boolean - false - diff --git a/app/src/main/resources/org/apache/roller/weblogger/pojos/AutoPing.orm.xml b/app/src/main/resources/org/apache/roller/weblogger/pojos/AutoPing.orm.xml deleted file mode 100644 index 8cc088c6a..000000000 --- a/app/src/main/resources/org/apache/roller/weblogger/pojos/AutoPing.orm.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - org.apache.roller.weblogger.pojos - - - - SELECT a FROM AutoPing a - - - SELECT a FROM AutoPing a WHERE a.pingTarget = ?1 - - - SELECT a FROM AutoPing a WHERE a.website = ?1 - - - DELETE FROM AutoPing a WHERE a.pingTarget = ?1 - - - DELETE FROM AutoPing a WHERE a.pingTarget = ?1 AND a.website = ?2 - - - DELETE FROM AutoPing a - - - - - - - - - - - - - - diff --git a/app/src/main/resources/org/apache/roller/weblogger/pojos/PingQueueEntry.orm.xml b/app/src/main/resources/org/apache/roller/weblogger/pojos/PingQueueEntry.orm.xml deleted file mode 100644 index 764d53418..000000000 --- a/app/src/main/resources/org/apache/roller/weblogger/pojos/PingQueueEntry.orm.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - org.apache.roller.weblogger.pojos - -
- - SELECT p FROM PingQueueEntry p ORDER BY p.entryTime - - - SELECT p FROM PingQueueEntry p WHERE p.pingTarget = ?1 AND p.website = ?2 - - - - SELECT p FROM PingQueueEntry p WHERE p.website = ?1 - - - DELETE FROM PingQueueEntry p WHERE p.pingTarget = ?1 - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/resources/org/apache/roller/weblogger/pojos/PingTarget.orm.xml b/app/src/main/resources/org/apache/roller/weblogger/pojos/PingTarget.orm.xml deleted file mode 100644 index 3ec2e42a7..000000000 --- a/app/src/main/resources/org/apache/roller/weblogger/pojos/PingTarget.orm.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - org.apache.roller.weblogger.pojos - -
- - SELECT p FROM PingTarget p ORDER BY p.name - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/resources/org/apache/roller/weblogger/ui/struts2/admin/admin-menu.xml b/app/src/main/resources/org/apache/roller/weblogger/ui/struts2/admin/admin-menu.xml index cdf9cca23..7619f39a6 100644 --- a/app/src/main/resources/org/apache/roller/weblogger/ui/struts2/admin/admin-menu.xml +++ b/app/src/main/resources/org/apache/roller/weblogger/ui/struts2/admin/admin-menu.xml @@ -41,9 +41,6 @@ See comments in editor-menu.xml for info about attributes. name="tabbedmenu.admin.commentManagement" /> - - execute,delete,query,update - - .PingTargets - - - - AUTOMATIC - - .PingTargetConfirm - commonPingTargets - delete,deleteConfirm,disable,enable,execute - - - commonPingTargetAdd - pingTarget.addTarget - .PingTargetEdit - commonPingTargets - execute - - - commonPingTargetEdit - pingTarget.editTarget - .PingTargetEdit - commonPingTargets - commonPingTargets - execute - @@ -647,20 +618,6 @@ execute,resign - - - - - AUTOMATIC - - .Pings - - pings - ${actionWeblog.handle} - - disable,enable,execute,pingNow - diff --git a/app/src/main/webapp/WEB-INF/jsps/admin/PingTargets.jsp b/app/src/main/webapp/WEB-INF/jsps/admin/PingTargets.jsp deleted file mode 100644 index 5f541d806..000000000 --- a/app/src/main/webapp/WEB-INF/jsps/admin/PingTargets.jsp +++ /dev/null @@ -1,301 +0,0 @@ -<%-- - 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. ---%> -<%@ include file="/WEB-INF/jsps/taglibs-struts2.jsp" %> - -

- -

- - -
- - <%-- Headings --%> - - - - - - - - - <%-- Listing of current common targets --%> - - - - - - - - - - - - - - - - - - - -
- -   - - -   - - - - - - - - - - -
" method="post" style="display: inline;"> - " /> - " /> - -
-
- ', - '', - '' - )"> - - - - ')"> - - -
- -
- - - - - - - -
- - -<%-- ================================================================================================ --%> - - - - -<%-- ================================================================================================ --%> - -<%-- add/edit link form: a modal --%> - - <%-- modal --%> - - -<%-- page reload mechanism --%> - - - - - - -<%-- ================================================================================================ --%> - - - diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/Pings.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/Pings.jsp deleted file mode 100644 index 3591df115..000000000 --- a/app/src/main/webapp/WEB-INF/jsps/editor/Pings.jsp +++ /dev/null @@ -1,100 +0,0 @@ -<%-- - 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. ---%> -<%@ include file="/WEB-INF/jsps/taglibs-struts2.jsp" %> - -

- - - -

-

- -

- -

- - - -<%-- Headings --%> - - - - - - - -<%-- Table of current common targets with actions --%> - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -   - - -   - - - - - - - - - - -
" method="post" style="display: inline;"> - " /> - " /> - " /> - -
-
- -
" method="post" style="display: inline;"> - " /> - " /> - " /> - -
-
- -
diff --git a/app/src/main/webapp/WEB-INF/tiles.xml b/app/src/main/webapp/WEB-INF/tiles.xml index 113d48ece..8008a5b2d 100644 --- a/app/src/main/webapp/WEB-INF/tiles.xml +++ b/app/src/main/webapp/WEB-INF/tiles.xml @@ -193,13 +193,7 @@ - - - - - - @@ -378,9 +372,6 @@ - - - diff --git a/app/src/test/java/org/apache/roller/weblogger/TestUtils.java b/app/src/test/java/org/apache/roller/weblogger/TestUtils.java index 6dc96796a..1f24d0f4e 100644 --- a/app/src/test/java/org/apache/roller/weblogger/TestUtils.java +++ b/app/src/test/java/org/apache/roller/weblogger/TestUtils.java @@ -39,14 +39,10 @@ import org.apache.roller.weblogger.business.WeblogEntryManager; import org.apache.roller.weblogger.business.WeblogManager; import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; import org.apache.roller.weblogger.business.startup.ClasspathDatabaseScriptProvider; import org.apache.roller.weblogger.business.startup.SQLScriptRunner; import org.apache.roller.weblogger.business.startup.WebloggerStartup; import org.apache.roller.weblogger.config.WebloggerConfig; -import org.apache.roller.weblogger.pojos.AutoPing; -import org.apache.roller.weblogger.pojos.PingTarget; import org.apache.roller.weblogger.pojos.User; import org.apache.roller.weblogger.pojos.Weblog; import org.apache.roller.weblogger.pojos.WeblogBookmarkFolder; @@ -485,94 +481,6 @@ public static void teardownComment(String id) throws Exception { WebloggerFactory.getWeblogger().flush(); } - /** - * Convenience method for creating a ping target. - */ - public static PingTarget setupPingTarget(String name, String url) - throws Exception { - - PingTarget testPing = new PingTarget(); - testPing.setName("testCommonPing"); - testPing.setPingUrl("http://localhost/testCommonPing"); - - // store ping - PingTargetManager pingMgr = WebloggerFactory.getWeblogger() - .getPingTargetManager(); - pingMgr.savePingTarget(testPing); - - // flush to db - WebloggerFactory.getWeblogger().flush(); - - // query for it - PingTarget ping = pingMgr.getPingTarget(testPing.getId()); - - if (ping == null) { - throw new WebloggerException("error setting up ping target"); - } - - return ping; - } - - /** - * Convenience method for removing a ping target. - */ - public static void teardownPingTarget(String id) throws Exception { - - // query for it - PingTargetManager pingMgr = WebloggerFactory.getWeblogger() - .getPingTargetManager(); - PingTarget ping = pingMgr.getPingTarget(id); - - // remove the ping - pingMgr.removePingTarget(ping); - - // flush to db - WebloggerFactory.getWeblogger().flush(); - } - - /** - * Convenience method for creating an auto ping. - */ - public static AutoPing setupAutoPing(PingTarget ping, Weblog weblog) - throws Exception { - - AutoPingManager mgr = WebloggerFactory.getWeblogger() - .getAutopingManager(); - - // store auto ping - AutoPing autoPing = new AutoPing(null, ping, getManagedWebsite(weblog)); - mgr.saveAutoPing(autoPing); - - // flush to db - WebloggerFactory.getWeblogger().flush(); - - // query for it - autoPing = mgr.getAutoPing(autoPing.getId()); - - if (autoPing == null) { - throw new WebloggerException("error setting up auto ping"); - } - - return autoPing; - } - - /** - * Convenience method for removing an auto ping. - */ - public static void teardownAutoPing(String id) throws Exception { - - // query for it - AutoPingManager mgr = WebloggerFactory.getWeblogger() - .getAutopingManager(); - AutoPing autoPing = mgr.getAutoPing(id); - - // remove the auto ping - mgr.removeAutoPing(autoPing); - - // flush to db - WebloggerFactory.getWeblogger().flush(); - } - /** * Convenience method for creating a hit count. */ diff --git a/app/src/test/java/org/apache/roller/weblogger/business/PingsTest.java b/app/src/test/java/org/apache/roller/weblogger/business/PingsTest.java deleted file mode 100644 index 72c433579..000000000 --- a/app/src/test/java/org/apache/roller/weblogger/business/PingsTest.java +++ /dev/null @@ -1,355 +0,0 @@ -/* -* 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. -*/ -/* - * PingsTest.java - * - * Created on April 9, 2006, 1:27 PM - */ - -package org.apache.roller.weblogger.business; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.roller.weblogger.TestUtils; -import org.apache.roller.weblogger.business.pings.AutoPingManager; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.pojos.AutoPing; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.apache.roller.weblogger.pojos.User; -import org.apache.roller.weblogger.pojos.Weblog; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Test Pings related business operations. - */ -public class PingsTest { - - public static Log log = LogFactory.getLog(PingsTest.class); - - User testUser = null; - Weblog testWeblog = null; - PingTarget testCommonPing = null; - - - /** - * All tests in this suite require a user and a weblog. - */ - @BeforeEach - public void setUp() throws Exception { - - // setup weblogger - TestUtils.setupWeblogger(); - - try { - testUser = TestUtils.setupUser("wtTestUser"); - testWeblog = TestUtils.setupWeblog("wtTestWeblog", testUser); - TestUtils.endSession(true); - } catch (Exception ex) { - log.error(ex); - throw new Exception("Test setup failed", ex); - } - - testCommonPing = new PingTarget(); - testCommonPing.setName("testCommonPing"); - testCommonPing.setPingUrl("http://localhost/testCommonPing"); - } - - @AfterEach - public void tearDown() throws Exception { - - try { - TestUtils.teardownWeblog(testWeblog.getId()); - TestUtils.teardownUser(testUser.getUserName()); - TestUtils.endSession(true); - } catch (Exception ex) { - log.error(ex); - throw new Exception("Test teardown failed", ex); - } - - testCommonPing = null; - } - - - /** - * Test basic persistence operations ... Create, Update, Delete - */ - @Test - public void testPingTargetCRUD() throws Exception { - - PingTargetManager mgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - PingTarget ping = null; - - // create common ping - mgr.savePingTarget(testCommonPing); - String commonId = testCommonPing.getId(); - TestUtils.endSession(true); - - // make sure common ping was stored - ping = null; - ping = mgr.getPingTarget(commonId); - assertNotNull(ping); - assertEquals(testCommonPing.getPingUrl(), ping.getPingUrl()); - - // update common ping - ping = null; - ping = mgr.getPingTarget(commonId); - ping.setName("testtestCommon"); - mgr.savePingTarget(ping); - TestUtils.endSession(true); - - // make sure common ping was updated - ping = null; - ping = mgr.getPingTarget(commonId); - assertNotNull(ping); - assertEquals("testtestCommon", ping.getName()); - - // delete common ping - ping = null; - ping = mgr.getPingTarget(commonId); - mgr.removePingTarget(ping); - TestUtils.endSession(true); - - // make sure common ping was deleted - ping = null; - ping = mgr.getPingTarget(commonId); - assertNull(ping); - } - - - /** - * Test lookup mechanisms ... id, all common for weblog - */ - @Test - public void testPingTargetLookups() throws Exception { - - PingTargetManager mgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - PingTarget ping = null; - - // create common ping - mgr.savePingTarget(testCommonPing); - String commonId = testCommonPing.getId(); - TestUtils.endSession(true); - - // lookup by id - ping = null; - ping = mgr.getPingTarget(commonId); - assertNotNull(ping); - assertEquals(testCommonPing.getName(), ping.getName()); - - // lookup all common pings - List commonPings = mgr.getCommonPingTargets(); - assertNotNull(commonPings); - // correct answer is: 4 pings in config + 1 new one = 5 - assertEquals(5, commonPings.size()); - - // delete common ping - ping = null; - ping = mgr.getPingTarget(commonId); - mgr.removePingTarget(ping); - TestUtils.endSession(true); - } - - - /** - * Test basic persistence operations ... Create, Update, Delete - */ - @Test - public void testAutoPingCRUD() throws Exception { - - AutoPingManager mgr = WebloggerFactory.getWeblogger().getAutopingManager(); - PingTargetManager pingMgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - AutoPing autoPing = null; - - // create ping target to use for tests - PingTarget pingTarget = TestUtils.setupPingTarget("fooPing", "http://foo/null"); - PingTarget pingTarget2 = TestUtils.setupPingTarget("blahPing", "http://blah/null"); - TestUtils.endSession(true); - - // create autoPing - autoPing = new AutoPing(null, pingTarget, testWeblog); - mgr.saveAutoPing(autoPing); - String id = autoPing.getId(); - TestUtils.endSession(true); - - // make sure autoPing was stored - autoPing = null; - autoPing = mgr.getAutoPing(id); - assertNotNull(autoPing); - assertEquals(pingTarget, autoPing.getPingTarget()); - - // update autoPing - autoPing.setPingTarget(pingMgr.getPingTarget(pingTarget2.getId())); - mgr.saveAutoPing(autoPing); - TestUtils.endSession(true); - - // make sure autoPing was updated - autoPing = null; - autoPing = mgr.getAutoPing(id); - assertNotNull(autoPing); - assertEquals(pingTarget2, autoPing.getPingTarget()); - - // delete autoPing - mgr.removeAutoPing(autoPing); - TestUtils.endSession(true); - - // make sure common autoPing was deleted - autoPing = null; - autoPing = mgr.getAutoPing(id); - assertNull(autoPing); - - // teardown test ping target - TestUtils.teardownPingTarget(pingTarget.getId()); - TestUtils.teardownPingTarget(pingTarget2.getId()); - TestUtils.endSession(true); - } - - - /** - * Test special ping target removal methods ... by weblog/target, collection, all - */ - @Test - public void testPingTargetRemovals() throws Exception { - - AutoPingManager mgr = WebloggerFactory.getWeblogger().getAutopingManager(); - PingTargetManager ptmgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - AutoPing testAutoPing = null; - - // create ping target to use for tests - PingTarget pingTarget = TestUtils.setupPingTarget("fooPing", "http://foo/null"); - PingTarget pingTarget2 = TestUtils.setupPingTarget("blahPing", "http://blah/null"); - PingTarget pingTarget3 = TestUtils.setupPingTarget("gahPing", "http://gah/null"); - - try { - - // create auto pings for test - testWeblog = TestUtils.getManagedWebsite(testWeblog); - AutoPing autoPing = TestUtils.setupAutoPing(pingTarget, testWeblog); - AutoPing autoPing2 = TestUtils.setupAutoPing(pingTarget2, testWeblog); - AutoPing autoPing3 = TestUtils.setupAutoPing(pingTarget3, testWeblog); - TestUtils.endSession(true); - - // remove by weblog/target - testWeblog = TestUtils.getManagedWebsite(testWeblog); - pingTarget = ptmgr.getPingTarget(pingTarget.getId()); - mgr.removeAutoPing(pingTarget, testWeblog); - TestUtils.endSession(true); - - // make sure remove succeeded - testAutoPing = null; - testAutoPing = mgr.getAutoPing(autoPing.getId()); - assertNull(testAutoPing); - - // remove a collection - List autoPings = new ArrayList<>(); - autoPing2 = mgr.getAutoPing(autoPing2.getId()); //Get managed version of autoPing2 - autoPings.add(autoPing2); - autoPing3 = mgr.getAutoPing(autoPing3.getId()); //Get managed version of autoPing2 - autoPings.add(autoPing3); - mgr.removeAutoPings(autoPings); - TestUtils.endSession(true); - - // make sure delete was successful - testWeblog = TestUtils.getManagedWebsite(testWeblog); - autoPings = mgr.getAutoPingsByWebsite(testWeblog); - assertNotNull(autoPings); - assertEquals(0, autoPings.size()); - - // need to create more test pings - autoPing = TestUtils.setupAutoPing(pingTarget, testWeblog); - autoPing2 = TestUtils.setupAutoPing(pingTarget2, testWeblog); - autoPing3 = TestUtils.setupAutoPing(pingTarget3, testWeblog); - TestUtils.endSession(true); - - // remove all - mgr.removeAllAutoPings(); - TestUtils.endSession(true); - - // make sure remove succeeded - testWeblog = TestUtils.getManagedWebsite(testWeblog); - autoPings = mgr.getAutoPingsByWebsite(testWeblog); - assertNotNull(autoPings); - assertEquals(0, autoPings.size()); - - } finally { - // teardown test ping target - TestUtils.teardownPingTarget(pingTarget.getId()); - TestUtils.teardownPingTarget(pingTarget2.getId()); - TestUtils.endSession(true); - } - } - - - /** - * Test lookup mechanisms ... id, ping target, weblog - */ - @Test - public void testAutoPingLookups() throws Exception { - - AutoPingManager mgr = WebloggerFactory.getWeblogger().getAutopingManager(); - PingTargetManager ptmgr = WebloggerFactory.getWeblogger().getPingTargetManager(); - AutoPing autoPing = null; - - // create autoPing target to use for tests - PingTarget pingTarget = TestUtils.setupPingTarget("fooPing", "http://foo/null"); - TestUtils.endSession(true); - - // create autoPing - testWeblog = TestUtils.getManagedWebsite(testWeblog); - pingTarget = ptmgr.getPingTarget(pingTarget.getId()); - autoPing = new AutoPing(null, pingTarget, testWeblog); - mgr.saveAutoPing(autoPing); - String id = autoPing.getId(); - TestUtils.endSession(true); - - // lookup by id - autoPing = null; - autoPing = mgr.getAutoPing(id); - assertNotNull(autoPing); - assertEquals(pingTarget, autoPing.getPingTarget()); - - // lookup by ping target - pingTarget = ptmgr.getPingTarget(pingTarget.getId()); - List autoPings = mgr.getAutoPingsByTarget(pingTarget); - assertNotNull(autoPings); - assertEquals(1, autoPings.size()); - - // lookup by weblog - autoPings = null; - testWeblog = TestUtils.getManagedWebsite(testWeblog); - autoPings = mgr.getAutoPingsByWebsite(testWeblog); - assertNotNull(autoPing); - assertEquals(1, autoPings.size()); - - // delete autoPing - autoPing = mgr.getAutoPing(autoPing.getId()); - mgr.removeAutoPing(autoPing); - TestUtils.endSession(true); - - // teardown test ping target - TestUtils.teardownPingTarget(pingTarget.getId()); - TestUtils.endSession(true); - } - -} diff --git a/app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java b/app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java deleted file mode 100644 index 1f037e786..000000000 --- a/app/src/test/java/org/apache/roller/weblogger/business/pings/WeblogUpdatePingerTest.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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.business.pings; - -import com.sun.net.httpserver.HttpServer; -import org.apache.roller.weblogger.business.pings.WeblogUpdatePinger.PingResult; -import org.apache.xmlrpc.XmlRpcException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Base64; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Tests for {@link WeblogUpdatePinger}, covering how it builds the - * {@code weblogUpdates.ping} request and reads the response. - * - *

The response reader extracts only the {@code flerror} and {@code message} - * fields the protocol defines, reads every scalar as text, ignores response - * content it does not use, and refuses document type declarations. - */ -public class WeblogUpdatePingerTest { - - /** Set true if {@link Probe} is ever reconstructed from bytes. */ - private static volatile boolean probeReconstructed; - - @BeforeEach - public void reset() { - probeReconstructed = false; - } - - /** A serializable object whose reconstruction from bytes is observable. */ - static final class Probe implements Serializable { - private static final long serialVersionUID = 1L; - - private void readObject(ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { - in.defaultReadObject(); - probeReconstructed = true; - } - } - - private static byte[] serialize(Serializable object) throws Exception { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - try (ObjectOutputStream out = new ObjectOutputStream(bytes)) { - out.writeObject(object); - } - return bytes.toByteArray(); - } - - @Test - public void readsFlerrorAndMessageFromNormalResponse() throws Exception { - String response = "" - + "" - + "flerror0" - + "messageThanks for the ping." - + ""; - - PingResult result = WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8)); - assertFalse(result.isError()); - assertEquals("Thanks for the ping.", result.getMessage()); - } - - @Test - public void readsErrorFlagFromResponse() throws Exception { - String response = "" - + "" - + "flerror1" - + "messageUnknown blog." - + ""; - - PingResult result = WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8)); - assertTrue(result.isError()); - assertEquals("Unknown blog.", result.getMessage()); - } - - @Test - public void faultResponseBecomesException() throws Exception { - String response = "" - + "" - + "faultCode4" - + "faultStringboom" - + ""; - - XmlRpcException thrown = assertThrows(XmlRpcException.class, - () -> WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8))); - assertEquals(4, thrown.code); - assertEquals("boom", thrown.getMessage()); - } - - @Test - public void controlProbeIsReconstructedByAnObjectStream() throws Exception { - // Control: reading the probe through an ObjectInputStream reconstructs it. - byte[] serialized = serialize(new Probe()); - try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(serialized))) { - in.readObject(); - } - assertTrue(probeReconstructed); - } - - @Test - public void base64ResponseMemberIsReadAsTextOnly() throws Exception { - // A base64 member is read as text; the reader does not turn it into an object. - String embedded = Base64.getEncoder().encodeToString(serialize(new Probe())); - String response = "" - + "" - + "faultCode4" - + "faultStringboom" - + "payload" + embedded + "" - + ""; - - assertThrows(XmlRpcException.class, - () -> WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8))); - assertFalse(probeReconstructed, "a base64 response member must be read as text, not reconstructed"); - } - - @Test - public void doctypeInResponseIsRefused() { - // A response that declares a DTD is refused, and nothing it names is resolved. - String response = "" - + "]>" - + "" - + "flerror0" - + "message&x;" - + ""; - - PingResult result = parse(response); - assertFalse(result.isError()); - } - - @Test - public void buildsWellFormedEscapedRequest() { - String request = WeblogUpdatePinger.buildRequest(Arrays.asList("A & B ", "http://example.test/")); - assertTrue(request.contains("weblogUpdates.ping")); - assertTrue(request.contains("A & B <blog>"), "parameters must be XML-escaped"); - assertTrue(request.contains("http://example.test/")); - assertFalse(request.contains(""), "raw markup must not leak into the request"); - } - - @Test - public void nonSuccessHttpStatusThrows() throws Exception { - // A non-2xx response is a transport failure, not a successful ping. - HttpServer server = startServer(500, "error"); - try { - String url = "http://127.0.0.1:" + server.getAddress().getPort() + "/"; - assertThrows(IOException.class, () -> WeblogUpdatePinger.exchange(url, "")); - } finally { - server.stop(0); - } - } - - @Test - public void successHttpStatusReturnsBody() throws Exception { - String body = "" - + "ok"; - HttpServer server = startServer(200, body); - try { - String url = "http://127.0.0.1:" + server.getAddress().getPort() + "/"; - byte[] bytes = WeblogUpdatePinger.exchange(url, ""); - assertEquals(body, new String(bytes, StandardCharsets.UTF_8)); - } finally { - server.stop(0); - } - } - - @Test - public void nonHttpTargetThrowsIoException() { - // A file:/ftp: target must not escape as a ClassCastException. - assertThrows(IOException.class, - () -> WeblogUpdatePinger.exchange("file:///no/such/file", "")); - } - - private static HttpServer startServer(int status, String body) throws IOException { - HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); - server.createContext("/", exchange -> { - exchange.getRequestBody().readAllBytes(); - byte[] out = body.getBytes(StandardCharsets.UTF_8); - exchange.sendResponseHeaders(status, out.length); - try (OutputStream os = exchange.getResponseBody()) { - os.write(out); - } - }); - server.start(); - return server; - } - - private static PingResult parse(String response) { - try { - return WeblogUpdatePinger.parseResponse(response.getBytes(StandardCharsets.UTF_8)); - } catch (Exception ex) { - throw new AssertionError("parsing should not throw for this response", ex); - } - } -} diff --git a/app/src/test/java/org/apache/roller/weblogger/business/startup/DatabaseInstallerUpgradeTest.java b/app/src/test/java/org/apache/roller/weblogger/business/startup/DatabaseInstallerUpgradeTest.java index 31ef278a6..febe89608 100644 --- a/app/src/test/java/org/apache/roller/weblogger/business/startup/DatabaseInstallerUpgradeTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/business/startup/DatabaseInstallerUpgradeTest.java @@ -20,6 +20,7 @@ import java.sql.*; import java.util.Properties; +import java.util.UUID; import org.apache.roller.weblogger.business.DatabaseProvider; import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; @@ -28,24 +29,105 @@ class DatabaseInstallerUpgradeTest { @Test - void patchReleaseWithoutDatabaseChangesDoesNotRequireUpgrade() throws Exception { + void upgradeFrom610RemovesObsoleteTablesAndSettings() throws Exception { + assertUpgradeRemovesObsoleteTablesAndSettings(610); + } + + @Test + void upgradeFrom615RemovesObsoleteTablesAndSettings() throws Exception { + assertUpgradeRemovesObsoleteTablesAndSettings(615); + } + + @Test + void upgradeFrom617RemovesObsoleteTablesAndSettings() throws Exception { + assertUpgradeRemovesObsoleteTablesAndSettings(617); + } + + private void assertUpgradeRemovesObsoleteTablesAndSettings(int oldVersion) throws Exception { + String url = "jdbc:derby:memory:upgrade" + UUID.randomUUID() + ";create=true"; DatabaseProvider db = mock(DatabaseProvider.class); - Connection con = mock(Connection.class); - Statement query = mock(Statement.class); - ResultSet rows = mock(ResultSet.class); - when(db.getConnection()).thenReturn(con); - when(con.createStatement()).thenReturn(query); - when(query.executeQuery(anyString())).thenReturn(rows); - when(rows.next()).thenReturn(true); - when(rows.getString(1)).thenReturn("615"); + when(db.getConnection()).thenAnswer(invocation -> DriverManager.getConnection(url)); + DatabaseScriptProvider scripts = path -> getClass().getResourceAsStream("/dbscripts/" + path); + + try (Connection con = DriverManager.getConnection(url); Statement stmt = con.createStatement()) { + stmt.executeUpdate("create table weblog (id varchar(48) primary key)"); + stmt.executeUpdate("create table pingtarget (id varchar(48) primary key)"); + stmt.executeUpdate("create table autoping (id varchar(48) primary key, " + + "websiteid varchar(48) references weblog(id), " + + "pingtargetid varchar(48) references pingtarget(id))"); + stmt.executeUpdate("create table pingqueueentry (id varchar(48) primary key)"); + stmt.executeUpdate("create table roller_properties (name varchar(255) primary key, value varchar(255))"); + stmt.executeUpdate("create table roller_tasklock (id varchar(48) primary key, name varchar(255))"); + stmt.executeUpdate("insert into weblog values ('weblog')"); + stmt.executeUpdate("insert into pingtarget values ('target')"); + stmt.executeUpdate("insert into autoping values ('auto', 'weblog', 'target')"); + stmt.executeUpdate("insert into pingqueueentry values ('queued')"); + stmt.executeUpdate("insert into roller_properties values ('roller.database.version', '" + oldVersion + "')"); + stmt.executeUpdate("insert into roller_properties values ('pings.suspendPingProcessing', 'false')"); + stmt.executeUpdate("insert into roller_properties values ('pings.logOnly', 'true')"); + stmt.executeUpdate("insert into roller_properties values ('site.name', 'Keep this site')"); + stmt.executeUpdate("insert into roller_tasklock values ('old', 'PingQueueTask')"); + stmt.executeUpdate("insert into roller_tasklock values ('keep', 'ScheduledEntriesTask')"); + + DatabaseInstaller installer = new DatabaseInstaller(db, scripts); + assertTrue(installer.isUpgradeRequired()); + installer.upgradeDatabase(true); + + assertNoObsoleteTables(con); + assertEquals(0, count(stmt, "select count(*) from roller_properties where name like 'pings.%'")); + assertEquals(0, count(stmt, "select count(*) from roller_tasklock where name = 'PingQueueTask'")); + assertEquals(1, count(stmt, "select count(*) from roller_tasklock where name = 'ScheduledEntriesTask'")); + assertEquals(1, count(stmt, "select count(*) from roller_properties where name = 'site.name' and value = 'Keep this site'")); + assertVersion700(stmt); + assertEquals(1, count(stmt, "select count(*) from weblog")); + assertFalse(installer.isUpgradeRequired()); + assertFalse(installer.getMessages().stream().anyMatch(m -> m.contains("No table changes were required."))); + } + } - DatabaseInstaller installer = new DatabaseInstaller(db, mock(DatabaseScriptProvider.class)); + @Test + void freshInstallOmitsObsoleteTables() throws Exception { + String url = "jdbc:derby:memory:fresh" + UUID.randomUUID() + ";create=true"; + DatabaseProvider db = mock(DatabaseProvider.class); + when(db.getConnection()).thenAnswer(invocation -> DriverManager.getConnection(url)); + DatabaseScriptProvider scripts = path -> getClass().getResourceAsStream("/dbscripts/" + path); + DatabaseInstaller installer = new DatabaseInstaller(db, scripts); - assertFalse(installer.isUpgradeRequired()); + installer.createDatabase(); + + try (Connection con = DriverManager.getConnection(url); Statement stmt = con.createStatement()) { + assertNoObsoleteTables(con); + assertVersion700(stmt); + assertEquals(0, count(stmt, "select count(*) from weblogentry")); + assertFalse(installer.isCreationRequired()); + assertFalse(installer.isUpgradeRequired()); + } + } + + private void assertNoObsoleteTables(Connection con) throws SQLException { + for (String name : new String[] {"AUTOPING", "PINGQUEUEENTRY", "PINGTARGET"}) { + try (ResultSet tables = con.getMetaData().getTables(null, "APP", name, new String[] {"TABLE"})) { + assertFalse(tables.next(), name + " should be absent"); + } + } + } + + private int count(Statement stmt, String sql) throws SQLException { + try (ResultSet rows = stmt.executeQuery(sql)) { + assertTrue(rows.next()); + return rows.getInt(1); + } + } + + private void assertVersion700(Statement stmt) throws SQLException { + try (ResultSet rows = stmt.executeQuery("select value from roller_properties where name = 'roller.database.version'")) { + assertTrue(rows.next()); + assertEquals("700", rows.getString(1)); + } } @Test - void pendingDatabaseMigrationRequiresUpgrade() throws Exception { + void currentSchemaDoesNotRequireUpgrade() throws Exception { DatabaseProvider db = mock(DatabaseProvider.class); Connection con = mock(Connection.class); Statement query = mock(Statement.class); @@ -54,42 +136,28 @@ void pendingDatabaseMigrationRequiresUpgrade() throws Exception { when(con.createStatement()).thenReturn(query); when(query.executeQuery(anyString())).thenReturn(rows); when(rows.next()).thenReturn(true); - when(rows.getString(1)).thenReturn("520"); + when(rows.getString(1)).thenReturn("700"); DatabaseInstaller installer = new DatabaseInstaller(db, mock(DatabaseScriptProvider.class)); - assertTrue(installer.isUpgradeRequired()); + assertFalse(installer.isUpgradeRequired()); } @Test - void versionOnlyUpgradeReportsCompletion() throws Exception { + void pendingDatabaseMigrationRequiresUpgrade() throws Exception { DatabaseProvider db = mock(DatabaseProvider.class); - DatabaseScriptProvider scripts = mock(DatabaseScriptProvider.class); Connection con = mock(Connection.class); Statement query = mock(Statement.class); ResultSet rows = mock(ResultSet.class); - PreparedStatement update = mock(PreparedStatement.class); when(db.getConnection()).thenReturn(con); when(con.createStatement()).thenReturn(query); when(query.executeQuery(anyString())).thenReturn(rows); when(rows.next()).thenReturn(true); - when(rows.getString(1)).thenReturn("610"); - when(con.prepareStatement(anyString())).thenReturn(update); - DatabaseInstaller installer = new DatabaseInstaller(db, scripts); + when(rows.getString(1)).thenReturn("615"); - // the version the installer writes is the one it reads from - // /roller-version.properties, so derive the expectation from the - // same source and parser instead of pinning a release constant - Properties props = new Properties(); - props.load(getClass().getResourceAsStream("/roller-version.properties")); - int expectedVersion = DatabaseInstaller.parseVersionString(props.getProperty("ro.version", "UNKNOWN")); + DatabaseInstaller installer = new DatabaseInstaller(db, mock(DatabaseScriptProvider.class)); - installer.upgradeDatabase(true); - verify(update).setString(1, String.valueOf(expectedVersion)); - verify(update).executeUpdate(); - verifyNoInteractions(scripts); - assertTrue(installer.getMessages().stream().anyMatch(m -> m.contains("No table changes were required."))); - assertTrue(installer.getMessages().stream().anyMatch(m -> m.contains("Database version updated to " + expectedVersion + "."))); + assertTrue(installer.isUpgradeRequired()); } @Test @@ -112,7 +180,7 @@ void schemaUpgradeSuppressesNoChangesMessage() throws Exception { props.load(getClass().getResourceAsStream("/roller-version.properties")); int expectedVersion = DatabaseInstaller.parseVersionString(props.getProperty("ro.version", "UNKNOWN")); - // a 520 database upgrades through the 520->610 schema step; run with + // A 520 database upgrades through the 610 and 700 schema steps; run with // runScripts=false so the step does its bookkeeping without executing a // migration script. Because a schema step ran, the "no table changes" // message must be suppressed. diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEditTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEditTest.java deleted file mode 100644 index 59174430a..000000000 --- a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/admin/PingTargetEditTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * 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. - */ -package org.apache.roller.weblogger.ui.struts2.admin; - -import jakarta.servlet.http.HttpServletRequest; - -import org.apache.roller.weblogger.business.Weblogger; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class PingTargetEditTest { - - @Test - void getDoesNotSave() throws Exception { - PingTargetManager pingTargets = pingTargetManager(); - try (MockedStatic factory = mockStatic(WebloggerFactory.class)) { - Weblogger weblogger = weblogger(factory, pingTargets); - PingTargetEdit action = action("GET"); - - assertEquals(PingTargetEdit.INPUT, action.execute()); - verify(pingTargets, never()).savePingTarget(any()); - verify(weblogger, never()).flush(); - } - } - - @Test - void postSaves() throws Exception { - PingTargetManager pingTargets = pingTargetManager(); - try (MockedStatic factory = mockStatic(WebloggerFactory.class)) { - Weblogger weblogger = weblogger(factory, pingTargets); - PingTargetEdit action = action("POST"); - - assertEquals(PingTargetEdit.SUCCESS, action.execute()); - verify(pingTargets).savePingTarget(any(PingTarget.class)); - verify(weblogger).flush(); - } - } - - private static PingTargetManager pingTargetManager() throws Exception { - PingTargetManager pingTargets = mock(PingTargetManager.class); - when(pingTargets.isUrlWellFormed(anyString())).thenReturn(true); - when(pingTargets.isHostnameKnown(anyString())).thenReturn(true); - return pingTargets; - } - - private static Weblogger weblogger(MockedStatic factory, - PingTargetManager pingTargets) { - Weblogger weblogger = mock(Weblogger.class); - when(weblogger.getPingTargetManager()).thenReturn(pingTargets); - factory.when(WebloggerFactory::getWeblogger).thenReturn(weblogger); - return weblogger; - } - - private static PingTargetEdit action(String method) { - PingTargetEdit action = spy(new PingTargetEdit()); - doAnswer(call -> call.getArgument(0)).when(action).getText(anyString(), anyString(), anyString()); - action.setActionName("commonPingTargetAdd"); - action.getBean().setName("example"); - action.getBean().setPingUrl("http://ping.example.com/rpc"); - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getMethod()).thenReturn(method); - action.withServletRequest(request); - action.myPrepare(); - return action; - } -} diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java index b44f5e08e..b37b8b77d 100644 --- a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/EditorActionTargetBindingTest.java @@ -25,7 +25,6 @@ import org.apache.struts2.util.ValueStack; import org.apache.struts2.util.ValueStackFactory; import org.apache.struts2.util.reflection.ReflectionContextState; -import org.apache.roller.weblogger.ui.struts2.admin.PingTargets; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; @@ -37,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; /** - * No editor or ping-target action lets a request parameter create the domain + * No editor action lets a request parameter create the domain * object it works on. Those objects are loaded only by the action, from the * weblog, by id. */ @@ -79,10 +78,6 @@ private static class Case { private List cases() { return Arrays.asList( - new Case("Pings.pingTarget", Pings::new, "pingTarget.pingUrl", - a -> ((Pings) a).getPingTarget()), - new Case("PingTargets.pingTarget", PingTargets::new, "pingTarget.pingUrl", - a -> ((PingTargets) a).getPingTarget()), new Case("CategoryRemove.category", CategoryRemove::new, "category.name", a -> ((CategoryRemove) a).getCategory()), new Case("EntryRemove.removeEntry", EntryRemove::new, "removeEntry.anchor", diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java deleted file mode 100644 index 3f96520dd..000000000 --- a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/editor/PingsTargetBindingTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * 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. - */ -package org.apache.roller.weblogger.ui.struts2.editor; - -import java.util.Collections; - -import org.apache.struts2.config.ConfigurationManager; -import org.apache.struts2.config.providers.StrutsDefaultConfigurationProvider; -import org.apache.struts2.inject.Container; -import org.apache.struts2.util.ValueStackFactory; -import org.apache.struts2.util.ValueStack; -import org.apache.struts2.util.reflection.ReflectionContextState; -import org.apache.roller.weblogger.business.WebloggerFactory; -import org.apache.roller.weblogger.business.Weblogger; -import org.apache.roller.weblogger.business.pings.PingTargetManager; -import org.apache.roller.weblogger.pojos.PingTarget; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; - -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; - -/** - * The ping target that the Pings action works on must be one of the stored - * ping targets, selected by id. - */ -class PingsTargetBindingTest { - - private MockedStatic factory; - private PingTargetManager pingTargets; - private ValueStack stack; - - @BeforeEach - void setUp() throws Exception { - factory = mockStatic(WebloggerFactory.class); - pingTargets = mock(PingTargetManager.class); - when(pingTargets.getCommonPingTargets()).thenReturn(Collections.emptyList()); - Weblogger weblogger = mock(Weblogger.class); - when(weblogger.getPingTargetManager()).thenReturn(pingTargets); - factory.when(WebloggerFactory::getWeblogger).thenReturn(weblogger); - - ConfigurationManager configurationManager = new ConfigurationManager("xwork"); - configurationManager.addContainerProvider(new StrutsDefaultConfigurationProvider()); - Container container = configurationManager.getConfiguration().getContainer(); - stack = container.getInstance(ValueStackFactory.class).createValueStack(); - } - - @AfterEach - void tearDown() { - factory.close(); - } - - /** Set request parameters on the action the way the params interceptor does. */ - private void bind(Pings action, String name, String value) { - stack.push(action); - ReflectionContextState.setCreatingNullObjects(stack.getContext(), true); - ReflectionContextState.setDenyMethodExecution(stack.getContext(), true); - try { - stack.setParameter(name, value); - } finally { - ReflectionContextState.setCreatingNullObjects(stack.getContext(), false); - stack.pop(); - } - } - - @Test - void pingTargetFieldsInTheRequestDoNotMakeATarget() throws Exception { - Pings action = new Pings(); - bind(action, "pingTarget.pingUrl", "http://localhost:1/"); - action.myPrepare(); - - assertNull(action.getPingTarget()); - } - - @Test - void pingTargetIdSelectsAStoredTarget() throws Exception { - PingTarget stored = new PingTarget("target-1", "Stored", "http://ping.example.com/", false); - when(pingTargets.getPingTarget("target-1")).thenReturn(stored); - - Pings action = new Pings(); - bind(action, "pingTargetId", "target-1"); - bind(action, "pingTarget.pingUrl", "http://localhost:1/"); - action.myPrepare(); - - assertSame(stored, action.getPingTarget()); - assertSame("http://ping.example.com/", action.getPingTarget().getPingUrl()); - } -} diff --git a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/StrutsParameterAnnotationTest.java b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/StrutsParameterAnnotationTest.java index 72b5a65bf..896795efa 100644 --- a/app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/StrutsParameterAnnotationTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/ui/struts2/util/StrutsParameterAnnotationTest.java @@ -63,7 +63,6 @@ public class StrutsParameterAnnotationTest { */ private static final Set NOT_BOUND = Set.of( // set from the static of the action mapping in struts.xml - "PingTargetEdit.actionName", "FolderEdit.actionName", // no such property; the hidden fields are leftovers in the JSPs "Categories.categoryId", @@ -74,9 +73,6 @@ public class StrutsParameterAnnotationTest { * form id. An empty list skips the form. */ private static final Map> FORM_TARGETS = Map.of( - // posted by JavaScript to actionName + ".rol" - "admin/PingTargets.jsp#pingTargetEditForm", - List.of("commonPingTargetAdd", "commonPingTargetEdit"), // popup that is not reachable from any page "editor/MediaFileAddExternalInclude.jsp#entry", List.of()); diff --git a/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomManagementTest.java b/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomManagementTest.java index b99d64575..0fa176050 100644 --- a/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomManagementTest.java +++ b/app/src/test/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomManagementTest.java @@ -70,6 +70,14 @@ public void setUp() throws Exception { TestUtils.setupWeblogger(); owner = TestUtils.setupUser("atommgmtowner"); author = TestUtils.setupUser("atommgmtauthor"); + // Exercise weblog permissions without the first-user site-admin role. + var userManager = WebloggerFactory.getWeblogger().getUserManager(); + for (User user : List.of(owner, author)) { + if (userManager.hasRole("admin", user)) { + userManager.revokeRole("admin", user); + } + } + TestUtils.endSession(true); weblog = TestUtils.setupWeblog(HANDLE, owner); otherWeblog = TestUtils.setupWeblog(OTHER_HANDLE, owner); WebloggerFactory.getWeblogger().getUserManager().grantWeblogPermission( diff --git a/app/src/test/resources/roller-custom.properties b/app/src/test/resources/roller-custom.properties index bae645bdf..ab8e57f9b 100644 --- a/app/src/test/resources/roller-custom.properties +++ b/app/src/test/resources/roller-custom.properties @@ -51,7 +51,7 @@ cache.dir=${project.build.testOutputDirectory}/planet-cache uploads.migrate.auto=false # override tasks.enabled so we can add a test task -tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,PingQueueTask,TestTask +tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,TestTask # test task tasks.TestTask.class=org.apache.roller.weblogger.business.TestTask diff --git a/app/src/test/resources/roller-jettyrun.properties b/app/src/test/resources/roller-jettyrun.properties index 9543cbf74..42562fbce 100644 --- a/app/src/test/resources/roller-jettyrun.properties +++ b/app/src/test/resources/roller-jettyrun.properties @@ -50,7 +50,7 @@ mediafiles.storage.dir =${project.build.directory}/work/mediafiles uploads.migrate.auto=false # override tasks.enabled so we can add a test task -tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,PingQueueTask,TestTask +tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,TestTask # test task tasks.TestTask.class=org.apache.roller.weblogger.business.TestTask diff --git a/docs/images/user-guide-23-ping.png b/docs/images/user-guide-23-ping.png deleted file mode 100644 index dec1d524e278b433c813b631cc0fc9d15c73a806..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117336 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

CPva3}| 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�xgI>`CdKE?9V&W3HoqnKGlh| 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 z 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 m*{*y)gSpFDtk3}a~g!Ga`$oYQ6kTW-C>ORK>p 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

6j6dZGoRo7^Jg_ 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∨EZq@uM_20oc9=K8s=x_KCGW_k!5l!#IKz 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�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 WE&S;d#&I^_NAIxlp|pe6xc>ok-x;a^ diff --git a/docs/roller-install-guide.adoc b/docs/roller-install-guide.adoc index 6fef43b85..40f99d1dc 100644 --- a/docs/roller-install-guide.adoc +++ b/docs/roller-install-guide.adoc @@ -933,7 +933,7 @@ planet.aggregator.enabled=true # Tasks which are enabled. Only tasks listed here will be run. tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,\ -PingQueueTask,RefreshRollerPlanetTask,SyncWebsitesTask +RefreshRollerPlanetTask,SyncWebsitesTask # Set of page models specifically for site-wide rendering rendering.siteModels=\ diff --git a/docs/roller-user-guide.adoc b/docs/roller-user-guide.adoc index b79d2c693..c81430f24 100644 --- a/docs/roller-user-guide.adoc +++ b/docs/roller-user-guide.adoc @@ -37,7 +37,7 @@ You’ll learn how to register as a new user. You’ll learn how to create one or more weblogs for yourself or a group of your friends. You’ll learn how to create, edit and post weblog entries. We will also cover more advanced topics such as adjusting your weblog’s settings, how to -customize your blogroller, how to manage weblog pings and more. +customize your blogroll and more. First, let’s get some terminology out of the way because there’s a lot of jargon in weblogging. Here’s a list of some of the terms we use in @@ -1083,120 +1083,6 @@ are participating in and what privileges you have on each weblog. To resign from a group blog, login, on the Main Menu page, navigate to the blog information for which you wish to resign. Select 'Resign'. -== Using weblog pings - -Weblog update pings provide a means for you to notify aggregation and -indexing sites (for example Weblogs.com, Technorati and javablogs.com) -that your weblog has changed so that they will pick up your latest -content from your RSS feed. - -Roller supports the conventional XML-RPC weblog update ping mechanism -used by many sites for such notifications. - -=== Registering with an Aggregator - -Generally speaking, aggregation sites first require you to register your -weblog with their site. During this registration process you normally -provide both the HTTP URL and the RSS feed URL for your weblog. This is -important because the ping message conveys only the normal HTTP URL of -your site, and the site will use that to lookup the registered RSS feed -URL to fetch from. - -Aggregation sites that accept ping notifications generally publish the -ping URL to use to ping their site on their (human-readable) web site. -Once you have registered your site with an aggregator, you can set up -your weblog to deliver pings to that site. - -=== Ping Targets, Common and Custom - -You can set up the Roller server to ping sites of your choice -automatically whenever you post published updates to your weblog. - -Roller uses the term *ping target* to refer to a site, such as an -aggregator, that accepts weblog update ping notifications. A ping target -is configured with a (display) name and the ping URL needed to reach the -site. Before you can send a ping to a site, you must configure a ping -target in Roller for the site. Roller comes pre-shipped with multiple -ping targets and the Roller administrator can configure additional ones -as desired. - -=== Setting up Automated Pings - -Once a ping target has been configured for the site that you wish to -ping, you can use the *Weblog:Pings* page (shown below) to enable -automatic pings and send manual pings. - -image::user-guide-23-ping.png[] - -To enable automatic pings to a ping target, find the ping target on the -page and click the *Enable* link in the Automatic column. The status -indicator turns to *ON* and the link changes to *Disable* (as shown for -some sites in the screenshot above). To disable automatic pings to a -ping target click the *Disable* link in the Automatic column. The status -indicator turns to *OFF* and the link changes to *Enable*. - -When you have enabled automatic pinging for a ping target, Roller will -automatically send a ping to that site whenever you publish a new weblog -entry or update a published weblog entry. - -NOTE: In actuality, Roller queues a request to send the ping and -processes this request in the background, so that you can get on with -your blogging. The ping queue is processed at an interval configured by -the site administrator; this interval is 5 minutes in a default -configuration. In case the aggregator site is temporarily unreachable, -Roller will requeue your ping request and retry on subsequent passes -through the queue; in a default configuration the ping is requeued for -up to 3 ping attempts._ - -=== Sending a Manual Ping - -You can also send a manual ping to a ping target using the *Send Ping -Now* link listed for the target on the *Weblog:Pings* page. When you -send a manual ping the ping is not queued, it is sent immediately and -attempted only once. Roller shows you the response status (success or a -failure message) that results from the ping. - -You do not need to enable automatic pinging in order to send manual -pings. You can send a manual ping whether or not you have enabled -automatic pinging for that target. - -You can use manual pings if you ping a site very rarely, or if you are -feeling a bit impatient, and you don’t want to wait for the next queue -processing interval. - -If you don’t find a ping target listed for the site you wish to ping, -you can request that your administrator add a a new one for all blogs to -have available. See Chapter 10 for more information on adding ping -targets. - -=== More on Registering with an Aggregator - -When you register with an aggregator, you will usually need to provide -two pieces of information as part of the registration, your blog’s base -(HTML) url and your RSS feed (XML) URL. Make sure to read the -aggregator’s documentation and help on registering. - -For Roller weblogs, you get your weblog’s base URL by viewing your -weblog and taking the URL to the point just following your weblog’s -handle. (In other words it should end with _page_/_handlenamehere_). - -The RSS feed URL for your whole feed can be obtained by substituting -_page_ in your weblog’s URL with _RSS_. Most browsers will display this -link in the status bar when you place your mouse over RSS badge (the -little orange XML box) on your weblog page. - -You also have category-specific feeds, which are useful for registering -with topical aggregators like *java.blogs*. To get a category-specific -feed URL, just append _?catname=/categoryname_ substituting _name_ for -the category name. The "basic" theme has some category RSS feeds just -below the RSS badge in the right-hand vertical bar. - -Some aggregators can also scrape (read and parse the HTML of your -weblog) to discover the feed URL automatically when provided with the -HTML URL. The default Roller theme template pages include hints in the -form of tags that many sites can use to determine the feed URL -automatically. - == Roller administration This section of the Roller user guide is for users with the global admin @@ -1205,9 +1091,8 @@ system gets that role and then can grant it to other users via the *Global Admin->User Admin* page, which just happens to be the first topic we’ll cover in the section. -We’ll also describe how to configure Roller via the **Global -Admin->Configuration **page and how to configure Roller’s custom ping -facility via the *Global Admin->Ping Targets* page. +We’ll also describe how to configure Roller via the *Global +Admin->Configuration* page. === Managing users @@ -1273,8 +1158,6 @@ like the frontpage weblog’s RSS and Atom feeds to be an aggregation of all weblogs on the Roller site. * *Absolute URL to this site*: to be used as basis for creating absolute URLs. Required for Roller’s Planet aggregator feature. -* *Suspend all ping processing*: Allows you to turn off all (outgoing) -weblogs pings for all weblogs in the system. * *Enable debug mode*: currently not used. image::user-guide-27-comments.png[] @@ -1340,182 +1223,6 @@ can still place their own tracking code within their blog templates if you enable custom themes (in the "Theme Settings" section on this configuration page.) -== Weblog update ping administration - -This section, intended for Roller administrators, describes how the -Roller weblog update ping feature works and how to configure and -administer it. - -=== Creating and editing common ping targets - -Common ping targets are ping targets that are shared by all users. You -can create and edit common ping targets using the *Global Admin->Ping -Targets* page. - -image::user-guide-30-ping.png[] - -You can create and edit common ping in the same way that regular users -create and edit custom ping targets, but keep in mind that common ping -targets are shared amongst all users, and that your changes affect all -users using the ping target. Administrators should make sure to test new -common ping targets after creating them. - -Before adding a ping target, the administrator must determine the proper -ping URL for the site that you wish to ping. This information can be -obtained from the aggregator’s web site or from another knowledgeable -source. - -It can be hard to find the aggregator’s documentation for the specific -ping URL to use to notify their site. Commonly aggregators list this on -their web site under a topic providing help about registering your feed, -or under a topic providing information for developers. Keep in mind that -some aggregators only use periodic polling and do not accept ping -notifications at all. If you can’t find any information about pinging on -the aggregator’s web site, the site may not support pinging. - -Click the *Add New* button to add a new ping target. This will bring up -a form with a *Name* field and a *Ping URL* field. Fill in both fields, -and click the *Save* button. - -image::user-guide-31-add-ping.png[] - -Once the new ping target has been created, it will be listed for all -blogs on their *Weblog:Pings* page with the default activation status -defined by the administrator. From this page, individual users can -override the ping target’s enable status for their own blog as well as -send manual pings to the new target. - -=== How Roller Processes Weblog Update Pings - -Roller processes weblog update pings in the background. When a user -updates his or her weblog, Roller automatically queues any required -automated pings on a queue. Roller only keeps one ping queue entry for a -given user weblog and ping target. Subsequent updates to a weblog -occurring before the ping is processed will not cause additional pings -to be queued. - -Roller makes a full pass through the ping queue at regular intervals. -(Configuration of this interval is discussed below) In each pass, Roller -will attempt to send every queued ping request once. If any send fails -(and provided the failure appears to be a transient one), the ping -request will be re-queued, until the ping succeeds or a configured -number of attempts has been made. Note that when a ping request fails -and is re-queued, it is processed again only on subsequent ping passes. -If the number of attempts to reach a given ping target reaches the -maximum without succeeding, then an error message is logged and the ping -request is dropped. - -There is currently no mechanism for alerting users of failing ping -targets (though we plan some improvements in subsequent releases to -provide condition information on the weblog *Preferences->Pings* page, -as well as a failure policy to deactivate persistently failing ping -targets). - -=== Configuration Properties Controlling Ping Processing - -These properties control processing of the ping queue. They are -configured in the __roller.properties __file. - -**pings.queueProcessingIntervalMins **The interval in minutes between -ping queue processing runs. This must be a value in the range 0 to 120. -The default value is 5 minutes. We think the default value should work -for most sites, and is tolerable for most users. The number of users -publishing or updating entries in a given interval determines the length -of the queue, and Roller requires enough time in an interval to process -the queue once. We think that for all but the largest and most active -sites, it can probably be lowered as low as 1 minute if desired. -*IMPORTANT*: The value 0 (zero) has a special meaning. If the processing -interval is set to 0, ping queue processing is disabled on the server. -This can be used to exclude all but one host from sending pings in a -clustered environment where multiple Roller servers are sharing one -database schema. *Make sure to retain one host in the cluster that does -process the ping queue!* If multiple hosts in a cluster process the ping -queue, you may send duplicate pings and failing ping requests may drop -out of the queue sooner than the expected maximum (configured by the -next parameter). If no hosts in a cluster process the ping queue, auto -ping requests will accumulate in the queue and this will eventually -cause the database to run out of space, so don’t try to use this as a -way to disable ping features. You can use the properties described in -the following sections to disable ping features. - -**pings.maxPingAttempts **The maximum number of ping attempts made -before the ping request will no longer be requeued and will instead be -dropped from the queue. The default value is 3. We think this value is -fine for most sites. - -=== Suspending all ping processing - -Administrators can suspend all ping processing at runtime by checking -the *Suspend ping processing?* checkbox under the *Site Settings* -heading on the *Global Admin->Configuration* page and saving that form. - -When this checkbox is set, all ping processing is suspended. New -automatic ping requests are not added to the queue, and existing entries -on the queue are not processed. Manual pings are not sent either; they -result in a message telling the user that ping processing has been -suspended. Suspending ping processing is appropriate to temporarily stop -all ping processing if problems are encountered. - -Unchecking the checkbox and saving allows normal ping processing to -resume. Note, however, that autopings for weblogs that are updated while -ping processing is suspended will never be queued and hence never sent, -but pings queued before the suspension are sent once the suspension is -lifted. - -=== Controlling and disabling ping usage - -Since use of a ping target causes an outbound network connection to the -ping site, some administrators may not want to allow users to create -their own custom ping targets. It is possible to enable or disable the -use of custom ping targets across the whole site and also to disable all -ping usage. The following two properties are used to control this. - -**pings.disallowCustomTargets. **This property controls whether users -are allowed to create custom ping targets. If set to true, all existing -custom ping targets are _removed_, and the *Weblog:Custom Ping Targets* -page and the associated actions are disabled preventing any -configuration of custom ping targets. *Note:* __Setting this to true -this will cause the Roller server to __remove _any custom ping targets -that users have created when Roller is next started._ - -Administrators may also wish to disable ping functionality entirely. The -following property, used in conjunction with the above property, can be -used to do this. - -**pings.disablePingUsage. **This property controls whether users are -allowed to set up automatic pings or send manual pings. If set to true, -all existing autoping configurations are removed (i.e. disabled), the -weblog *Preferences->Pings* page and associated actions are disabled, -preventing any use of the ping features by regular users. *Note:* -__Setting this to true will cause the Roller server to __remove _any -autopings that users have configured when Roller is next started._ - -If both of the above properties are set to true, all ping functionality -is effectively disabled for regular users. The *Global Admin:Ping -Targets* page is still accessible (to administrators); you can use that -page to clear out any common targets if you wish. No user (including -administrators) will be able to configure automatic pings or send pings. -Ping queue processing continues but the queue will always be empty; you -can safely disable ping queue processing (by setting the processing -interval to zero) in this situation. - -=== Initialization of common ping targets - -The initial set of common ping targets is determined by the following -configuration property. - -**pings.initialCommonTargets. **This value is used to initialize the set -of common ping targets. The value consists of a comma-separated list of -ping targets, where each ping target is specified in the form - -{{name}{url}} - -This value is used every time Roller starts _and finds an empty list of -common ping targets._ Normally, this is only the first time Roller is -started on a fresh or upgraded database; note, however, that if you -really want to maintain an empty list of common ping targets, you will -need to comment out this value or set it to an empty string. - == Weblog Export Roller includes an optional weblog export feature that allows you to diff --git a/docs/security-model.md b/docs/security-model.md index 7f3d3e036..35915ddae 100644 --- a/docs/security-model.md +++ b/docs/security-model.md @@ -71,7 +71,6 @@ flowchart TB subgraph Admins["Global admins"] AD["Admin UI
(/roller-ui/admin)"] PL["Planet feed aggregator"] - PG["Ping targets"] end subgraph Setup["First run only"] IN["Installer + setup token"] @@ -86,7 +85,6 @@ flowchart TB AD --> DB IN --> DB PL -->|fetches| NET(("Network")) - PG -->|pings| NET ``` Everything in the Roller WAR is in scope, including the bundled themes. Build @@ -133,9 +131,9 @@ do everything a Limited member can. | **Anonymous visitor** | Read published entries, feeds and search; post comments; register, when registration is on | See drafts, settings or anything unpublished; change anything | No | | **Registered user** (global `editor`) | Log in; edit own profile; comment; create weblogs, when the site allows it | Act in a weblog where they hold no role | No | | **Limited** (`edit_draft`) | Create and edit draft entries in that weblog | Publish; manage comments, categories or media; act in other weblogs | No | -| **Author** (`post`) | Everything Limited can do; publish entries; manage comments and categories; manage media, when uploads are on | Change weblog settings, members, bookmarks, pings or design | No | -| **Admin** (`admin`) | Everything Author can do; weblog settings, members, bookmarks (including OPML import), pings, maintenance; theme and templates, when custom themes are on | Touch other weblogs; read server files; run Java code; read other users' private data; run script in other users' browsers | **No** | -| **Global admin** (global `admin`) | Everything in every weblog; users; site settings; Planet feeds; ping targets | Get shell or file access on the host | Yes | +| **Author** (`post`) | Everything Limited can do; publish entries; manage comments and categories; manage media, when uploads are on | Change weblog settings, members, bookmarks or design | No | +| **Admin** (`admin`) | Everything Author can do; weblog settings, members, bookmarks (including OPML import), maintenance; theme and templates, when custom themes are on | Touch other weblogs; read server files; run Java code; read other users' private data; run script in other users' browsers | **No** | +| **Global admin** (global `admin`) | Everything in every weblog; users; site settings; Planet feeds | Get shell or file access on the host | Yes | | **Network attacker** | Read or change traffic when the site runs over plain HTTP | Anything, once the site runs over HTTPS | No | | **Operator** | Owns the container, JVM, database, configuration files and logs | — | Yes, out of scope | @@ -164,7 +162,6 @@ attack by an anonymous visitor, not by that user. | OPML bookmark import | Weblog Admins | | Remote API requests (XML-RPC, AtomPub) | Anyone, before authentication | | Feeds fetched by Planet | Whoever runs the feed's host | -| Responses from ping targets | Whoever runs the ping target | | Installer forms | Anyone, until setup is complete | Only the operator controls `roller-custom.properties`, JNDI resources and the @@ -202,7 +199,7 @@ flowchart LR - Uploaded files are never served as active content (see below). 4. **Server → network.** Users below global admin cannot make the server fetch a URL of their choice. Entry enclosure URLs are stored, not fetched. Global - admins can point the server at any URL through Planet feeds and ping targets. + admins can point the server at any URL through Planet feeds. That is by design. Two more rules protect the whole site: @@ -333,7 +330,7 @@ Roller does not protect against: installation and upgrades. - Keep the database, data directories and `roller-custom.properties` private. - If the server can reach internal services, put egress rules on Roller's - outbound traffic (Planet, pings). + outbound traffic (Planet). - Run the current release. Older releases are not patched. ## Common mistakes diff --git a/it-playwright/src/test/java/org/apache/roller/playwright/NewUserJourneyIT.java b/it-playwright/src/test/java/org/apache/roller/playwright/NewUserJourneyIT.java index 0a69101ff..6fbb5693f 100644 --- a/it-playwright/src/test/java/org/apache/roller/playwright/NewUserJourneyIT.java +++ b/it-playwright/src/test/java/org/apache/roller/playwright/NewUserJourneyIT.java @@ -119,6 +119,7 @@ void firstUserCanRegisterAndPublish() { register(); signIn(); createWeblog(); + assertRetiredPingPagesAreUnavailable(); publishEntry(BLOG_HANDLE, ENTRY_TITLE, ENTRY_TEXT); assertEntryOnBlog(BLOG_HANDLE, ENTRY_TITLE, ENTRY_TEXT); enableUploads(); @@ -130,6 +131,21 @@ void firstUserCanRegisterAndPublish() { assertPublishingTimeOnOneLine(); } + private void assertRetiredPingPagesAreUnavailable() { + goTo(GLOBAL_CONFIG_PAGE); + assertThat(page.locator("a[href*='commonPingTarget']")).hasCount(0); + goTo("roller-ui/authoring/entries.rol?weblog=" + BLOG_HANDLE); + assertThat(page.locator("a[href*='pings.rol']")).hasCount(0); + for (String url : new String[] { + "roller-ui/admin/commonPingTargets.rol", + "roller-ui/admin/commonPingTargetAdd.rol", + "roller-ui/admin/commonPingTargetEdit.rol", + "roller-ui/authoring/pings.rol?weblog=" + BLOG_HANDLE}) { + APIResponse response = page.request().get(baseUrl + url); + Assertions.assertEquals(404, response.status(), "Retired page should be absent: " + url); + } + } + private void register() { goTo(REGISTER_PAGE); assertThat(page).hasTitle(Pattern.compile("New User Registration")); diff --git a/it-playwright/src/test/resources/roller-jettyrun.properties b/it-playwright/src/test/resources/roller-jettyrun.properties index c91cf5d85..a67d19984 100644 --- a/it-playwright/src/test/resources/roller-jettyrun.properties +++ b/it-playwright/src/test/resources/roller-jettyrun.properties @@ -51,7 +51,7 @@ log4j.appender.roller.File=${project.build.directory}/work/roller.log uploads.migrate.auto=false # override tasks.enabled so we can add a test task -tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,TurnoverReferersTask,PingQueueTask,TestTask +tasks.enabled=ScheduledEntriesTask,ResetHitCountsTask,TurnoverReferersTask,TestTask # test task tasks.TestTask.class=org.apache.roller.weblogger.business.TestTask From 9650f18223c11acc26bc40ef0ffa05f7a824d52f Mon Sep 17 00:00:00 2001 From: "David M. Johnson" Date: Wed, 11 Nov 2026 14:36:05 -0500 Subject: [PATCH 4/4] Use plain DROP TABLE in the 7.0 upgrade and stop dropping ping tables in droptables.sql Not every supported database accepts DROP TABLE IF EXISTS, and every 6.1 schema has the three ping tables. A fresh 7.0 schema no longer has them, so droptables.sql no longer drops them. --- app/src/main/resources/sql/610-to-700-migration.vm | 9 ++------- app/src/main/resources/sql/droptables.sql | 3 --- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/app/src/main/resources/sql/610-to-700-migration.vm b/app/src/main/resources/sql/610-to-700-migration.vm index 0719eab76..e2f4fab0a 100644 --- a/app/src/main/resources/sql/610-to-700-migration.vm +++ b/app/src/main/resources/sql/610-to-700-migration.vm @@ -17,16 +17,11 @@ *# -- Remove the obsolete weblog update ping feature. Drop the referencing table first. -#if ($db.DBTYPE == "DERBY") --- Derby has no DROP TABLE IF EXISTS; these tables exist in the 6.1 schema. +-- Every 6.1 schema has these tables, and not every supported database accepts +-- DROP TABLE IF EXISTS, so use plain DROP TABLE. drop table autoping; drop table pingqueueentry; drop table pingtarget; -#else -#dropTableIfExists('autoping') -#dropTableIfExists('pingqueueentry') -#dropTableIfExists('pingtarget') -#end delete from roller_properties where name like 'pings.%'; delete from roller_tasklock where name = 'PingQueueTask'; diff --git a/app/src/main/resources/sql/droptables.sql b/app/src/main/resources/sql/droptables.sql index 82218dbca..694d269a1 100644 --- a/app/src/main/resources/sql/droptables.sql +++ b/app/src/main/resources/sql/droptables.sql @@ -29,9 +29,6 @@ drop table roller_audit_log; drop table roller_tasklock; -- supplemental services tables -drop table pingqueueentry; -drop table autoping; -drop table pingtarget; drop table bookmark; drop table bookmark_folder; drop table roller_mediafiletag;