View Javadoc
1   /**
2    * This file Copyright (c) 2012-2015 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.ui.form.field.factory;
35  
36  import info.magnolia.i18nsystem.SimpleTranslator;
37  import info.magnolia.repository.RepositoryConstants;
38  import info.magnolia.ui.api.app.AppController;
39  import info.magnolia.ui.api.app.ChooseDialogCallback;
40  import info.magnolia.ui.api.context.UiContext;
41  import info.magnolia.ui.form.field.definition.RichTextFieldDefinition;
42  import info.magnolia.ui.vaadin.integration.jcr.JcrItemId;
43  import info.magnolia.ui.vaadin.integration.jcr.JcrItemUtil;
44  import info.magnolia.ui.vaadin.richtext.MagnoliaRichTextField;
45  import info.magnolia.ui.vaadin.richtext.MagnoliaRichTextFieldConfig;
46  import info.magnolia.ui.vaadin.richtext.MagnoliaRichTextFieldConfig.ToolbarGroup;
47  
48  import java.util.ArrayList;
49  import java.util.List;
50  
51  import javax.jcr.Node;
52  
53  import org.apache.commons.lang3.StringUtils;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  import com.google.gson.Gson;
58  import com.google.inject.Inject;
59  import com.vaadin.data.Item;
60  import com.vaadin.server.Sizeable.Unit;
61  import com.vaadin.server.VaadinService;
62  import com.vaadin.ui.Field;
63  
64  /**
65   * Creates and configures a rich-text field based on its definition.
66   */
67  public class RichTextFieldFactory extends AbstractFieldFactory<RichTextFieldDefinition, String> {
68  
69      private static final Logger log = LoggerFactory.getLogger(RichTextFieldFactory.class);
70  
71      private static final String PLUGIN_NAME_MAGNOLIALINK = "magnolialink";
72  
73      private static final String PLUGIN_PATH_MAGNOLIALINK = "/VAADIN/js/magnolialink/";
74  
75      /**
76       * Event is transmitted from server to client when link has been selected.
77       */
78      public static final String EVENT_SEND_MAGNOLIA_LINK = "mgnlLinkSelected";
79  
80      /**
81       * Event is transmitted from server to client when link dialog has been
82       * canceled or exception has occurred. In case of exception
83       * the event will carry error message.
84       */
85      public static final String EVENT_CANCEL_LINK = "mgnlLinkCancel";
86  
87      /**
88       * Event is transmitted from client to server when user requests a link dialog.
89       * Event carries optional link that should be treated as default link value.
90       */
91      public static final String EVENT_GET_MAGNOLIA_LINK = "mgnlGetLink";
92  
93      protected final AppController appController;
94      protected final UiContext uiContext;
95      protected final SimpleTranslator i18n;
96      protected MagnoliaRichTextField richTextEditor;
97  
98      @Inject
99      public RichTextFieldFactory(RichTextFieldDefinition definition, Item relatedFieldItem, AppController appController, UiContext uiContext, SimpleTranslator i18n) {
100         super(definition, relatedFieldItem);
101         this.appController = appController;
102         this.uiContext = uiContext;
103         this.i18n = i18n;
104     }
105 
106     @Override
107     protected Field<String> createFieldComponent() {
108 
109         MagnoliaRichTextFieldConfig config = initializeCKEditorConfig();
110         richTextEditor = new MagnoliaRichTextField(config);
111         if (definition.getHeight() > 0) {
112             richTextEditor.setHeight(definition.getHeight(), Unit.PIXELS);
113         }
114 
115         richTextEditor.addListener(new MagnoliaRichTextField.PluginListener() {
116             @Override
117             public void onPluginEvent(String eventName, String value) {
118                 if (eventName.equals(EVENT_GET_MAGNOLIA_LINK)) {
119                     try {
120                         Gson gson = new Gson();
121                         PluginData pluginData = gson.fromJson(value, PluginData.class);
122                         openLinkDialog(pluginData.path, pluginData.workspace);
123                     } catch (Exception e) {
124                         log.error("openLinkDialog failed", e);
125                         richTextEditor.firePluginEvent(EVENT_CANCEL_LINK, i18n.translate("ui-form.richtexteditorexception.opentargetappfailure"));
126                     }
127                 }
128             }
129         });
130 
131         return richTextEditor;
132     }
133 
134     protected MagnoliaRichTextFieldConfig initializeCKEditorConfig() {
135 
136         MagnoliaRichTextFieldConfig config = new MagnoliaRichTextFieldConfig();
137         String path = VaadinService.getCurrentRequest().getContextPath();
138 
139         // MAGNOLIA LINK PLUGIN — may be used with/without customConfig
140         config.addExternalPlugin(PLUGIN_NAME_MAGNOLIALINK, path + PLUGIN_PATH_MAGNOLIALINK);
141         config.addListenedEvent(EVENT_GET_MAGNOLIA_LINK);
142 
143         // CUSTOM CONFIG.JS — bypass further config because it can't be overridden otherwise
144         if (StringUtils.isNotBlank(definition.getConfigJsFile())) {
145             config.addExtraConfig("customConfig", "'" + path + definition.getConfigJsFile() + "'");
146             return config;
147         }
148 
149         // DEFINITION
150         if (!definition.isAlignment()) {
151             config.addToRemovePlugins("justify");
152         }
153         if (!definition.isImages()) {
154             config.addToRemovePlugins("image");
155         }
156         if (!definition.isLists()) {
157             // In CKEditor 4.1.1 enterkey depends on indent which itself depends on list
158             config.addToRemovePlugins("enterkey");
159             config.addToRemovePlugins("indent");
160             config.addToRemovePlugins("list");
161         }
162         if (!definition.isSource()) {
163             config.addToRemovePlugins("sourcearea");
164         }
165         if (!definition.isTables()) {
166             config.addToRemovePlugins("table");
167             config.addToRemovePlugins("tabletools");
168         }
169 
170         if (definition.getColors() != null) {
171             config.addExtraConfig("colorButton_colors", "'" + definition.getColors() + "'");
172             config.addExtraConfig("colorButton_enableMore", "false");
173             config.addToRemovePlugins("colordialog");
174         } else {
175             config.addToRemovePlugins("colorbutton");
176             config.addToRemovePlugins("colordialog");
177         }
178         if (definition.getFonts() != null) {
179             config.addExtraConfig("font_names", "'" + definition.getFonts() + "'");
180         } else {
181             config.addExtraConfig("removeButtons", "'Font'");
182         }
183         if (definition.getFontSizes() != null) {
184             config.addExtraConfig("fontSize_sizes", "'" + definition.getFontSizes() + "'");
185         } else {
186             config.addExtraConfig("removeButtons", "'FontSize'");
187         }
188         if (definition.getFonts() == null && definition.getFontSizes() == null) {
189             config.addToRemovePlugins("font");
190             config.addToRemovePlugins("fontSize");
191         }
192 
193         // MAGNOLIA EXTRA CONFIG
194         List<ToolbarGroup> toolbars = initializeToolbarConfig();
195         config.addToolbarLine(toolbars);
196 
197         config.addToExtraPlugins(PLUGIN_NAME_MAGNOLIALINK);
198         config.addToRemovePlugins("elementspath");
199         config.setBaseFloatZIndex(150);
200         config.setResizeEnabled(false);
201 
202         return config;
203     }
204 
205     protected List<ToolbarGroup> initializeToolbarConfig() {
206         List<ToolbarGroup> toolbars = new ArrayList<ToolbarGroup>();
207         toolbars.add(new ToolbarGroup("basicstyles", new String[] { "Bold", "Italic", "Underline", "SpecialChar" }));
208         toolbars.add(new ToolbarGroup("paragraph", new String[] { "NumberedList", "BulletedList", "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock", "Image", "Table" }));
209         toolbars.add(new ToolbarGroup("links", new String[] { "Link", "InternalLink", "DamLink", "Unlink" }));
210         toolbars.add(new ToolbarGroup("styles", new String[] { "Font", "FontSize", "TextColor" }));
211         toolbars.add(new ToolbarGroup("clipboard", new String[] { "Cut", "Copy", "Paste", "PasteText", "PasteFromWord" }));
212         toolbars.add(new ToolbarGroup("undo", new String[] { "Undo", "Redo" }));
213         toolbars.add(new ToolbarGroup("tools", new String[] { "Source" }));
214         return toolbars;
215     }
216 
217     private String mapWorkSpaceToApp(String workspace) {
218         if (workspace.equalsIgnoreCase("dam")) {
219             return "assets";
220         } else if (workspace.equalsIgnoreCase(RepositoryConstants.WEBSITE)) {
221             return "pages";
222         }
223 
224         return "";
225     }
226 
227     private void openLinkDialog(String path, String workspace) {
228         appController.openChooseDialog(mapWorkSpaceToApp(workspace), uiContext, null, new ChooseDialogCallback() {
229             @Override
230             public void onItemChosen(String actionName, Object chosenValue) {
231                 if (!(chosenValue instanceof JcrItemId)) {
232                     richTextEditor.firePluginEvent(EVENT_CANCEL_LINK);
233                     return;
234                 }
235                 try {
236                     javax.jcr.Item jcrItem = JcrItemUtil.getJcrItem((JcrItemId) chosenValue);
237                     if (!jcrItem.isNode()) {
238                         return;
239                     }
240                     final Node selected = (Node) jcrItem;
241                     Gson gson = new Gson();
242                     MagnoliaLink mlink = new MagnoliaLink();
243                     mlink.identifier = selected.getIdentifier();
244                     mlink.repository = selected.getSession().getWorkspace().getName();
245                     mlink.path = selected.getPath();
246                     if (selected.hasProperty("title")) {
247                         mlink.caption = selected.getProperty("title").getString();
248                     } else {
249                         mlink.caption = selected.getName();
250                     }
251 
252                     richTextEditor.firePluginEvent(EVENT_SEND_MAGNOLIA_LINK, gson.toJson(mlink));
253                 } catch (Exception e) {
254                     String error = i18n.translate("ui-form.richtexteditorexception.cannotaccessselecteditem");
255                     log.error(error, e);
256                     richTextEditor.firePluginEvent(EVENT_CANCEL_LINK, error);
257                 }
258             }
259 
260             @Override
261             public void onCancel() {
262                 richTextEditor.firePluginEvent(EVENT_CANCEL_LINK);
263             }
264         });
265     }
266 
267     /**
268      * Link info wrapper.
269      */
270     protected static class MagnoliaLink {
271 
272         public String identifier;
273 
274         public String repository;
275 
276         public String path;
277 
278         public String caption;
279 
280     }
281 
282     /**
283      * Plugin data wrapper.
284      */
285     protected static class PluginData {
286         public String workspace;
287         public String path;
288     }
289 }