diff --git a/celements-messages/component/pom.xml b/celements-messages/component/pom.xml new file mode 100644 index 00000000..64fbdd6f --- /dev/null +++ b/celements-messages/component/pom.xml @@ -0,0 +1,74 @@ + + + + com.celements + celements-parent + 7.1-SNAPSHOT + + 4.0.0 + celements-messages + 7.2-SNAPSHOT + Celements Messages + + + com.celements + celements-core + provided + + + com.celements + celements-model + provided + + + com.celements + celements-spring-security + provided + + + com.celements + celements-xwiki-velocity + provided + + + com.celements + celements-xwiki-core + provided + + + org.springframework + spring-web + provided + + + org.xwiki.platform + xwiki-core-script + provided + + + javax.inject + javax.inject + + + javax.servlet + javax.servlet-api + provided + + + com.celements + celements-shared-tests + test + + + com.celements + celements-subsystem-migration-manager + test + + + + scm:git:git@github.com:celements/celements-features.git + scm:git:git@github.com:celements/celements-features.git + https://github.com/celements/celements-features + HEAD + + diff --git a/celements-messages/component/src/main/java/com/celements/messages/api/MessagesController.java b/celements-messages/component/src/main/java/com/celements/messages/api/MessagesController.java new file mode 100644 index 00000000..3e1ea041 --- /dev/null +++ b/celements-messages/component/src/main/java/com/celements/messages/api/MessagesController.java @@ -0,0 +1,55 @@ +package com.celements.messages.api; + +import java.io.IOException; + +import javax.inject.Inject; + +import org.apache.velocity.VelocityContext; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.xwiki.velocity.VelocityManager; +import org.xwiki.velocity.XWikiVelocityException; + +import com.celements.messages.service.MessageService; +import com.celements.spring.security.AuthenticatedBaseController; +import com.celements.web.service.IPrepareVelocityContext; + +@RestController +@RequestMapping("/v1/messages") +@PreAuthorize("permitAll()") +public class MessagesController extends AuthenticatedBaseController { + + private static final String JSON_MEDIA_TYPE = MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"; + + private final VelocityManager velocityManager; + private final IPrepareVelocityContext prepareVelocityContext; + private final MessageService messageService; + + @Inject + public MessagesController(VelocityManager velocityManager, + IPrepareVelocityContext prepareVelocityContext, MessageService messageService) { + this.velocityManager = velocityManager; + this.prepareVelocityContext = prepareVelocityContext; + this.messageService = messageService; + } + + @GetMapping(produces = JSON_MEDIA_TYPE) + public String getMessages() throws IOException, XWikiVelocityException { + return messageService.getMessages(getPreparedVelocityContext()); + } + + @GetMapping(value = "/validation", produces = JSON_MEDIA_TYPE) + public String getValidationMessages() throws IOException, XWikiVelocityException { + return messageService.getValidationMessages(getPreparedVelocityContext()); + } + + private VelocityContext getPreparedVelocityContext() { + checkAuth(); + VelocityContext velocityContext = velocityManager.getVelocityContext(); + prepareVelocityContext.prepareVelocityContext(velocityContext); + return velocityContext; + } +} diff --git a/celements-messages/component/src/main/java/com/celements/messages/script/MessagesScriptService.java b/celements-messages/component/src/main/java/com/celements/messages/script/MessagesScriptService.java new file mode 100644 index 00000000..6fcdeacd --- /dev/null +++ b/celements-messages/component/src/main/java/com/celements/messages/script/MessagesScriptService.java @@ -0,0 +1,35 @@ +package com.celements.messages.script; + +import java.io.IOException; + +import javax.inject.Inject; + +import org.springframework.stereotype.Component; +import org.xwiki.script.service.ScriptService; +import org.xwiki.velocity.VelocityManager; +import org.xwiki.velocity.XWikiVelocityException; + +import com.celements.messages.service.MessageService; + +@Component(MessagesScriptService.NAME) +public class MessagesScriptService implements ScriptService { + + public static final String NAME = "celementsMessages"; + + private final VelocityManager velocityManager; + private final MessageService messageService; + + @Inject + public MessagesScriptService(VelocityManager velocityManager, MessageService messageService) { + this.velocityManager = velocityManager; + this.messageService = messageService; + } + + public String getMessages() throws IOException, XWikiVelocityException { + return messageService.getMessages(velocityManager.getVelocityContext()); + } + + public String getValidationMessages() throws IOException, XWikiVelocityException { + return messageService.getValidationMessages(velocityManager.getVelocityContext()); + } +} diff --git a/celements-messages/component/src/main/java/com/celements/messages/service/MessageService.java b/celements-messages/component/src/main/java/com/celements/messages/service/MessageService.java new file mode 100644 index 00000000..721b891d --- /dev/null +++ b/celements-messages/component/src/main/java/com/celements/messages/service/MessageService.java @@ -0,0 +1,124 @@ +package com.celements.messages.service; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import javax.inject.Inject; +import javax.servlet.ServletContext; + +import org.apache.velocity.VelocityContext; +import org.springframework.stereotype.Component; +import org.xwiki.velocity.VelocityEngine; +import org.xwiki.velocity.VelocityManager; +import org.xwiki.velocity.XWikiVelocityException; + +import com.celements.sajson.JsonBuilder; + +@Component +public class MessageService { + + static final String GENERAL_FRAGMENT_DIRECTORY = "/templates/celMessages/general/"; + static final String VALIDATION_FRAGMENT_DIRECTORY = "/templates/celMessages/validation/"; + private static final String REQUIRED_FRAGMENT_NAME = "celements.vm"; + private static final String JSON_BUILDER_CONTEXT_KEY = "jsonBuilder"; + + private final ServletContext servletContext; + private final VelocityManager velocityManager; + + @Inject + public MessageService(ServletContext servletContext, VelocityManager velocityManager) { + this.servletContext = servletContext; + this.velocityManager = velocityManager; + } + + public String getMessages(VelocityContext velocityContext) + throws IOException, XWikiVelocityException { + return renderFragments(GENERAL_FRAGMENT_DIRECTORY, velocityContext); + } + + public String getValidationMessages(VelocityContext velocityContext) + throws IOException, XWikiVelocityException { + return renderFragments(VALIDATION_FRAGMENT_DIRECTORY, velocityContext); + } + + private String renderFragments(String directory, VelocityContext velocityContext) + throws IOException, XWikiVelocityException { + var jsonBuilder = new JsonBuilder(); + jsonBuilder.openDictionary(); + List fragments = discoverFragments(directory); + requireBaseFragment(directory, fragments); + boolean hadJsonBuilder = velocityContext.containsKey(JSON_BUILDER_CONTEXT_KEY); + Object previousJsonBuilder = velocityContext.get(JSON_BUILDER_CONTEXT_KEY); + velocityContext.put(JSON_BUILDER_CONTEXT_KEY, jsonBuilder); + try { + VelocityEngine velocityEngine = velocityManager.getVelocityEngine(); + for (String fragment : fragments) { + evaluateFragment(velocityEngine, velocityContext, jsonBuilder, fragment); + } + jsonBuilder.closeDictionary(); + if (!jsonBuilder.isComplete()) { + throw new IllegalStateException("Messages JSON builder is incomplete"); + } + return jsonBuilder.getJSON(); + } finally { + if (hadJsonBuilder) { + velocityContext.put(JSON_BUILDER_CONTEXT_KEY, previousJsonBuilder); + } else { + velocityContext.remove(JSON_BUILDER_CONTEXT_KEY); + } + } + } + + private List discoverFragments(String directory) { + return Optional.ofNullable(servletContext.getResourcePaths(directory)) + .orElseGet(Collections::emptySet) + .stream() + .filter(path -> isDirectFragment(directory, path)) + .sorted() + .toList(); + } + + private boolean isDirectFragment(String directory, String path) { + return path.startsWith(directory) && path.endsWith(".vm") + && !path.substring(directory.length()).contains("/"); + } + + private void requireBaseFragment(String directory, List fragments) throws IOException { + String requiredFragment = directory + REQUIRED_FRAGMENT_NAME; + if (!fragments.contains(requiredFragment)) { + throw new IOException("Required message fragment not found: " + requiredFragment); + } + } + + private void evaluateFragment(VelocityEngine velocityEngine, VelocityContext velocityContext, + JsonBuilder jsonBuilder, String fragment) throws IOException, XWikiVelocityException { + int initialDepth = jsonBuilder.getOpenCommandCount(); + var writer = new StringWriter(); + boolean evaluated = velocityEngine.evaluate(velocityContext, writer, fragment, + getFragmentContent(fragment)); + if (!evaluated) { + throw new XWikiVelocityException("Failed to evaluate message fragment: " + fragment); + } + if (!writer.toString().trim().isEmpty()) { + throw new IOException("Message fragment emitted response content: " + fragment); + } + if (jsonBuilder.getOpenCommandCount() != initialDepth) { + throw new IllegalStateException( + "Message fragment left an unbalanced JSON builder: " + fragment); + } + } + + private String getFragmentContent(String fragment) throws IOException { + try (InputStream stream = servletContext.getResourceAsStream(fragment)) { + if (stream == null) { + throw new IOException("Message fragment not found: " + fragment); + } + return new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + } +} diff --git a/celements-messages/component/src/test/java/com/celements/messages/api/MessagesControllerTest.java b/celements-messages/component/src/test/java/com/celements/messages/api/MessagesControllerTest.java new file mode 100644 index 00000000..103ea62a --- /dev/null +++ b/celements-messages/component/src/test/java/com/celements/messages/api/MessagesControllerTest.java @@ -0,0 +1,109 @@ +package com.celements.messages.api; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.velocity.VelocityContext; +import org.junit.Before; +import org.junit.Test; +import org.springframework.aop.framework.Advised; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.xwiki.velocity.VelocityManager; + +import com.celements.auth.user.UserService; +import com.celements.common.test.AbstractComponentTest; +import com.celements.messages.service.MessageService; +import com.celements.rights.access.IRightsAccessFacadeRole; +import com.celements.web.service.IPrepareVelocityContext; +import com.xpn.xwiki.XWiki; + +public class MessagesControllerTest extends AbstractComponentTest { + + private VelocityContext velocityContext; + private MessagesController controller; + + @Before + public void prepareTest() throws Exception { + registerComponentMocks(VelocityManager.class, IPrepareVelocityContext.class, + MessageService.class, UserService.class, IRightsAccessFacadeRole.class); + velocityContext = new VelocityContext(); + controller = getBeanTarget(MessagesController.class); + } + + @Test + public void testEndpointMappings() throws Exception { + assertNotNull(MessagesController.class.getAnnotation(RestController.class)); + assertArrayEquals(new String[] { "/v1/messages" }, + MessagesController.class.getAnnotation(RequestMapping.class).value()); + assertEquals("permitAll()", MessagesController.class.getAnnotation(PreAuthorize.class).value()); + GetMapping general = MessagesController.class.getMethod("getMessages") + .getAnnotation(GetMapping.class); + assertArrayEquals(new String[0], general.value()); + assertArrayEquals(new String[] { "application/json;charset=UTF-8" }, general.produces()); + GetMapping validation = MessagesController.class.getMethod("getValidationMessages") + .getAnnotation(GetMapping.class); + assertArrayEquals(new String[] { "/validation" }, validation.value()); + assertArrayEquals(new String[] { "application/json;charset=UTF-8" }, validation.produces()); + } + + @Test + public void testGetMessagesUsesPreparedExistingContext() throws Exception { + expectAnonymousAuth(); + expectPreparedContext(); + expect(getMock(MessageService.class).getMessages(same(velocityContext))) + .andReturn("{\"message\":\"value\"}"); + replayDefault(); + + assertEquals("{\"message\":\"value\"}", controller.getMessages()); + + verifyDefault(); + } + + @Test + public void testGetValidationMessagesUsesPreparedExistingContext() throws Exception { + expectAnonymousAuth(); + expectPreparedContext(); + expect(getMock(MessageService.class).getValidationMessages(same(velocityContext))) + .andReturn("{\"required\":\"Required\"}"); + replayDefault(); + + assertEquals("{\"required\":\"Required\"}", controller.getValidationMessages()); + + verifyDefault(); + } + + @Test + public void testServiceFailurePropagates() throws Exception { + expectAnonymousAuth(); + expectPreparedContext(); + expect(getMock(MessageService.class).getMessages(same(velocityContext))) + .andThrow(new IOException("failed")); + replayDefault(); + + assertThrows(IOException.class, controller::getMessages); + + verifyDefault(); + } + + private void expectPreparedContext() { + expect(getMock(VelocityManager.class).getVelocityContext()).andReturn(velocityContext); + getMock(IPrepareVelocityContext.class).prepareVelocityContext(same(velocityContext)); + } + + private void expectAnonymousAuth() throws Exception { + expect(getMock(XWiki.class).checkAuth(getXContext())).andReturn(null); + } + + @SuppressWarnings("unchecked") + private T getBeanTarget(Class beanClass) throws Exception { + T bean = getBeanFactory().getBean(beanClass); + return bean instanceof Advised advised + ? (T) advised.getTargetSource().getTarget() + : bean; + } +} diff --git a/celements-messages/component/src/test/java/com/celements/messages/script/MessagesScriptServiceTest.java b/celements-messages/component/src/test/java/com/celements/messages/script/MessagesScriptServiceTest.java new file mode 100644 index 00000000..a42abae5 --- /dev/null +++ b/celements-messages/component/src/test/java/com/celements/messages/script/MessagesScriptServiceTest.java @@ -0,0 +1,72 @@ +package com.celements.messages.script; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.velocity.VelocityContext; +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Component; +import org.xwiki.script.service.ScriptService; +import org.xwiki.velocity.VelocityManager; + +import com.celements.common.test.AbstractComponentTest; +import com.celements.messages.service.MessageService; + +public class MessagesScriptServiceTest extends AbstractComponentTest { + + private VelocityContext velocityContext; + private MessagesScriptService scriptService; + + @Before + public void prepareTest() throws Exception { + registerComponentMocks(VelocityManager.class, MessageService.class); + velocityContext = new VelocityContext(); + scriptService = getBeanFactory().getBean(MessagesScriptService.class); + } + + @Test + public void testComponentRegistration() { + assertTrue(scriptService instanceof ScriptService); + assertEquals(MessagesScriptService.NAME, + MessagesScriptService.class.getAnnotation(Component.class).value()); + } + + @Test + public void testGetMessagesUsesActiveExistingContext() throws Exception { + expect(getMock(VelocityManager.class).getVelocityContext()).andReturn(velocityContext); + expect(getMock(MessageService.class).getMessages(same(velocityContext))) + .andReturn("{\"message\":\"value\"}"); + replayDefault(); + + assertEquals("{\"message\":\"value\"}", scriptService.getMessages()); + + verifyDefault(); + } + + @Test + public void testGetValidationMessagesUsesActiveExistingContext() throws Exception { + expect(getMock(VelocityManager.class).getVelocityContext()).andReturn(velocityContext); + expect(getMock(MessageService.class).getValidationMessages(same(velocityContext))) + .andReturn("{\"required\":\"Required\"}"); + replayDefault(); + + assertEquals("{\"required\":\"Required\"}", scriptService.getValidationMessages()); + + verifyDefault(); + } + + @Test + public void testServiceFailurePropagates() throws Exception { + expect(getMock(VelocityManager.class).getVelocityContext()).andReturn(velocityContext); + expect(getMock(MessageService.class).getMessages(same(velocityContext))) + .andThrow(new IOException("failed")); + replayDefault(); + + assertThrows(IOException.class, scriptService::getMessages); + + verifyDefault(); + } +} diff --git a/celements-messages/component/src/test/java/com/celements/messages/service/MessageServiceTest.java b/celements-messages/component/src/test/java/com/celements/messages/service/MessageServiceTest.java new file mode 100644 index 00000000..1813eaf2 --- /dev/null +++ b/celements-messages/component/src/test/java/com/celements/messages/service/MessageServiceTest.java @@ -0,0 +1,198 @@ +package com.celements.messages.service; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.velocity.VelocityContext; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.mock.web.MockServletContext; +import org.springframework.util.FileSystemUtils; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.xwiki.velocity.VelocityEngine; +import org.xwiki.velocity.VelocityManager; +import org.xwiki.velocity.XWikiVelocityException; + +import com.celements.common.test.AbstractComponentTest; +import com.celements.sajson.JsonBuilder; + +public class MessageServiceTest extends AbstractComponentTest { + + @ClassRule + public static final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private VelocityEngine velocityEngine; + private VelocityContext velocityContext; + private MessageService messageService; + private List evaluatedFragments; + + @Override + protected void beforeSpringContextRefresh(ConfigurableApplicationContext context) { + super.beforeSpringContextRefresh(context); + ((ConfigurableWebApplicationContext) context).setServletContext(new MockServletContext( + "file:" + temporaryFolder.getRoot().getAbsolutePath())); + } + + @Before + public void prepareTest() throws Exception { + FileSystemUtils.deleteRecursively(temporaryFolder.getRoot().toPath().resolve("templates")); + registerComponentMocks(VelocityManager.class); + velocityEngine = createDefaultMock(VelocityEngine.class); + velocityContext = new VelocityContext(); + messageService = getBeanFactory().getBean(MessageService.class); + evaluatedFragments = new ArrayList<>(); + } + + @Test + public void testGetMessagesUsesLexicalDirectFragments() throws Exception { + String directory = MessageService.GENERAL_FRAGMENT_DIRECTORY; + String celements = directory + "celements.vm"; + String zeta = directory + "zeta.vm"; + createFragment(celements); + createFragment(zeta); + createFragment(directory + "nested/ignored.vm"); + expectVelocityEngine(); + expectFragment(celements, " \n", true, builder -> { + builder.addProperty("string", "value"); + builder.addProperty("boolean", true); + builder.addProperty("number", 7); + }); + expectFragment(zeta, "\n", true, builder -> builder.addProperty("zeta", "last")); + replayDefault(); + + String json = messageService.getMessages(velocityContext); + + assertEquals( + "{\"string\" : \"value\", \"boolean\" : true, \"number\" : 7, " + + "\"zeta\" : \"last\"}", + json); + assertEquals(List.of(celements, zeta), evaluatedFragments); + assertFalse(velocityContext.containsKey("jsonBuilder")); + verifyDefault(); + } + + @Test + public void testGetValidationMessagesUsesOnlyValidationDirectory() throws Exception { + String directory = MessageService.VALIDATION_FRAGMENT_DIRECTORY; + String celements = directory + "celements.vm"; + createFragment(celements); + expectVelocityEngine(); + expectFragment(celements, "", true, builder -> builder.addProperty("required", "Required")); + replayDefault(); + + assertEquals("{\"required\" : \"Required\"}", + messageService.getValidationMessages(velocityContext)); + + assertEquals(List.of(celements), evaluatedFragments); + verifyDefault(); + } + + @Test + public void testMissingCelementsFragmentFailsCompleteRequest() throws Exception { + String directory = MessageService.GENERAL_FRAGMENT_DIRECTORY; + createFragment(directory + "product.vm"); + replayDefault(); + + assertThrows(IOException.class, () -> messageService.getMessages(velocityContext)); + + assertFalse(velocityContext.containsKey("jsonBuilder")); + verifyDefault(); + } + + @Test + public void testNonWhitespaceFragmentOutputFailsCompleteRequest() throws Exception { + String directory = MessageService.GENERAL_FRAGMENT_DIRECTORY; + String celements = directory + "celements.vm"; + createFragment(celements); + expectVelocityEngine(); + expectFragment(celements, "unexpected", true, + builder -> builder.addProperty("message", "value")); + replayDefault(); + + assertThrows(IOException.class, () -> messageService.getMessages(velocityContext)); + + assertFalse(velocityContext.containsKey("jsonBuilder")); + verifyDefault(); + } + + @Test + public void testFailedFragmentEvaluationFailsCompleteRequest() throws Exception { + String directory = MessageService.GENERAL_FRAGMENT_DIRECTORY; + String celements = directory + "celements.vm"; + createFragment(celements); + expectVelocityEngine(); + expectFragment(celements, "", false, builder -> assertNotNull(builder)); + replayDefault(); + + assertThrows(XWikiVelocityException.class, () -> messageService.getMessages(velocityContext)); + + assertFalse(velocityContext.containsKey("jsonBuilder")); + verifyDefault(); + } + + @Test + public void testUnbalancedBuilderDepthFailsCompleteRequest() throws Exception { + String directory = MessageService.GENERAL_FRAGMENT_DIRECTORY; + String celements = directory + "celements.vm"; + createFragment(celements); + expectVelocityEngine(); + expectFragment(celements, "", true, builder -> builder.openDictionary("nested")); + replayDefault(); + + assertThrows(IllegalStateException.class, + () -> messageService.getMessages(velocityContext)); + + assertFalse(velocityContext.containsKey("jsonBuilder")); + verifyDefault(); + } + + @Test + public void testExistingJsonBuilderContextValueIsRestored() throws Exception { + String directory = MessageService.GENERAL_FRAGMENT_DIRECTORY; + String celements = directory + "celements.vm"; + var previousBuilder = new JsonBuilder(); + velocityContext.put("jsonBuilder", previousBuilder); + createFragment(celements); + expectVelocityEngine(); + expectFragment(celements, "", true, builder -> builder.addProperty("message", "value")); + replayDefault(); + + messageService.getMessages(velocityContext); + + assertSame(previousBuilder, velocityContext.get("jsonBuilder")); + verifyDefault(); + } + + private void createFragment(String path) throws IOException { + var fragment = temporaryFolder.getRoot().toPath().resolve(path.substring(1)); + Files.createDirectories(fragment.getParent()); + Files.writeString(fragment, "fragment:" + path, StandardCharsets.UTF_8); + } + + private void expectVelocityEngine() throws XWikiVelocityException { + expect(getMock(VelocityManager.class).getVelocityEngine()).andReturn(velocityEngine); + } + + private void expectFragment(String path, String output, boolean result, + Consumer builderAction) throws Exception { + String source = "fragment:" + path; + expect(velocityEngine.evaluate(same(velocityContext), isA(StringWriter.class), eq(path), + eq(source))).andAnswer(() -> { + evaluatedFragments.add(path); + builderAction.accept((JsonBuilder) velocityContext.get("jsonBuilder")); + ((StringWriter) getCurrentArguments()[1]).write(output); + return result; + }); + } +} diff --git a/celements-messages/web-module/pom.xml b/celements-messages/web-module/pom.xml new file mode 100644 index 00000000..76acdbdc --- /dev/null +++ b/celements-messages/web-module/pom.xml @@ -0,0 +1,28 @@ + + + + com.celements + celements-parent + 7.1-SNAPSHOT + + 4.0.0 + celements-messages-web + 7.2-SNAPSHOT + war + Celements Messages Web + + + com.celements + celements-messages + + + + celements-messages-web + + + scm:git:git@github.com:celements/celements-features.git + scm:git:git@github.com:celements/celements-features.git + https://github.com/celements/celements-features + HEAD + + diff --git a/celements-messages/web-module/src/main/webapp/WEB-INF/web.xml b/celements-messages/web-module/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..efb8ef04 --- /dev/null +++ b/celements-messages/web-module/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,3 @@ + + + diff --git a/celements-messages/web-module/src/main/webapp/templates/celAjax/Messages.vm b/celements-messages/web-module/src/main/webapp/templates/celAjax/Messages.vm new file mode 100644 index 00000000..e6a863b2 --- /dev/null +++ b/celements-messages/web-module/src/main/webapp/templates/celAjax/Messages.vm @@ -0,0 +1 @@ +$services.celementsMessages.getMessages() diff --git a/celements-messages/web-module/src/main/webapp/templates/celAjax/ValidationMessages.vm b/celements-messages/web-module/src/main/webapp/templates/celAjax/ValidationMessages.vm new file mode 100644 index 00000000..256eaa1e --- /dev/null +++ b/celements-messages/web-module/src/main/webapp/templates/celAjax/ValidationMessages.vm @@ -0,0 +1 @@ +$services.celementsMessages.getValidationMessages() diff --git a/celements-messages/web-module/src/main/webapp/templates/celMessages/general/celements.vm b/celements-messages/web-module/src/main/webapp/templates/celMessages/general/celements.vm new file mode 100644 index 00000000..63dedd8f --- /dev/null +++ b/celements-messages/web-module/src/main/webapp/templates/celMessages/general/celements.vm @@ -0,0 +1,215 @@ +$jsonBuilder.openProperty('celmeta') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('language', "$language") +$jsonBuilder.addProperty('admin-language', "$admin_language") +$jsonBuilder.addProperty('contextPath', "${request.contextPath}") +$jsonBuilder.addProperty('database', "${context.database}") +$jsonBuilder.openProperty('isAdmin') +$jsonBuilder.addValue($isAdmin) +$jsonBuilder.openProperty('isSuperAdmin') +$jsonBuilder.addValue($isSuperAdmin) +$jsonBuilder.addProperty('startupTimeStamp', "${services.celementsweb.getLastStartupTimeStamp()}") +$jsonBuilder.closeDictionary() +## Reconnector +$jsonBuilder.openProperty('Reconnector') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('retryNotice', "$!adminMsg.get('cel_reconnector_retryNotice')") +$jsonBuilder.addProperty('retryDelayNotice', "$!adminMsg.get('cel_reconnector_retryDelayNotice')") +$jsonBuilder.closeDictionary() +## jquery.formater +$jsonBuilder.openProperty('jqueryFormater') +$jsonBuilder.openDictionary() +$jsonBuilder.openProperty('monthsFull') +$jsonBuilder.openArray() +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_january')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_february')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_march')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_april')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_may')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_june')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_july')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_august')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_september')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_october')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_november')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsFull_december')") +$jsonBuilder.closeArray() +$jsonBuilder.openProperty('monthsShort') +$jsonBuilder.openArray() +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_january')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_february')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_march')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_april')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_may')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_june')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_july')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_august')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_september')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_october')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_november')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_monthsShort_december')") +$jsonBuilder.closeArray() +$jsonBuilder.openProperty('daysFull') +$jsonBuilder.openArray() +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysFull_sunday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysFull_monday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysFull_tuesday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysFull_wednesday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysFull_thursday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysFull_friday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysFull_saturday')") +$jsonBuilder.closeArray() +$jsonBuilder.openProperty('daysShort') +$jsonBuilder.openArray() +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysShort_sunday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysShort_monday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysShort_tuesday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysShort_wednesday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysShort_thursday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysShort_friday')") +$jsonBuilder.addValue("$!adminMsg.get('cel_date_formater_daysShort_saturday')") +$jsonBuilder.closeArray() +$jsonBuilder.closeDictionary() +## celements photo slideShow +$jsonBuilder.openProperty('celslideshow') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('cmPause', "$!adminMsg.get('cel_slideshow_cm_pause')") +$jsonBuilder.addProperty('cmContinue', "$!adminMsg.get('cel_slideshow_cm_continue')") +$jsonBuilder.addProperty('cmOpenLink', "$!adminMsg.get('cel_slideshow_cm_openlink')") +$jsonBuilder.closeDictionary() +## celements admin importer (import.js) +$jsonBuilder.openProperty('celAdminImport') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('availableDocuments', "$!adminMsg.get('core.importer.availableDocuments')") +$jsonBuilder.addProperty('importHistoryLabel', "$!adminMsg.get('core.importer.importHistory')") +$jsonBuilder.addProperty('selectionEmpty', "$!adminMsg.get('core.importer.selectionEmptyWarning')") +$jsonBuilder.addProperty('import', "$!adminMsg.get('core.importer.import')") +$jsonBuilder.addProperty('package', "$!adminMsg.get('core.importer.package')") +$jsonBuilder.addProperty('description', "$!adminMsg.get('core.importer.package.description')") +$jsonBuilder.addProperty('version', "$!adminMsg.get('core.importer.package.version')") +$jsonBuilder.addProperty('licence', "$!adminMsg.get('core.importer.package.licence')") +$jsonBuilder.addProperty('author', "$!adminMsg.get('core.importer.package.author')") +$jsonBuilder.addProperty('documentSelected', "$!adminMsg.get('core.importer.documentSelected')") +$jsonBuilder.addProperty('whenDocumentAlreadyExists', "$!adminMsg.get('core.importer.whenDocumentAlreadyExists')") +$jsonBuilder.addProperty('addNewVersion', "$!adminMsg.get('core.importer.addNewVersion')") +$jsonBuilder.addProperty('replaceDocumentHistory', "$!adminMsg.get('core.importer.replaceDocumentHistory')") +$jsonBuilder.addProperty('resetHistory', "$!adminMsg.get('core.importer.resetHistory')") +$jsonBuilder.addProperty('importAsBackup', "$!adminMsg.get('core.importer.importAsBackup')") +$jsonBuilder.addProperty('select', "$!adminMsg.get('core.importer.select')") +$jsonBuilder.addProperty('all', "$!adminMsg.get('core.importer.selectAll')") +$jsonBuilder.addProperty('none', "$!adminMsg.get('core.importer.selectNone')") +$jsonBuilder.closeDictionary() +## celements menu +$jsonBuilder.openProperty('celmenu') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('supportLinkURL', "$!services.celementsweb.getSupportLinkURL()") +$jsonBuilder.closeDictionary() +## xwiki/actionButtons.js +$jsonBuilder.openProperty('actionButtons') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('cancel', "$!adminMsg.get('core.shortcuts.edit.cancel')") +$jsonBuilder.addProperty('preview', "$!adminMsg.get('core.shortcuts.edit.preview')") +$jsonBuilder.addProperty('backtoedit', "$!adminMsg.get('core.shortcuts.edit.backtoedit')") +$jsonBuilder.addProperty('saveandview', "$!adminMsg.get('core.shortcuts.edit.saveandview')") +$jsonBuilder.addProperty('saveandcontinue', "$!adminMsg.get('core.shortcuts.edit.saveandcontinue')") +$jsonBuilder.addProperty('mandatoryField', "$!adminMsg.get('core.editors.validation.mandatoryField')") +$jsonBuilder.addProperty('commentPrompt', "$!adminMsg.get('core.comment.prompt')") +$jsonBuilder.closeDictionary() +## xwiki/attachments.js +$jsonBuilder.openProperty('attachments') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('deleteConfirm', "$!adminMsg.get('core.viewers.attachments.delete.confirm')") +$jsonBuilder.addProperty('deleteInProgress', "$!adminMsg.get('core.viewers.attachments.delete.inProgress')") +$jsonBuilder.addProperty('deleteDone', "$!adminMsg.get('core.viewers.attachments.delete.done')") +$jsonBuilder.addProperty('deleteFailed', "$!adminMsg.get('core.viewers.attachments.delete.failed')") +$jsonBuilder.addProperty('addFileInput', "$!adminMsg.get('core.viewers.attachments.upload.addFileInput')") +$jsonBuilder.addProperty('removeFileInput', "$!adminMsg.get('core.viewers.attachments.upload.removeFileInput')") +$jsonBuilder.addProperty('removeFileInputTitle', "$!adminMsg.get('core.viewers.attachments.upload.removeFileInput.title')") +$jsonBuilder.addProperty('extranb', "$!adminMsg.get('docextra.extranb', ['__number__'])") +$jsonBuilder.closeDictionary() +## xwiki/code.js +$jsonBuilder.openProperty('code') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('showLineNumbers', "$!adminMsg.get('core.viewers.code.showLineNumbers')") +$jsonBuilder.addProperty('hideLineNumbers', "$!adminMsg.get('core.viewers.code.hideLineNumbers')") +$jsonBuilder.closeDictionary() +## xwiki/comments.js +$jsonBuilder.openProperty('comments') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('commentDeleted', "$!adminMsg.get('core.viewers.comments.commentDeleted')") +$jsonBuilder.addProperty('deleteConfirm', "$!adminMsg.get('core.viewers.comments.delete.confirm')") +$jsonBuilder.addProperty('deleteInProgress', "$!adminMsg.get('core.viewers.comments.delete.inProgress')") +$jsonBuilder.addProperty('deleteDone', "$!adminMsg.get('core.viewers.comments.delete.done')") +$jsonBuilder.addProperty('deleteFailed', "$!adminMsg.get('core.viewers.comments.delete.failed')") +$jsonBuilder.addProperty('editFormFetchInProgress', "$!adminMsg.get('core.viewers.comments.editForm.fetch.inProgress')") +$jsonBuilder.addProperty('editFormFetchFailed', "$!adminMsg.get('core.viewers.comments.editForm.fetch.failed')") +$jsonBuilder.addProperty('addInProgress', "$!adminMsg.get('core.viewers.comments.add.inProgress')") +$jsonBuilder.addProperty('addDone', "$!adminMsg.get('core.viewers.comments.add.done')") +$jsonBuilder.addProperty('addFailed', "$!adminMsg.get('core.viewers.comments.add.failed')") +$jsonBuilder.addProperty('previewButtonPreview', "$!adminMsg.get('core.viewers.comments.preview.button.preview')") +$jsonBuilder.addProperty('previewInProgress', "$!adminMsg.get('core.viewers.comments.preview.inProgress')") +$jsonBuilder.addProperty('previewFailed', "$!adminMsg.get('core.viewers.comments.preview.failed')") +$jsonBuilder.addProperty('previewButtonBack', "$!adminMsg.get('core.viewers.comments.preview.button.back')") +$jsonBuilder.addProperty('extranb', "$!adminMsg.get('docextra.extranb', ['__number__'])") +$jsonBuilder.closeDictionary() +## xwiki/confirmationBox.js +$jsonBuilder.openProperty('confirmationBox') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('defaultQuestion', "$!adminMsg.get('core.widgets.confirmationBox.defaultQuestion')") +$jsonBuilder.addProperty('yes', "$!adminMsg.get('core.widgets.confirmationBox.button.yes')") +$jsonBuilder.addProperty('no', "$!adminMsg.get('core.widgets.confirmationBox.button.no')") +$jsonBuilder.closeDictionary() +## xwiki/confirmedAjaxRequest.js +$jsonBuilder.openProperty('confirmedAjaxRequest') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('notificationInProgress', "$!adminMsg.get('core.widgets.confirmationBox.notification.inProgress')") +$jsonBuilder.addProperty('notificationDone', "$!adminMsg.get('core.widgets.confirmationBox.notification.done')") +$jsonBuilder.addProperty('notificationFailed', "$!adminMsg.get('core.widgets.confirmationBox.notification.failed')") +$jsonBuilder.closeDictionary() +## xwiki/dataeditors.js +$jsonBuilder.openProperty('dataeditors') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('confirmationText', "$!adminMsg.get('core.editors.object.delete.confirm')") +$jsonBuilder.addProperty('progressMessageText', "$!adminMsg.get('core.editors.object.delete.inProgress')") +$jsonBuilder.addProperty('successMessageText', "$!adminMsg.get('core.editors.object.delete.done')") +$jsonBuilder.addProperty('failureMessageText', "$!adminMsg.get('core.editors.object.delete.failed')") +$jsonBuilder.closeDictionary() +## xwiki/livetable.js +$jsonBuilder.openProperty('livetable') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('resultsOf', "$!adminMsg.get('xe.pagination.results.of')") +$jsonBuilder.addProperty('results', "$!adminMsg.get('xe.pagination.results')") +$jsonBuilder.closeDictionary() +## xwiki/usersandgroups.js +$jsonBuilder.openProperty('usersandgroups') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('denyrightforcurrentuser', "$!adminMsg.get('rightsmanager.denyrightforcurrentuser')") +$jsonBuilder.addProperty('clearrightforcurrentuserinstead', "$!adminMsg.get('rightsmanager.clearrightforcurrentuserinstead')") +$jsonBuilder.addProperty('clearrightforcurrentuser', "$!adminMsg.get('rightsmanager.clearrightforcurrentuser')") +$jsonBuilder.addProperty('denyrightforgroup', "$!adminMsg.get('rightsmanager.denyrightforgroup')") +$jsonBuilder.addProperty('clearrightforgroupinstead', "$!adminMsg.get('rightsmanager.clearrightforgroupinstead')") +$jsonBuilder.addProperty('clearrightforgroup', "$!adminMsg.get('rightsmanager.clearrightforgroup')") +$jsonBuilder.addProperty('denyrightforuorg', "$!adminMsg.get('rightsmanager.denyrightforuorg')") +$jsonBuilder.addProperty('clearrightforuorg', "$!adminMsg.get('rightsmanager.clearrightforuorg')") +$jsonBuilder.addProperty('saveFailure', "$!adminMsg.get('platform.core.rightsManagement.saveFailure')") +$jsonBuilder.addProperty('ajaxFailure', "$!adminMsg.get('platform.core.rightsManagement.ajaxFailure')") +$jsonBuilder.addProperty('edit', "$!adminMsg.get('edit')") +$jsonBuilder.addProperty('delete', "$!adminMsg.get('delete')") +$jsonBuilder.addProperty('confirmdeleteuser', "$!adminMsg.get('rightsmanager.confirmdeleteuser')") +$jsonBuilder.addProperty('confirmdeletegroup', "$!adminMsg.get('rightsmanager.confirmdeletegroup')") +$jsonBuilder.addProperty('confirmdeletemember', "$!adminMsg.get('rightsmanager.confirmdeletemember')") +$jsonBuilder.closeDictionary() +## xwiki.js +$jsonBuilder.openProperty('xwiki') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('readTechnicalInformation', "$!adminMsg.get('platform.core.rendering.error.readTechnicalInformation')") +$jsonBuilder.addProperty('edit', "$!adminMsg.get('edit')") +$jsonBuilder.addProperty('createAjaxError', "$!adminMsg.get('core.create.ajax.error')") +$jsonBuilder.closeDictionary() +## xwikiexplorer.js +$jsonBuilder.openProperty('xwikiexplorer') +$jsonBuilder.openDictionary() +$jsonBuilder.addProperty('attachmentsTitle', "$!adminMsg.get('xwikiexplorer.attachments.title')") +$jsonBuilder.addProperty('addPageTitle', "$!adminMsg.get('xwikiexplorer.addpage.title')") +$jsonBuilder.addProperty('addAttachmentTitle', "$!adminMsg.get('xwikiexplorer.addattachment.title')") +$jsonBuilder.closeDictionary() diff --git a/celements-messages/web-module/src/main/webapp/templates/celMessages/validation/celements.vm b/celements-messages/web-module/src/main/webapp/templates/celMessages/validation/celements.vm new file mode 100644 index 00000000..36d571ea --- /dev/null +++ b/celements-messages/web-module/src/main/webapp/templates/celMessages/validation/celements.vm @@ -0,0 +1,19 @@ +$jsonBuilder.addProperty('language', "$language") +$jsonBuilder.addProperty('admin-language', "$admin_language") +$jsonBuilder.addProperty('required', "$adminMsg.get('cel_form_valid_required')") +$jsonBuilder.addProperty('validate-dateRange', "$!adminMsg.get('cel_valid_dateRange', ['', ''])") +$jsonBuilder.addProperty('validate-warning-header', "$!adminMsg.get('cel_tm_saving_dialog_warning_header')") +$jsonBuilder.addProperty('validate-blog-dateRange', "$!adminMsg.get('cel_valid_blog_dateRange', ['', ''])") +$jsonBuilder.addProperty('validate-number', "Please enter a valid number in this field.") +$jsonBuilder.addProperty('validate-digits', "Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.") +$jsonBuilder.addProperty('validate-alpha', "Please use letters only (a-z) in this field.") +$jsonBuilder.addProperty('validate-alphanum', "Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.") +$jsonBuilder.addProperty('validate-date', "Please enter a valid date.") +$jsonBuilder.addProperty('validate-email', "Please enter a valid email address. For example fred@domain.com .") +$jsonBuilder.addProperty('validate-url', "Please enter a valid URL.") +$jsonBuilder.addProperty('validate-date-au', "Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.") +$jsonBuilder.addProperty('validate-currency-dollar', 'Please enter a valid $ amount. For example $100.00 .') +$jsonBuilder.addProperty('validate-selection', 'Please make a selection') +$jsonBuilder.addProperty('validate-one-required', 'Please select one of the above options.') +$jsonBuilder.addProperty('validate-email-equal', "$!adminMsg.get('cel_form_valid_email-equal')") +$jsonBuilder.addProperty('validate-docname', "$adminMsg.get('cel_tm_validate_warning')")