/* * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.arsdigita.cms.ui.authoring; import java.io.File; import java.io.FileInputStream; import java.io.UnsupportedEncodingException; import org.apache.log4j.Logger; import com.arsdigita.bebop.BoxPanel; import com.arsdigita.bebop.ColumnPanel; import com.arsdigita.bebop.Form; import com.arsdigita.bebop.FormData; import com.arsdigita.bebop.FormProcessException; import com.arsdigita.bebop.Label; import com.arsdigita.bebop.Page; import com.arsdigita.bebop.PageState; import com.arsdigita.bebop.RequestLocal; import com.arsdigita.bebop.Resettable; import com.arsdigita.bebop.SaveCancelSection; import com.arsdigita.bebop.event.FormInitListener; import com.arsdigita.bebop.event.FormProcessListener; import com.arsdigita.bebop.event.FormSectionEvent; import com.arsdigita.bebop.event.FormSubmissionListener; import com.arsdigita.bebop.event.FormValidationListener; import com.arsdigita.bebop.event.RequestEvent; import com.arsdigita.bebop.event.RequestListener; import com.arsdigita.bebop.form.FormErrorDisplay; import com.arsdigita.bebop.form.SingleSelect; import com.arsdigita.bebop.form.TextArea; import com.arsdigita.bebop.parameters.NotNullValidationListener; import com.arsdigita.bebop.parameters.StringParameter; import com.arsdigita.cms.Asset; import com.arsdigita.cms.ContentSection; import com.arsdigita.cms.CMSConfig; import com.arsdigita.cms.ItemSelectionModel; import com.arsdigita.cms.TextAsset; import com.arsdigita.cms.ui.CMSDHTMLEditor; import com.arsdigita.cms.ui.ContentItemPage; import com.arsdigita.cms.ui.FileUploadSection; import com.arsdigita.cms.ui.SecurityPropertyEditor; import com.arsdigita.cms.util.GlobalizationUtil; import com.arsdigita.globalization.Globalization; import com.arsdigita.kernel.Kernel; import com.arsdigita.mimetypes.MimeType; import com.arsdigita.mimetypes.MimeTypeStatus; import com.arsdigita.mimetypes.TextMimeType; import com.arsdigita.mimetypes.converters.ConvertFormat; import com.arsdigita.toolbox.ui.DomainObjectPropertySheet; import com.arsdigita.util.UncheckedWrapperException; /** * Displays the mime-type and the body of a single {@link TextAsset}. Maintains * a form or uploading files into the text body of the asset, and a form for * editing the text of the asset. *
* Unlike most other authoring components, this component does not require
* the asset to exist. If the asset does not exist (i.e., if
* !m_assetModel.isSelected(state)), the upload and editing
* forms will create a new asset and set it in the model by calling
* setSelectedObject on the asset selection model. Child
* classes should override the {@link #createTextAsset(PageState)} method
* in to create a valid text asset.
*
* This component is used primarily in {@link GenericArticleBody} and
* {@link com.arsdigita.cms.ui.templates.TemplateBody}
*
* @author Stanislav Freidin (sfreidin@arsdigita.com)
* @version $Id: TextAssetBody.java 2167 2011-06-19 21:12:12Z pboy $
*/
public abstract class TextAssetBody extends SecurityPropertyEditor
implements Resettable, AuthoringStepComponent, RequestListener {
private static Logger s_log =
Logger.getLogger(TextAssetBody.class);
private ItemSelectionModel m_assetModel;
public static final String FILE_UPLOAD = "file";
public static final String TEXT_ENTRY = "text";
private StringParameter m_streamlinedCreationParam;
private static final String STREAMLINED = "_streamlined";
private static final String STREAMLINED_DONE = "1";
private static final CMSConfig s_config = new CMSConfig();
static {
s_log.debug("Static initializer is starting...");
s_config.load();
s_log.debug("Static initializer finished.");
}
/**
* Construct a new GenericArticleBody component
*
* @param assetModel The {@link ItemSelectionModel} which will
* be responsible for maintaining the current asset
*/
public TextAssetBody(ItemSelectionModel assetModel) {
this(assetModel, null);
}
/**
* Construct a new GenericArticleBody component
*
* @param assetModel The {@link ItemSelectionModel} which will
* be responsible for maintaining the current asset
* @param parent The parent wizard which contains the form. The form
* may use the wizard's methods, such as stepForward and stepBack,
* in its process listener.
*/
public TextAssetBody(ItemSelectionModel assetModel,
AuthoringKitWizard parent) {
super();
m_assetModel = assetModel;
m_streamlinedCreationParam =
new StringParameter(parent == null ? "item" : parent.getContentType().
getAssociatedObjectType() + "_body_done");
if (!s_config.getHideTextAssetUploadFile()) {
PageFileForm f = getPageFileForm();
addFileWidgets(f);
add(FILE_UPLOAD,
GlobalizationUtil.globalize("cms.ui.upload"),
f,
f.getSaveCancelSection().getCancelButton());
}
PageTextForm t = new PageTextForm();
addTextWidgets(t);
add(TEXT_ENTRY,
GlobalizationUtil.globalize("cms.ui.edit"),
t,
t.getSaveCancelSection().getCancelButton());
// Specify full path to properties of the text asset
DomainObjectPropertySheet sheet = getBodyPropertySheet(assetModel);
sheet.add(GlobalizationUtil.globalize("cms.ui.authoring.text_type"),
Asset.MIME_TYPE + "." + MimeType.LABEL);
sheet.add(GlobalizationUtil.globalize("cms.ui.authoring.body"),
TextAsset.CONTENT);
setDisplayComponent(sheet);
getDisplayPane().setClassAttr("invertedPropertyDisplay");
}
protected DomainObjectPropertySheet getBodyPropertySheet(
ItemSelectionModel assetModel) {
return new TextAssetBodyPropertySheet(assetModel);
}
/**
* Adds the options for the mime type select widget of
* GenericArticleForm and sets the default mime type.
**/
protected void setMimeTypeOptions(SingleSelect mimeSelect) {
FileUploadSection.addMimeOptions(mimeSelect, "text");
mimeSelect.setOptionSelected("text/html");
}
/**
* Get the current text asset, if any
* @param state represents the current request
*/
public TextAsset getTextAsset(PageState state) {
return (TextAsset) m_assetModel.getSelectedObject(state);
}
/**
* Reset this component to its original state
*
* @param s the current page state
*/
public void reset(PageState s) {
showDisplayPane(s);
}
// Create a text asset if it does not exist.
// This should probably be a method in GenericArticle ?
protected TextAsset createOrGetTextAsset(ItemSelectionModel assetModel,
PageState s) {
// Get the text asset or create a new one
TextAsset t = getTextAsset(s);
if (t == null) {
t = createTextAsset(s);
assetModel.setSelectedObject(s, t);
}
return t;
}
/**
* Create a brand new TextAsset. Child classes
* should override this method to do the right thing. The
* default implementation creates a parent-less TextAsset
* with a unique name.
*
* @param s the current page state
* @return a valid TextAsset
*/
protected abstract TextAsset createTextAsset(PageState s);
/**
* Set additional parameters of a brand new text asset, such as the
* parent ID, after the asset has been successfully uploaded
*
* @param s the current page state
* @param a the new TextAsset
*/
protected abstract void updateTextAsset(PageState s, TextAsset a);
/**
* Return the ItemSelectionModel which will be used
* to maintain the current text asset
*/
public ItemSelectionModel getAssetSelectionModel() {
return m_assetModel;
}
/**
* Forward to the next step if the streamlined creation parameter
* is turned on _and_ the streamlined_creation global state param
* is set to 'active'
*
* @param state the PageState
*/
protected void maybeForwardToNextStep(PageState state) {
if (ContentItemPage.isStreamlinedCreationActive(state) && !STREAMLINED_DONE.
equals(state.getValue(m_streamlinedCreationParam))) {
state.setValue(m_streamlinedCreationParam, STREAMLINED_DONE);
fireCompletionEvent(state);
}
}
/**
* Cancel streamlined creation for this step if the streamlined
* creation parameter is turned on _and_ the streamlined_creation
* global state param is set to 'active'
*
* @param state the PageState
*/
protected void cancelStreamlinedCreation(PageState state) {
if (ContentItemPage.isStreamlinedCreationActive(state)) {
state.setValue(m_streamlinedCreationParam, STREAMLINED_DONE);
}
}
/**
* Open the edit component if the streamlined
* creation parameter is turned on _and_ the streamlined_creation
* global state param is set to 'active'
*
* @param state the PageState
*/
public void pageRequested(RequestEvent e) {
PageState state = e.getPageState();
//if (getTextAsset(state) != null) {
// ComponentAccess ca = (ComponentAccess) getAccessMap().get(TEXT_ENTRY);
if (ContentItemPage.isStreamlinedCreationActive(state) && !STREAMLINED_DONE.
equals(state.getValue(m_streamlinedCreationParam))) {
showComponent(state, TEXT_ENTRY);
}
//}
}
/**
* This is the form that is used to upload files. This method can
* be used so that a subclass can use their own subclass of PageFileForm.
*/
protected PageFileForm getPageFileForm() {
return new PageFileForm();
}
/**
* A form for editing TextAsset items. Displays a "file upload"
* widget, auto-guesses mime type
*/
public class PageFileForm extends Form
implements FormProcessListener, FormValidationListener {
private SaveCancelSection m_saveCancelSection;
private FileUploadSection m_fileUploadSection;
// Variables saved by validate for processing
private RequestLocal m_file_upload_content;
private RequestLocal m_file_upload_usedINSO;
/**
* The text entry widget
*/
public static final String TEXT_ENTRY = "text_entry";
/**
* Construct a new PageFileForm
*/
public PageFileForm() {
super("PageFileUpload", new BoxPanel(BoxPanel.VERTICAL));
setMethod(Form.POST);
setEncType("multipart/form-data");
}
protected String getFileUploadContent(PageState state) {
return (String) m_file_upload_content.get(state);
}
/**
* Make sure that files of this type can be uploaded
*/
private void validateFileType(MimeType mime, boolean textType)
throws FormProcessException {
boolean validType = textType || ((mime instanceof TextMimeType) && ((TextMimeType) mime).
allowINSOConvert().booleanValue());
if (!validType) {
throw new FormProcessException("Cannot load " + "files of type " + mime.
getMimeType() + " into the article body.");
}
boolean insoWorks = MimeTypeStatus.getMimeTypeStatus().
getInsoFilterWorks().intValue() == 1;
if (!textType && !insoWorks) {
// Can't convert. inso filter is not working. Give message.
throw new FormProcessException(
"Could not convert to html "
+ "format because interMedia INSO filter is not installed.");
}
}
/**
* read in the content of the file (in bytes).
*/
private byte[] readFileBytes(File file) throws FormProcessException {
byte[] file_bytes;
try {
FileInputStream fs = new FileInputStream(file);
file_bytes = new byte[fs.available()];
fs.read(file_bytes);
fs.close();
} catch (Exception e) {
throw new FormProcessException((String) GlobalizationUtil.
globalize("cms.ui.authoring.unable_to_load_file").
localize() + e.getMessage());
}
return file_bytes;
}
/**
* Convert bytes to String, possibly using INSO filter to convert to
* HTML type
*/
private String convertBytes(byte[] file_bytes, boolean text_type,
boolean[] used_inso)
throws FormProcessException {
String file_content;
// If mime type is not text type, try to convert to html
if (!text_type) {
file_content = ConvertFormat.toHTML(file_bytes);
if (file_content != null) {
// Converted successfully, flag type should be html
used_inso[0] = true;
} else {
throw new FormProcessException(
"Could not convert to html format. "
+ "interMedia INSO filter conversion failed.");
}
} else {
// Text type, no need to convert
String enc = Globalization.getDefaultCharset(Kernel.getContext().
getLocale());
try {
file_content = new String(file_bytes, enc);
} catch (UnsupportedEncodingException ex) {
throw new UncheckedWrapperException("cannot convert to encoding "
+ enc, ex);
}
used_inso[0] = false;
}
return file_content;
}
/**
* Extract the contents of the html Body tag.
* (Done to prevent base and other header tags
* from interfering with page display).
*/
private String extractHTMLBody(String htmlText)
throws FormProcessException {
String lc = htmlText.toLowerCase();
int bodyStart = lc.indexOf("