View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.fckeditor.dialogs;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.gui.control.ControlImpl;
38  import info.magnolia.cms.gui.dialog.DialogBox;
39  import info.magnolia.cms.link.LinkHelper;
40  import info.magnolia.context.MgnlContext;
41  import info.magnolia.link.EditorLinkTransformer;
42  import info.magnolia.link.LinkException;
43  import info.magnolia.link.LinkTransformerManager;
44  import info.magnolia.link.LinkUtil;
45  import org.apache.commons.lang.StringEscapeUtils;
46  import org.apache.commons.lang.StringUtils;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  import javax.jcr.RepositoryException;
51  import javax.servlet.http.HttpServletRequest;
52  import javax.servlet.http.HttpServletResponse;
53  import java.io.IOException;
54  import java.io.Writer;
55  import java.util.regex.Matcher;
56  
57  
58  /**
59   *
60   * An Magnolia dialog for the universal usage and configuration of the fckeditor. Credits for FCKEditor:
61   * http://www.fckeditor.net/<p/> The fckEditor is mainly configured in javascript files. Those files are configured
62   * with the following attributes.
63   * <ul>
64   * <li>jsInitFile</li>
65   * <li>customConfigurationPath</li>
66   * </ul>
67   * Where the default values are:
68   * <ul>
69   * <li>/admindocroot/fckeditor/custom/init/magnoliaStandard.js</li>
70   * <li>/admindocroot/fckeditor/custom/config/magnoliaStandard.js</li>
71   * </ul>
72   * To make live simple we provide some attributes to configure the control in the magnolia configuration instead within
73   * the javascript files. <table>
74   * <tr>
75   * <td>css</td>
76   * <td>The css file to use. Default is /admindocroot/fckeditor/custom/css/magnoliaStandard.css </td>
77   * </tr>
78   * <tr>
79   * <td>height</td>
80   * <td>The height of the editor.</td>
81   * </tr>
82   * <tr>
83   * <td>width</td>
84   * <td>The width of the editor.</td>
85   * </tr>
86   * <tr>
87   * <td>tables</td>
88   * <td>The table editing features are available if true</td>
89   * </tr>
90   * <tr>
91   * <td>lists</td>
92   * <td>The list features are available if true</td>
93   * </tr>
94   * <tr>
95   * <td>aligment</td>
96   * <td>The aligment features are available if true</td>
97   * </tr>
98   * <tr>
99   * <td>images</td>
100  * <td>The image editing features including upload are available if true</td>
101  * </tr>
102  * <tr>
103  * <td>fileUpload</td>
104  * <td>The file upload features is enabled if true</td>
105  * </tr>
106  * <tr>
107  * <td>styles</td>
108  * <td>Defines the xml file defining the used styles. See
109  * http://wiki.fckeditor.net/Developer%27s_Guide/Configuration/Styles for details</td>
110  * </tr>
111  * <tr>
112  * <td>templates</td>
113  * <td>Defines the xml file defining the used templates. See
114  * http://wiki.fckeditor.net/Developer%27s_Guide/Configuration/Templates for details</td>
115  * </tr>
116  * <tr>
117  * <td>fonts</td>
118  * <td>A semicolon separated list of font names.</td>
119  * </tr>
120  * <tr>
121  * <td>fontSizes</td>
122  * <td>A semicolon separated list of font sizes</td>
123  * </tr>
124  * <tr>
125  * <tr>
126  * <td>colors</td>
127  * <td>A comma separated list of colors. hex values without #.</td>
128  * </tr>
129  * <tr>
130  * <td>source</td>
131  * <td>Show the source button</td>
132  * </tr>
133  * </table>
134  *
135  * @author bert schulzki
136  * @author Fabrizio Giustina
137  * @version $Id: FckEditorDialog.java 32667 2010-03-13 00:37:06Z gjoseph $
138  */
139 public class FckEditorDialog extends DialogBox {
140     private static final Logger log = LoggerFactory.getLogger(FckEditorDialog.class);
141 
142     /**
143      * The new .BasePath of the editor
144      */
145     public static final String FCKEDIT_PATH = "/.resources/fckeditor/"; //$NON-NLS-1$
146 
147     /**
148      * Used to make sure that the javascript files are loaded only once
149      */
150     private static final String ATTRIBUTE_FCKED_LOADED = "info.magnolia.cms.gui.dialog.fckedit.loaded";
151 
152     /**
153      * This parameter defines the startup script. This parameter is searched in the dialog configuration.
154      */
155     public static final String PARAM_JS_INIT_FILE = "jsInitFile"; //$NON-NLS-1$
156 
157     /**
158      * This parameter defines the configuration script
159      */
160     public static final String PARAM_CUSTOM_CONFIGURATION_PATH = "jsConfigFile"; //$NON-NLS-1$
161 
162     public static final String PARAM_CSS = "css"; //$NON-NLS-1$
163 
164     public static final String PARAM_HEIGHT = "height"; //$NON-NLS-1$
165 
166     public static final String PARAM_WIDTH = "width"; //$NON-NLS-1$
167 
168     public static final String PARAM_TABLES = "tables"; //$NON-NLS-1$
169 
170     private static final String PARAM_LISTS = "lists";
171 
172     private static final String PARAM_ALIGNMENT = "alignment";
173 
174     public static final String PARAM_IMAGES = "images"; //$NON-NLS-1$
175 
176     public static final String PARAM_STYLES = "styles"; //$NON-NLS-1$
177 
178     public static final String PARAM_TEMPLATES = "templates"; //$NON-NLS-1$
179 
180     public static final String PARAM_FONTS = "fonts"; //$NON-NLS-1$
181 
182     public static final String PARAM_FONT_SIZES = "fontSizes"; //$NON-NLS-1$
183 
184     private static final String PARAM_COLORS = "colors";
185 
186     public static final String PARAM_SOURCE = "source"; //$NON-NLS-1$
187 
188     private static final String PARAM_ENTER_MODE = "enterMode";
189 
190     private static final String PARAM_SHIFT_ENTER_MODE = "shiftEnterMode";
191 
192     /**
193      * Default values
194      */
195     public static final String PARAM_JS_INIT_FILE_DEFAULT = "/.resources/fckeditor/custom/init/magnoliaStandard.js"; //$NON-NLS-1$
196 
197     public static final String PARAM_CUSTOM_CONFIGURATION_PATH_DEFAULT = "/.resources/fckeditor/custom/config/magnoliaStandard.js"; //$NON-NLS-1$
198 
199     public static final String PARAM_CSS_DEFAULT = "/.resources/fckeditor/custom/css/magnoliaStandard.css"; //$NON-NLS-1$
200 
201     public static final String PARAM_HEIGHT_DEFAULT = ""; //$NON-NLS-1$
202 
203     public static final String PARAM_WIDTH_DEFAULT = ""; //$NON-NLS-1$
204 
205     public static final String PARAM_TABLES_DEFAULT = "false"; //$NON-NLS-1$
206 
207     public static final String PARAM_IMAGES_DEFAULT = "false"; //$NON-NLS-1$
208 
209     public static final String PARAM_STYLES_DEFAULT = ""; //$NON-NLS-1$
210 
211     public static final String PARAM_TEMPLATES_DEFAULT = ""; //$NON-NLS-1$
212 
213     public static final String PARAM_FONTS_DEFAULT = ""; //$NON-NLS-1$
214 
215     public static final String PARAM_FONT_SIZES_DEFAULT = ""; //$NON-NLS-1$
216 
217     public static final String PARAM_SOURCE_DEFAULT = "false"; //$NON-NLS-1$
218 
219     private static final String PARAM_COLORS_DEFAULT = "";
220 
221     private static final String PARAM_LISTS_DEFAULT = "true";
222 
223     private static final String PARAM_ALIGNMENT_DEFAULT = "false";
224 
225     private static final String PARAM_ENTER_MODE_DEFAULT = "p";
226 
227     private static final String PARAM_SHIFT_ENTER_MODE_DEFAULT = "br";
228 
229 
230     /**
231      * Empty constructor should only be used by DialogFactory.
232      */
233     public FckEditorDialog() {
234     }
235 
236     /**
237      * @return The name of the variable for the editor object
238      */
239     public String getVarName() {
240         String id = getId();
241         if (id == null) {
242             id = getName();
243         }
244         return "fck_" + id.replace('-', '_'); //$NON-NLS-1$
245     }
246 
247     /**
248      * @see info.magnolia.cms.gui.dialog.DialogControl#init(HttpServletRequest, HttpServletResponse, Content, Content)
249      */
250     public void init(HttpServletRequest request, HttpServletResponse response, Content websiteNode, Content configNode)
251         throws RepositoryException {
252         super.init(request, response, websiteNode, configNode);
253     }
254 
255     /**
256      * @see info.magnolia.cms.gui.dialog.DialogControl#drawHtml(Writer)
257      */
258     public void drawHtml(Writer out) throws IOException {
259         // get the config values
260         String jsInitFile = this.getConfigValue(PARAM_JS_INIT_FILE, PARAM_JS_INIT_FILE_DEFAULT);
261         String customConfigurationPath = this.getConfigValue(PARAM_CUSTOM_CONFIGURATION_PATH, this.getConfigValue(
262             "customConfigurationPath",
263             PARAM_CUSTOM_CONFIGURATION_PATH_DEFAULT));
264         String height = this.getConfigValue(PARAM_HEIGHT, PARAM_HEIGHT_DEFAULT);
265         String width = this.getConfigValue(PARAM_WIDTH, PARAM_WIDTH_DEFAULT);
266 
267         this.drawHtmlPre(out);
268 
269         // load the script onece: if there are multiple instances
270         if (getRequest().getAttribute(ATTRIBUTE_FCKED_LOADED) == null) {
271             out.write("<script type=\"text/javascript\" src=\"" //$NON-NLS-1$
272                 + this.getRequest().getContextPath()
273                 + "/.resources/fckeditor/fckeditor.js\"></script>"); //$NON-NLS-1$
274             getRequest().setAttribute(ATTRIBUTE_FCKED_LOADED, "true"); //$NON-NLS-1$
275         }
276 
277         String id = getName();
278 
279         if (id == null) {
280             log.error("Missing id for fckEditor instance"); //$NON-NLS-1$
281         }
282 
283         String var = getVarName();
284         String value = convertToView(getValue());
285         out.write("<script type=\"text/javascript\">"); //$NON-NLS-1$
286         out.write("// <![CDATA[\n"); //$NON-NLS-1$
287 
288         // make the configuration accessible to the config javascript
289         writeMgnlFCKConfig(out, id);
290 
291         out.write("var " + var + " = null;"); //$NON-NLS-1$ //$NON-NLS-2$
292         out.write("fckInstance = new FCKeditor( '" + id + "' );"); //$NON-NLS-1$ //$NON-NLS-2$
293         out.write("fckInstance.Value = '" + escapeJsValue(value) + "';"); //$NON-NLS-1$ //$NON-NLS-2$
294         out.write("fckInstance.BasePath = '" + this.getRequest().getContextPath() + FCKEDIT_PATH + "';"); //$NON-NLS-1$ //$NON-NLS-2$
295 
296         if (StringUtils.isNotEmpty(height)) {
297             out.write("fckInstance.Height = '" + this.getConfigValue("height") + "';"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
298         }
299 
300         if (StringUtils.isNotEmpty(width)) {
301             out.write("fckInstance.Width = '" + this.getConfigValue("width") + "';"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
302         }
303 
304         // now set the custom configuration path
305         if (StringUtils.isNotEmpty(customConfigurationPath)) {
306             out.write("fckInstance.Config['CustomConfigurationsPath'] = '" //$NON-NLS-1$
307                 + this.getRequest().getContextPath()
308                 + customConfigurationPath
309                 + "';"); //$NON-NLS-1$
310         }
311 
312         // here we pass the parameters to the custom configuration file --> via
313         // javascript
314 
315         // start the initfile
316         if (jsInitFile.length() > 0) {
317             out.write("// ]]>\n"); //$NON-NLS-1$
318             out.write("</script>"); //$NON-NLS-1$
319             out.write("<script type=\"text/javascript\" src=\"" //$NON-NLS-1$
320                 + this.getRequest().getContextPath()
321                 + jsInitFile
322                 + "\"></script>\n"); //$NON-NLS-1$
323             out.write("<script type=\"text/javascript\">"); //$NON-NLS-1$
324             out.write("// <![CDATA[\n"); //$NON-NLS-1$
325         }
326 
327         // finaly create the editor
328         out.write("fckInstance.Create();"); //$NON-NLS-1$
329         out.write(var + " = fckInstance;"); //$NON-NLS-1$
330         out.write("// ]]>\n"); //$NON-NLS-1$
331         out.write("</script>"); //$NON-NLS-1$
332 
333         // write the saveInfo for the writing back to the repository
334         out.write("<input type='hidden' name='mgnlSaveInfo' value='" //$NON-NLS-1$
335             + id
336             + ",String," //$NON-NLS-1$
337             + ControlImpl.VALUETYPE_SINGLE
338             + "," //$NON-NLS-1$
339             + ControlImpl.RICHEDIT_FCK
340             + "," //$NON-NLS-1$
341             + ControlImpl.ENCODING_NO
342             + "' />"); //$NON-NLS-1$
343 
344         this.drawHtmlPost(out);
345     }
346 
347     private void writeMgnlFCKConfig(Writer out, String id) throws IOException {
348         String css = this.getConfigValue(PARAM_CSS, PARAM_CSS_DEFAULT);
349         String fonts = this.getConfigValue(PARAM_FONTS, PARAM_FONTS_DEFAULT);
350         String fontSizes = this.getConfigValue(PARAM_FONT_SIZES, PARAM_FONT_SIZES_DEFAULT);
351         String colors = this.getConfigValue(PARAM_COLORS, PARAM_COLORS_DEFAULT);
352         String styles = this.getConfigValue(PARAM_STYLES, PARAM_STYLES_DEFAULT);
353         String templates = this.getConfigValue(PARAM_TEMPLATES, PARAM_TEMPLATES_DEFAULT);
354 
355         String lists = this.getConfigValue(PARAM_LISTS, PARAM_LISTS_DEFAULT);
356         String alignment = this.getConfigValue(PARAM_ALIGNMENT, PARAM_ALIGNMENT_DEFAULT);
357         String tables = this.getConfigValue(PARAM_TABLES, PARAM_TABLES_DEFAULT);
358         String images = this.getConfigValue(PARAM_IMAGES, PARAM_IMAGES_DEFAULT);
359         String source = this.getConfigValue(PARAM_SOURCE, PARAM_SOURCE_DEFAULT);
360         String enterMode = this.getConfigValue(PARAM_ENTER_MODE, PARAM_ENTER_MODE_DEFAULT);
361         String shiftEnterMode = this.getConfigValue(PARAM_SHIFT_ENTER_MODE, PARAM_SHIFT_ENTER_MODE_DEFAULT);
362 
363         // create the the holder of the editors configs if not yet done
364         out.write("if( window.MgnlFCKConfigs == null)\n");
365         out.write("    window.MgnlFCKConfigs = new Object();\n");
366 
367         // add the config for this editor
368 
369         out.write("MgnlFCKConfigs." + id + " = new Object();\n");
370         // string values
371         out.write("MgnlFCKConfigs." + id + ".language = '" + MgnlContext.getUser().getLanguage() + "';\n");
372         out.write("MgnlFCKConfigs." + id + ".contextPath = '" + getRequest().getContextPath() + "';\n");
373 
374         out.write("MgnlFCKConfigs." + id + ".repository = '" + getTopParent().getConfigValue("repository") + "';\n");
375         out.write("MgnlFCKConfigs." + id + ".path = '" + getTopParent().getConfigValue("path") + "';\n");
376         out.write("MgnlFCKConfigs."
377             + id
378             + ".nodeCollection = '"
379             + getTopParent().getConfigValue("nodeCollection")
380             + "';\n");
381         out.write("MgnlFCKConfigs." + id + ".node = '" + getTopParent().getConfigValue("node") + "';\n");
382 
383         out.write("MgnlFCKConfigs." + id + ".css = '" + css + "';\n");
384         out.write("MgnlFCKConfigs." + id + ".fonts = '" + fonts + "';\n");
385         out.write("MgnlFCKConfigs." + id + ".fontSizes = '" + fontSizes + "';\n");
386         out.write("MgnlFCKConfigs." + id + ".colors = '" + colors + "';\n");
387         out.write("MgnlFCKConfigs." + id + ".styles = '" + styles + "';\n");
388         out.write("MgnlFCKConfigs." + id + ".templates = '" + templates + "';\n");
389         out.write("MgnlFCKConfigs." + id + ".enterMode = '" + enterMode + "';\n");
390         out.write("MgnlFCKConfigs." + id + ".shiftEnterMode = '" + shiftEnterMode + "';\n");
391 
392         // boolean values
393         out.write("MgnlFCKConfigs." + id + ".lists = " + lists + ";\n");
394         out.write("MgnlFCKConfigs." + id + ".alignment = " + alignment + ";\n");
395         out.write("MgnlFCKConfigs." + id + ".tables = " + tables + ";\n");
396         out.write("MgnlFCKConfigs." + id + ".images = " + images + ";\n");
397         out.write("MgnlFCKConfigs." + id + ".source = " + source + ";\n");
398     }
399 
400     /**
401      * @param value
402      * @return
403      */
404     public String convertToView(String value) {
405         if (value != null) {
406             final EditorLinkTransformer transformer = LinkTransformerManager.getInstance().getEditorLink();
407             try {
408                 value = LinkUtil.convertLinksFromUUIDPattern(value, transformer);
409             } catch (LinkException e) {
410                 // LinkUtil.convertLinksFromUUIDPattern should have taken care of unknown nodes properly by now, so all we can do is ...
411                 log.warn(e.getMessage());
412                 throw new RuntimeException(e);
413             }
414 
415             // this section is for backward compatibility - see MAGNOLIA-2088
416             final Matcher matcher = LinkHelper.LINK_OR_IMAGE_PATTERN.matcher(value);
417             final StringBuffer res = new StringBuffer();
418             while (matcher.find()) {
419                 final String src = matcher.group(4);
420 
421                 // process only internal and relative links
422                 if (LinkUtil.isInternalRelativeLink(src)) {
423                     final String link = MgnlContext.getContextPath()
424                         + this.getTopParent().getConfigValue("path")
425                         + "/"
426                         // remove the page name
427                         + StringUtils.substringAfter(src, "/");
428 
429                     matcher.appendReplacement(res, "$1" + link + "$5"); //$NON-NLS-1$
430                 }
431             }
432             matcher.appendTail(res);
433             return res.toString();
434         }
435 
436         return StringUtils.EMPTY;
437     }
438 
439     /**
440      * Escapes the given String to make it javascript friendly.
441      * (escaping single quotes, double quotes, new lines, backslashes, ...)
442      * @param src
443      * @return escaped js String
444      * @see StringEscapeUtils#escapeJavaScript(String)
445      */
446     public String escapeJsValue(String src) {
447         return StringEscapeUtils.escapeJavaScript(src);
448     }
449 
450 }