View Javadoc

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