/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.riotfamily.components.editor; import java.io.StringWriter; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.directwebremoting.Browser; import org.directwebremoting.ScriptSession; import org.directwebremoting.ScriptSessionFilter; import org.directwebremoting.ScriptSessions; import org.directwebremoting.WebContext; import org.directwebremoting.WebContextFactory; import org.directwebremoting.annotations.RemoteMethod; import org.directwebremoting.annotations.RemoteProxy; import org.riotfamily.common.util.Generics; import org.riotfamily.common.web.support.CapturingResponseWrapper; import org.riotfamily.components.config.ContentFormRepository; import org.riotfamily.components.meta.ComponentMetaData; import org.riotfamily.components.meta.ComponentMetaDataProvider; import org.riotfamily.components.model.Component; import org.riotfamily.components.model.ComponentList; import org.riotfamily.components.model.Content; import org.riotfamily.components.model.ContentContainer; import org.riotfamily.components.model.ContentFragment; import org.riotfamily.components.model.ContentMap; import org.riotfamily.components.render.component.ComponentRenderer; import org.riotfamily.components.render.component.EditModeComponentRenderer; import org.riotfamily.components.support.OverrideMethodRequestWrapper; import org.riotfamily.core.security.AccessController; import org.riotfamily.core.security.auth.RiotUser; import org.riotfamily.core.security.session.LoginManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import org.springframework.web.servlet.support.RequestContextUtils; /** * Service bean to edit ComponentLists and ComponentVersions. */ @Transactional @RemoteProxy(name="ComponentEditor") public class ComponentEditorImpl implements ComponentEditor, MessageSourceAware { private Logger log = LoggerFactory.getLogger(ComponentEditorImpl.class); private ComponentRenderer renderer; private ComponentMetaDataProvider metaDataProvider; private Map<String, Map<String, Object>> tinyMCEProfiles; private MessageSource messageSource; public ComponentEditorImpl(ComponentRenderer renderer, ComponentMetaDataProvider metaDataProvider, ContentFormRepository formRepository) { this.renderer = new EditModeComponentRenderer(renderer, metaDataProvider, formRepository); this.metaDataProvider = metaDataProvider; } public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } @RemoteMethod public Map<String, Map<String, Object>> getTinyMCEProfiles() { return this.tinyMCEProfiles; } public void setTinyMCEProfiles(Map<String, Map<String, Object>> tinyMCEProfiles) { this.tinyMCEProfiles = tinyMCEProfiles; } /** * Returns the value of the given property. */ @RemoteMethod public String getText(String contentId, String property) { ContentMap content = Content.loadFragment(contentId); Object value = content.get(property); return value != null ? value.toString() : null; } /** * Sets the given property to a new value. */ @RemoteMethod public void updateText(String contentId, String property, String text) { ContentMap content = Content.loadFragment(contentId); assertIsEditGranted(content); content.put(property, text); nofifyUsers(); } /** * */ @RemoteMethod public String[] updateTextChunks(String componentId, String property, String[] chunks) { String[] html = new String[chunks.length]; Component component = Component.load(componentId); assertIsEditGranted(component); component.put(property, chunks[0]); html[0] = renderComponent(component); String type = component.getType(); ComponentList list = component.getList(); int offset = list.indexOf(component); for (int i = 1; i < chunks.length; i++) { component = createComponent(list, type); list.add(offset + i, component); component.put(property, chunks[i]); html[i] = renderComponent(component); } nofifyUsers(); return html; } @RemoteMethod public List<ComponentMetaData> getComponentMetaData(String[] types) { List<ComponentMetaData> result = Generics.newArrayList(); for (int i = 0; i < types.length; i++) { result.add(metaDataProvider.getMetaData(types[i])); } return result; } /** * Creates a new Component and inserts it in the list identified * by the given id. */ @RemoteMethod public String insertComponent(String listId, int position, String type) { Assert.notNull(listId, "listId must not be null"); Assert.notNull(type, "type must not be null"); ComponentList componentList = ComponentList.load(listId); assertIsEditGranted(componentList); Component component = createComponent(componentList, type); componentList.add(position, component); nofifyUsers(); return renderComponent(component); } /** * Creates a new Component of the given type. * * @param type The type of the version to create * @return The newly created component */ private Component createComponent(ComponentList list, String type) { Component component = new Component(list); component.setType(type); Map<String, Object> defaults = metaDataProvider.getMetaData(type).getDefaults(); if (defaults != null) { component.putAll(defaults); } return component; } @RemoteMethod public String setType(String componentId, String type) { Assert.notNull(componentId, "componentId must not be null"); Assert.notNull(type, "type must not be null"); Component component = Component.load(componentId); assertIsEditGranted(component); component.setType(type); return renderComponent(component); } @RemoteMethod public String renderComponent(String componentId) { return renderComponent(Component.load(componentId)); } private String renderComponent(Component component) { try { StringWriter sw = new StringWriter(); HttpServletRequest request = new OverrideMethodRequestWrapper( WebContextFactory.get().getHttpServletRequest()) .setMethod("GET"); HttpServletResponse response = getCapturingResponse(sw); renderer.render(component, request, response); return sw.toString(); } catch (Exception e) { log.error("Error rendering component.", e); throw new RuntimeException(e); } } @RemoteMethod public void moveComponent(String componentId, String prevComponentId) { Component component = Component.load(componentId); assertIsEditGranted(component); component.move(prevComponentId); nofifyUsers(); } @RemoteMethod public void deleteComponent(String componentId) { Component component = Component.load(componentId); assertIsEditGranted(component); component.delete(); nofifyUsers(); } @RemoteMethod public ToolbarState getState(Long[] containerIds) { initScriptSession(); HttpServletRequest request = WebContextFactory.get().getHttpServletRequest(); ToolbarState state = new ToolbarState(); if (containerIds != null) { for (Long id : containerIds) { if (id != null) { ContentContainer container = ContentContainer.load(id); if (AccessController.isGranted("edit", container.getOwner(), request)) { state.setEdit(true); } if (AccessController.isGranted("publish", container.getOwner(), request)) { state.getContainerIds().add(container.getId()); if (container.isDirty()) { state.setDirty(true); } } } } } return state; } @RemoteMethod public void publish(Long[] containerIds) { if (containerIds != null) { for (Long id : Generics.newHashSet(Arrays.asList(containerIds))) { if (id != null) { ContentContainer container = ContentContainer.load(id); assertIsPublishGranted(container); container.publish(); } } } nofifyUsers(); } @RemoteMethod public void discard(Long[] containerIds) { if (containerIds != null) { for (Long id : Generics.newHashSet(Arrays.asList(containerIds))) { if (id != null) { ContentContainer container = ContentContainer.load(id); assertIsEditGranted(container); container.discard(); } } } nofifyUsers(); } /** * Performs a logout. */ @RemoteMethod public void logout() { WebContext ctx = WebContextFactory.get(); LoginManager.logout(ctx.getHttpServletRequest(), ctx.getHttpServletResponse()); } /* Utility methods */ private void assertIsEditGranted(ContentFragment fragment) { assertIsEditGranted(fragment.getContent().getContainer()); } private void assertIsEditGranted(ContentContainer container) { AccessController.assertIsGranted("edit", container.getOwner(), WebContextFactory.get().getHttpServletRequest()); } private void assertIsPublishGranted(ContentContainer container) { AccessController.assertIsGranted("publish", container.getOwner(), WebContextFactory.get().getHttpServletRequest()); } private HttpServletResponse getCapturingResponse(StringWriter sw) { WebContext ctx = WebContextFactory.get(); return new CapturingResponseWrapper(ctx.getHttpServletResponse(), sw); } private void initScriptSession() { WebContext webContext = WebContextFactory.get(); HttpServletRequest request = webContext.getHttpServletRequest(); ScriptSession currentSession = webContext.getScriptSession(); String host = request.getServerName(); RiotUser user = AccessController.getCurrentUser(); currentSession.setAttribute("host", host); currentSession.setAttribute("userId", user.getUserId()); } private void nofifyUsers() { WebContext webContext = WebContextFactory.get(); HttpServletRequest request = webContext.getHttpServletRequest(); final ScriptSession currentSession = webContext.getScriptSession(); final RiotUser user = AccessController.getCurrentUser(); final String host = request.getServerName(); String userName = ""; if (user.getName() != null) { userName = " (" + user.getName() + ")"; } Locale locale = RequestContextUtils.getLocale(request); final String message = messageSource.getMessage( "components.concurrentModification", new Object[] { userName, "javascript:location.reload()" }, "The page has been modified by another user{0}. Please " + "<a href=\"{1}\">reload</a> the page in order " + "to see the changes.", locale); Browser.withAllSessionsFiltered( new ScriptSessionFilter() { public boolean match(ScriptSession session) { return !user.getUserId().equals(session.getAttribute("userId")) && currentSession.getPage().equals(session.getPage()) && host.equals(session.getAttribute("host")); } }, new Runnable() { public void run() { ScriptSessions.addFunctionCall("riot.showNotification", message); } }); } }