/* * ContentEditFormHandler.java * * This work is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This work is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * Copyright (c) 2004-2005 Per Cederberg. All rights reserved. */ package org.liquidsite.app.admin; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.Iterator; import java.util.Map; import org.liquidsite.app.admin.view.AdminView; import org.liquidsite.core.content.Content; import org.liquidsite.core.content.ContentDocument; import org.liquidsite.core.content.ContentException; import org.liquidsite.core.content.ContentFile; import org.liquidsite.core.content.ContentForum; import org.liquidsite.core.content.ContentManager; import org.liquidsite.core.content.ContentPost; import org.liquidsite.core.content.ContentSection; import org.liquidsite.core.content.ContentSecurityException; import org.liquidsite.core.content.ContentTopic; import org.liquidsite.core.content.DocumentProperty; import org.liquidsite.core.text.HtmlFormatter; import org.liquidsite.core.text.PlainFormatter; import org.liquidsite.core.text.TaggedFormatter; import org.liquidsite.core.web.FormHandlingException; import org.liquidsite.core.web.FormValidationException; import org.liquidsite.core.web.FormValidator; import org.liquidsite.core.web.Request; import org.liquidsite.core.web.Request.FileParameter; import org.liquidsite.core.web.RequestSession; /** * The content edit request handler. This class handles the edit * workflow for the content view. * * @author Per Cederberg, <per at percederberg dot net> * @version 1.0 */ public class ContentEditFormHandler extends AdminFormHandler { /*** * The latest object instance created. */ private static ContentEditFormHandler instance = null; /** * The section form validator. */ private FormValidator sectionValidator = new FormValidator(); /** * The document form validator. */ private FormValidator documentValidator = new FormValidator(); /** * The forum form validator. */ private FormValidator forumValidator = new FormValidator(); /** * The topic form validator. */ private FormValidator topicValidator = new FormValidator(); /** * The post form validator. */ private FormValidator postValidator = new FormValidator(); /** * Returns an instance of this class. If a prior instance has * been created, it will be returned instead of creating a new * one. * * @return an instance of a content edit form handler */ public static ContentEditFormHandler getInstance() { if (instance == null) { return new ContentEditFormHandler(); } else { return instance; } } /** * Creates a new content edit request handler. */ public ContentEditFormHandler() { super("content.html", "edit-content.html", true); instance = this; initialize(); } /** * Initializes all the form validators. */ private void initialize() { String error; // Add and edit section validator error = "No section name specified"; sectionValidator.addRequiredConstraint("name", error); error = "Section name contains invalid character"; sectionValidator.addCharacterConstraint("name", Content.NAME_CHARS, error); // Add and edit document validator error = "No document name specified"; documentValidator.addRequiredConstraint("name", error); error = "Document name contains invalid character"; documentValidator.addCharacterConstraint("name", Content.NAME_CHARS, error); // Add and edit forum validator error = "No forum name specified"; forumValidator.addRequiredConstraint("name", error); error = "Forum name contains invalid character"; forumValidator.addCharacterConstraint("name", Content.NAME_CHARS, error); error = "No real forum name specified"; forumValidator.addRequiredConstraint("realname", error); error = "No forum description specified"; forumValidator.addRequiredConstraint("description", error); // Add and edit topic validator error = "No topic subject specified"; topicValidator.addRequiredConstraint("subject", error); // Add and edit post validator error = "No post subject specified"; postValidator.addRequiredConstraint("subject", error); error = "No post text specified"; postValidator.addRequiredConstraint("text", error); } /** * Displays a form for the specified workflow step. This method * will NOT be called when returning to the start page. * * @param request the request object * @param step the workflow step * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ protected void displayStep(Request request, int step) throws ContentException, ContentSecurityException { Object ref = AdminUtils.getReference(request); if (ref instanceof ContentSection) { AdminView.CONTENT.viewEditSection(request, null, (ContentSection) ref); } else if (ref instanceof ContentDocument) { AdminView.CONTENT.viewEditDocument(request, (ContentDocument) ref); } else if (ref instanceof ContentFile) { AdminView.CONTENT.viewEditFile(request, (ContentFile) ref); } else if (ref instanceof ContentForum) { AdminView.CONTENT.viewEditForum(request, (ContentForum) ref); } else if (ref instanceof ContentTopic) { AdminView.CONTENT.viewEditTopic(request, (ContentTopic) ref); } else if (ref instanceof ContentPost) { AdminView.CONTENT.viewEditPost(request, (ContentPost) ref); } else { throw new ContentException("cannot edit this object"); } } /** * Validates a form for the specified workflow step. If the form * validation fails in this step, the form page for the workflow * step will be displayed again with an 'error' attribute * containing the message in the validation exception. * * @param request the request object * @param step the workflow step * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions * @throws FormValidationException if the form request data * validation failed */ protected void validateStep(Request request, int step) throws ContentException, ContentSecurityException, FormValidationException { String category; String action; String message; category = request.getParameter("category", ""); if (category.equals("section")) { sectionValidator.validate(request); message = "Another object with identical name already " + "exists in the parent section or domain"; validateParent(request, "parent", message); } else if (category.equals("document")) { action = request.getParameter("action", ""); if (action.equals("reload") || action.equals("filedelete")) { // No validation needed } else if (action.equals("upload")) { validateUpload(request); } else { validateDocument(request); } } else if (category.equals("file")) { validateFile(request); } else if (category.equals("forum")) { forumValidator.validate(request); message = "Another object with identical name already " + "exists in the parent section"; validateParent(request, "section", message); } else if (category.equals("topic")) { topicValidator.validate(request); } else if (category.equals("post")) { postValidator.validate(request); } else { message = "Unknown content category specified"; throw new FormValidationException("category", message); } } /** * Validates a content document add or edit form. * * @param request the request object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions * @throws FormValidationException if the form request data * validation failed */ private void validateDocument(Request request) throws ContentException, ContentSecurityException, FormValidationException { String str; documentValidator.validate(request); str = "Another object with identical name already exists in " + "the parent section"; validateParent(request, "section", str); } /** * Validates a content file edit form. * * @param request the request object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions * @throws FormValidationException if the form request data * validation failed */ private void validateFile(Request request) throws ContentException, ContentSecurityException, FormValidationException { ContentManager manager = AdminUtils.getContentManager(); Content content; String name; int id; String message; name = request.getParameter("name"); if (name == null || name.equals("")) { message = "No file name specified"; throw new FormValidationException("name", message); } for (int i = 0; i < name.length(); i++) { if (Content.NAME_CHARS.indexOf(name.charAt(i)) < 0) { message = "File name contains invalid character: " + "'" + name.charAt(i) + "'"; throw new FormValidationException("name", message); } } content = (Content) AdminUtils.getReference(request); id = content.getId(); content = manager.getContentChild(request.getUser(), content.getParent(), name); if (content != null && content.getId() != id) { message = "Another file with identical name is already " + "attached to the parent document"; throw new FormValidationException("name", message); } } /** * Validates a document file upload. * * @param request the request object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions * @throws FormValidationException if the form request data * validation failed */ private void validateUpload(Request request) throws ContentException, ContentSecurityException, FormValidationException { ContentManager manager = AdminUtils.getContentManager(); RequestSession session = request.getSession(); FileParameter param; String name; Content content; String message; param = request.getFileParameter("upload"); if (param == null || param.getSize() <= 0) { message = "No file to add specified"; throw new FormValidationException("upload", message); } name = convertFileName(param.getName()); if (session.getFile(name) != null) { message = "Another file named '" + name + "' already exists"; throw new FormValidationException("upload", message); } content = (Content) AdminUtils.getReference(request); if (content instanceof ContentDocument) { content = manager.getContentChild(request.getUser(), content, name); if (content != null) { message = "Another file named '" + name + "' already exists"; throw new FormValidationException("upload", message); } } } /** * Handles a validated form for the specified workflow step. This * method returns the next workflow step, i.e. the step used when * calling the display method. If the special zero (0) workflow * step is returned, the workflow is assumed to have terminated. * Note that this method also allows additional validation to * occur. By returning the incoming workflow step number and * setting the appropriate request attributes the same results as * in the normal validate method can be achieved. For recoverable * errors, this is the recommended course of action. * * @param request the request object * @param step the workflow step * * @return the next workflow step, or * zero (0) if the workflow has finished * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ protected int handleStep(Request request, int step) throws ContentException, ContentSecurityException { Object ref = AdminUtils.getReference(request); String action; if (ref instanceof ContentSection) { handleEditSection(request, (ContentSection) ref); } else if (ref instanceof ContentDocument) { action = request.getParameter("action", ""); if (action.equals("reload")) { return step; } else if (action.equals("upload")) { handleFileUpload(request); return step; } else if (action.equals("filedelete")) { handleFileRemoval(request); return step; } else { handleEditDocument(request, (ContentDocument) ref); } } else if (ref instanceof ContentFile) { handleEditFile(request, (ContentFile) ref); } else if (ref instanceof ContentForum) { handleEditForum(request, (ContentForum) ref); } else if (ref instanceof ContentTopic) { handleEditTopic(request, (ContentTopic) ref); } else if (ref instanceof ContentPost) { handleEditPost(request, (ContentPost) ref); } else { throw new ContentException("cannot edit this object"); } return 0; } /** * Handles the edit section form. * * @param request the request object * @param section the section content object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ private void handleEditSection(Request request, ContentSection section) throws ContentException, ContentSecurityException { Map params = request.getAllParameters(); Iterator iter = params.keySet().iterator(); DocumentProperty[] properties; DocumentProperty property; String name; String id; String str; int parent; section.setRevisionNumber(0); section.setName(request.getParameter("name")); section.setDescription(request.getParameter("description")); section.setComment(request.getParameter("comment")); try { parent = Integer.parseInt(request.getParameter("parent")); section.setParentId(parent); } catch (NumberFormatException ignore) { // This is ignored } while (iter.hasNext()) { name = iter.next().toString(); if (name.startsWith("property.") && name.endsWith(".position")) { name = name.substring(0, name.length() - 9); id = name.substring(9); property = new DocumentProperty(id); str = request.getParameter(name + ".name", id); property.setName(str); try { str = request.getParameter(name + ".position", "1"); property.setPosition(Integer.parseInt(str)); } catch (NumberFormatException ignore) { // This is ignored } try { str = request.getParameter(name + ".type", "1"); property.setType(Integer.parseInt(str)); } catch (NumberFormatException ignore) { // This is ignored } str = request.getParameter(name + ".description", ""); property.setDescription(str); section.setDocumentProperty(id, property); } } properties = section.getAllDocumentProperties(); for (int i = 0; i < properties.length; i++) { id = properties[i].getId(); if (!params.containsKey("property." + id + ".position")) { section.setDocumentProperty(id, null); } } if (request.getParameter("action", "").equals("publish")) { section.setRevisionNumber(section.getMaxRevisionNumber() + 1); section.setOnlineDate(new Date()); section.setOfflineDate(null); } section.save(request.getUser()); } /** * Handles the edit document form. * * @param request the request object * @param doc the document content object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ private void handleEditDocument(Request request, ContentDocument doc) throws ContentException, ContentSecurityException { Map params = request.getAllParameters(); Iterator iter = params.keySet().iterator(); int section; String id; int type; String str; doc.setRevisionNumber(0); doc.setName(request.getParameter("name")); doc.setComment(request.getParameter("comment")); try { section = Integer.parseInt(request.getParameter("section")); doc.setParentId(section); } catch (NumberFormatException ignore) { // This is ignored } while (iter.hasNext()) { str = iter.next().toString(); if (str.startsWith("property.")) { id = str.substring(9); try { str = request.getParameter("propertytype." + id); type = Integer.parseInt(str); } catch (NumberFormatException e) { type = DocumentProperty.STRING_TYPE; } doc.setPropertyType(id, type); str = request.getParameter("property." + id); if (type == DocumentProperty.STRING_TYPE) { str = PlainFormatter.clean(str); } else if (type == DocumentProperty.TAGGED_TYPE) { str = TaggedFormatter.clean(str); } else if (type == DocumentProperty.HTML_TYPE) { str = HtmlFormatter.clean(str); } doc.setProperty(id, str); } } iter = doc.getPropertyIdentifiers().iterator(); while (iter.hasNext()) { id = iter.next().toString(); if (!params.containsKey("property." + id)) { doc.setProperty(id, null); } } if (request.getParameter("action", "").equals("publish")) { doc.setRevisionNumber(doc.getMaxRevisionNumber() + 1); doc.setOnlineDate(new Date()); doc.setOfflineDate(null); } doc.save(request.getUser()); handleAddDocumentFiles(request, doc); } /** * Handles the edit file form. * * @param request the request object * @param file the file content object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ private void handleEditFile(Request request, ContentFile file) throws ContentException, ContentSecurityException { FileParameter param; String name; try { file.setRevisionNumber(0); file.setName(request.getParameter("name")); file.setComment(request.getParameter("comment")); param = request.getFileParameter("upload"); if (param != null && param.getSize() > 0) { file.setFileName(param.getName()); param.write(file.getFile()); } else if (request.getParameter("content") != null) { name = file.getFileName(); name = name.substring(name.indexOf(".") + 1); if (name.indexOf(".") <= 0) { file.setFileName(file.getFileName()); } else { file.setFileName(name); } file.setTextContent(request.getParameter("content")); } if (request.getParameter("action", "").equals("publish")) { file.setRevisionNumber(file.getMaxRevisionNumber() + 1); file.setOnlineDate(new Date()); file.setOfflineDate(null); } file.save(request.getUser()); } catch (IOException e) { throw new ContentException(e.getMessage()); } } /** * Handles the edit forum form. * * @param request the request object * @param forum the forum content object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ private void handleEditForum(Request request, ContentForum forum) throws ContentException, ContentSecurityException { int section; forum.setRevisionNumber(0); forum.setName(request.getParameter("name")); forum.setRealName(request.getParameter("realname")); forum.setDescription(request.getParameter("description")); forum.setModeratorName(request.getParameter("moderator")); forum.setComment(request.getParameter("comment")); try { section = Integer.parseInt(request.getParameter("section")); forum.setParentId(section); } catch (NumberFormatException ignore) { // This is ignored } if (request.getParameter("action", "").equals("publish")) { forum.setRevisionNumber(forum.getMaxRevisionNumber() + 1); forum.setOnlineDate(new Date()); forum.setOfflineDate(null); } forum.save(request.getUser()); } /** * Handles the edit topic form. * * @param request the request object * @param topic the topic content object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ private void handleEditTopic(Request request, ContentTopic topic) throws ContentException, ContentSecurityException { int forum; topic.setRevisionNumber(0); topic.setSubject(request.getParameter("subject")); topic.setLocked(request.getParameter("locked", "").equals("true")); topic.setComment(request.getParameter("comment")); try { forum = Integer.parseInt(request.getParameter("forum")); topic.setParentId(forum); } catch (NumberFormatException ignore) { // This is ignored } if (request.getParameter("action", "").equals("publish")) { topic.setRevisionNumber(topic.getMaxRevisionNumber() + 1); topic.setOnlineDate(new Date()); topic.setOfflineDate(null); } topic.save(request.getUser()); } /** * Handles the edit post form. * * @param request the request object * @param post the post content object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ private void handleEditPost(Request request, ContentPost post) throws ContentException, ContentSecurityException { post.setRevisionNumber(0); post.setSubject(request.getParameter("subject")); post.setTextType(ContentPost.PLAIN_TEXT_TYPE); post.setText(request.getParameter("text")); post.setComment(request.getParameter("comment")); if (request.getParameter("action", "").equals("publish")) { post.setRevisionNumber(post.getMaxRevisionNumber() + 1); post.setOnlineDate(new Date()); post.setOfflineDate(null); } post.save(request.getUser()); } /** * Handles a file upload (in the document editing form). The file * will be added to the request session. * * @param request the request object * * @throws ContentException if the file couldn't be added to the * session correctly */ public void handleFileUpload(Request request) throws ContentException { FileParameter param; String name; File file; try { param = request.getFileParameter("upload"); name = convertFileName(param.getName()); file = param.write(); request.getSession().addFile(name, file); } catch (IOException e) { throw new ContentException(e.getMessage()); } } /** * Handles a file removal (in the document editing form). The * file will either be removed from the request session or * deleted as a content object. * * @param request the request object */ public void handleFileRemoval(Request request) { request.getSession().removeFile(request.getParameter("filename")); } /** * Handles adding all session files to a document. * * @param request the request object * @param doc the document content object * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ public void handleAddDocumentFiles(Request request, ContentDocument doc) throws ContentException, ContentSecurityException { ContentManager manager = AdminUtils.getContentManager(); RequestSession session = request.getSession(); ContentFile file; Iterator iter; String name; File data; String error; iter = session.getAllFiles().keySet().iterator(); while (iter.hasNext()) { name = (String) iter.next(); data = session.getFile(name); file = new ContentFile(manager, doc, name); file.setName(name); file.setComment(doc.getComment()); file.setRevisionNumber(1); file.setOnlineDate(new Date()); file.save(request.getUser()); try { data.renameTo(file.getFile()); } catch (Exception e) { error = "couldn't move session file " + data + " to " + file.getFile() + ": " + e.getMessage(); throw new ContentException(error); } } session.removeAllFiles(); } /** * This method removes any files attached to the user session. It * also calls the superclass implementation to unlock any locked * objects. * * @param request the request object * * @throws FormHandlingException if an error was encountered * while processing the form */ protected void workflowExited(Request request) throws FormHandlingException { super.workflowExited(request); request.getSession().removeAllFiles(); } }