001/*
002 *  Copyright 2017 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.ui.widgets.richtext;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.Set;
027
028import org.ametys.core.ui.Callable;
029import org.ametys.core.ui.ClientSideElement;
030import org.ametys.core.ui.ClientSideElement.Script;
031import org.ametys.core.ui.ClientSideElement.ScriptFile;
032import org.ametys.core.ui.widgets.richtext.RichTextConfigurationTag.EMPTY_TAG;
033import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
034
035/**
036 * Extension point for configuring the widget 'richtext' 
037 */
038public class RichTextConfigurationExtensionPoint extends AbstractThreadSafeComponentExtensionPoint<RichTextConfiguration>
039{
040    /** The component role */
041    public static final String ROLE = RichTextConfigurationExtensionPoint.class.getName();
042    
043    /**
044     * Converts all values to a single JSON object
045     * { 
046     *     "category1" : 
047     *     {
048     *         "tags" : {
049     *             "p" : {
050     *                 empty: "CLOSE|OPEN|REMOVE_EMPTY_CONTENT|PADDING|REMOVE_EMPTY_ATTRIBUTES",
051     *                 synonyms: ["div"],
052     *                 attributes: {
053     *                     "id": {},
054     *                     "alt": {
055     *                         "default-value": ""
056     *                     },
057     *                     "class": {
058     *                         "default-value": "value1",
059     *                         "values": ["value1", "value2"],
060     *                         "technical-values": [ "value1" ] // special for 'class' attribute
061     *                     }
062     *                 }
063     *             }
064     *         },
065     *         
066     *         "styles" : {
067     *             "paragraph" : {
068     *                  "groups": [ {
069     *                      "label": "Customized styles",
070     *                      "priority": 1,
071     *                      "values" : [
072     *                          {
073     *                              "tagname": "h1",
074     *                              "cssclass": "big",
075     *                          
076     *                              "label": "Header 1 (big)",
077     *                              "description": "A big header",
078     *                              "buttonCSSClass": "h1-big",
079     *                              "buttonSmallIcon": "img/h1_16.png",
080     *                              "buttonMediumIcon": "img/h1_32.png",
081     *                              "buttonLargeIcon": "img/h1_48.png"
082     *                          }
083     *                      ]
084     *                  } ]
085     *             }
086     *         },
087     *         
088     *         "css" : [ 
089     *             { language: false, url: "file1", debug: "all|true|false", rtl: "all|true|false" }, 
090     *             { language: true, default: "en", url : {"fr": "file1"} 
091     *         ],
092     *         
093     *         "validators": [
094     *             "class": {
095     *                 "name": "classname",
096     *                 "parameters": { ... }
097     *             }
098     *         ]
099     *         
100     *         "convertors": [
101     *             "class": {
102     *                 "name": "classname",
103     *                 "parameters": { ... }
104     *             }
105     *         ]
106     *     }
107     * }
108     * @param contextualParameters Contextuals parameters transmitted by the environment.
109     * @return The non-null configuration
110     */
111    @Callable
112    public Map<String, Object> toJSON(Map<String, Object> contextualParameters)
113    {
114        Map<String, Object> json = new HashMap<>();
115
116        for (String extensionId : getExtensionsIds())
117        {
118            _addExtensionToJSON(contextualParameters, json, getExtension(extensionId));
119        }
120        
121        return json;
122    }
123    
124    @SuppressWarnings("unchecked")
125    private  void _addExtensionToJSON(Map<String, Object> contextualParameters, Map<String, Object> json, RichTextConfiguration extension)
126    {
127        Set<String> categories = extension.getCategories();
128        for (String category : categories)
129        {
130            if (!json.containsKey(category))
131            {
132                Map<String, Object> jsonCategory = new HashMap<>();
133                json.put(category, jsonCategory);
134                
135                jsonCategory.put("tags", new HashMap<>());
136                jsonCategory.put("styles", new HashMap<>());
137                jsonCategory.put("css", new HashSet<>());
138                jsonCategory.put("validators", new ArrayList<>());
139                jsonCategory.put("convertors", new ArrayList<>());
140            }
141            
142            Map<String, Object> jsonCategory = (Map<String, Object>) json.get(category);
143            
144            _addTagsToJSON((Map<String, Object>) jsonCategory.get("tags"), extension.getHandledTags(category, contextualParameters));
145            _addStylesToJSON((Map<String, Map<String, List<Map<String, Object>>>>) jsonCategory.get("styles"), extension.getAvailableStyles(category, contextualParameters));
146            _addFilesToJSON((Set<Map<String, Object>>) jsonCategory.get("css"), extension.getCSSFiles(category, contextualParameters));
147            _addClientSideElementToJSON((List<Object>) jsonCategory.get("validators"), extension.getValidators(category, contextualParameters));
148            _addClientSideElementToJSON((List<Object>) jsonCategory.get("convertors"), extension.getConvertors(category, contextualParameters));
149        }
150        
151    }
152
153    @SuppressWarnings("unchecked")
154    private void _addTagsToJSON(Map<String, Object> object, Collection<RichTextConfigurationTag> handledTags)
155    {
156        if (handledTags != null)
157        {
158            for (RichTextConfigurationTag handledTag : handledTags)
159            {
160                if (!object.containsKey(handledTag.getTag()))
161                {
162                    Map<String, Object> jsonTag = new HashMap<>();
163                    jsonTag.put("empty", EMPTY_TAG.CLOSE);
164                    jsonTag.put("synonyms", new HashSet<>());
165                    jsonTag.put("attributes", new HashMap<>()); 
166                    object.put(handledTag.getTag(), jsonTag);
167                }
168    
169                Map<String, Object> jsonTag = (Map<String, Object>) object.get(handledTag.getTag());
170                
171                jsonTag.put("empty", handledTag.onEmptyTag().compareTo((EMPTY_TAG) jsonTag.get("empty")) < 0 ? handledTag.onEmptyTag() : (EMPTY_TAG) jsonTag.get("empty"));
172                
173                ((Set<String>) jsonTag.get("synonyms")).addAll(handledTag.getSynonyms());
174                
175                Map<String, Object> attributes = (Map<String, Object>) jsonTag.get("attributes");
176                for (RichTextConfigurationAttribute attribute : handledTag.getAttributes())
177                {
178                    boolean classAttribute = "class".equals(attribute.getName());
179                    
180                    if (!attributes.containsKey(attribute.getName()))
181                    {
182                        Map<String, Object> jsonAttribute = new HashMap<>();
183                        jsonAttribute.put("default-value", null);
184                        jsonAttribute.put("values", new ArrayList<>());
185                        if (classAttribute)
186                        {
187                            jsonAttribute.put("technical-values", new ArrayList<>());
188                        }
189                        attributes.put(attribute.getName(), jsonAttribute);
190                    }
191                    
192                    Map<String, Object> jsonAttribute = (Map<String, Object>) attributes.get(attribute.getName());
193                    jsonAttribute.put("default-value", attribute.getDefaultValue()); // Let's us take the last one
194                    ((List<String>) jsonAttribute.get("values")).addAll(attribute.getAuthorizedValues());
195                    if (classAttribute)
196                    {
197                        ((List<String>) jsonAttribute.get("technical-values")).addAll(attribute.getTechnicalValues());
198                    }
199                }
200            }
201        }
202    }
203    
204    private void _addStylesToJSON(Map<String, Map<String, List<Map<String, Object>>>> object, Map<String, Map<RichTextConfigurationStyleGroup, List<RichTextConfigurationStyle>>> availableStyles)
205    {
206        if (availableStyles != null)
207        {
208            for (Entry<String, Map<RichTextConfigurationStyleGroup, List<RichTextConfigurationStyle>>> entry : availableStyles.entrySet())
209            {
210                if (!object.containsKey(entry.getKey()))
211                {
212                    Map<String, List<Map<String, Object>>> jsonStylesGroups = new HashMap<>();
213                    jsonStylesGroups.put("groups", new ArrayList<>());
214                    object.put(entry.getKey(), jsonStylesGroups);
215                }
216    
217                List<Map<String, Object>> jsonStylesGroups = object.get(entry.getKey()).get("groups");
218                
219                for (Entry<RichTextConfigurationStyleGroup, List<RichTextConfigurationStyle>> entry2 : entry.getValue().entrySet())
220                {
221                    List<Map<String, Object>> jsonStyles = new ArrayList<>();
222                    for (RichTextConfigurationStyle richTextConfigurationStyle : entry2.getValue())
223                    {
224                        Map<String, Object> styleAsJSON = new HashMap<>();
225                        jsonStyles.add(styleAsJSON);
226                        
227                        styleAsJSON.put("tagname", richTextConfigurationStyle.getTagName());
228                        styleAsJSON.put("cssclass", richTextConfigurationStyle.getClassName());
229                        
230                        styleAsJSON.put("label", richTextConfigurationStyle.getButtonLabel());
231                        styleAsJSON.put("description", richTextConfigurationStyle.getButtonDescription());
232                        styleAsJSON.put("buttonCSSClass", richTextConfigurationStyle.getButtonCSSClass());
233                        styleAsJSON.put("buttonSmallIcon", richTextConfigurationStyle.getButtonSmallIcon());
234                        styleAsJSON.put("buttonMediumIcon", richTextConfigurationStyle.getButtonMediumIcon());
235                        styleAsJSON.put("buttonLargeIcon", richTextConfigurationStyle.getButtonLargeIcon());
236                    }
237                    
238                    Map<String, Object> group = new HashMap<>();
239                    group.put("label", entry2.getKey().getLabel());
240                    group.put("priority", entry2.getKey().getPriority());
241                    group.put("values", jsonStyles);
242                    jsonStylesGroups.add(group);
243                }
244            }
245        }
246    }
247    
248    private void _addFilesToJSON(Set<Map<String, Object>> object, List<ScriptFile> cssFiles)
249    {
250        if (cssFiles != null)
251        {
252            for (ScriptFile file : cssFiles)
253            {
254                object.add(_scriptFileToJson(file));
255            }
256        }
257    }
258    
259    private void _addClientSideElementToJSON(List<Object> list, Set<ClientSideElement> clientSideElements)
260    {
261        if (clientSideElements != null)
262        {
263            for (ClientSideElement clientSideElementn : clientSideElements)
264            {
265                List<Script> scripts = clientSideElementn.getScripts(Collections.EMPTY_MAP);
266                for (Script script : scripts)
267                {
268                    Map<String, Object> jsonClientSideElement = new HashMap<>();
269                    
270                    Map<String, Object> jsonClass = new HashMap<>();
271                    jsonClass.put("name", script.getScriptClassname());
272                    jsonClass.put("parameters", script.getParameters());
273                    jsonClientSideElement.put("class", jsonClass);
274                    
275                    list.add(jsonClientSideElement);
276                }
277            }
278        }
279    }
280    
281    private Map<String, Object> _scriptFileToJson(ScriptFile scriptFile)
282    {
283        Map<String, Object> json = new HashMap<>();
284        
285        if (scriptFile.isLangSpecific())
286        {
287            json.put("language", true);
288            json.put("default", scriptFile.getDefaultLang());
289            json.put("url", scriptFile.getLangPaths());
290        }
291        else
292        {
293            json.put("language", false);
294            json.put("url", scriptFile.getPath());
295            json.put("rtl", scriptFile.getRtlMode());
296        }
297
298        return json;
299    }
300}