001/*
002 *  Copyright 2021 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.plugins.forms.menu;
017
018import java.util.ArrayList;
019import java.util.Comparator;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023import java.util.TreeMap;
024import java.util.TreeSet;
025
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.configuration.DefaultConfiguration;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031
032import org.ametys.core.observation.ObservationManager;
033import org.ametys.core.ui.ClientSideElementHelper;
034import org.ametys.core.ui.SimpleMenu;
035import org.ametys.core.ui.StaticClientSideElement;
036import org.ametys.core.util.I18nizableTextKeyComparator;
037import org.ametys.plugins.forms.question.FormQuestionType;
038import org.ametys.plugins.forms.question.FormQuestionTypeExtensionPoint;
039import org.ametys.plugins.forms.question.types.RichTextQuestionType;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.runtime.i18n.I18nizableText;
042
043/**
044 * The form question button menu configuration class
045 */
046public class FormQuestionButtonMenu extends SimpleMenu 
047{
048    /** The form question type extension point. */
049    protected FormQuestionTypeExtensionPoint _formQuestionTypeExtensionPoint;
050    
051    /** Repository content */
052    protected AmetysObjectResolver _resolver;
053    
054    /** The observation manager */
055    protected ObservationManager _observationManager;
056    
057    private boolean _galleryInitialized;
058    
059    @Override
060    public void service(ServiceManager serviceManager) throws ServiceException
061    {
062        super.service(serviceManager);
063        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
064        _observationManager = (ObservationManager) serviceManager.lookup(ObservationManager.ROLE);
065        _formQuestionTypeExtensionPoint = (FormQuestionTypeExtensionPoint) serviceManager.lookup(FormQuestionTypeExtensionPoint.ROLE);
066    }
067    
068    @Override
069    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
070    {
071        try
072        {
073            _lazyInitializeContentViewGallery();
074        }
075        catch (Exception e)
076        {
077            throw new IllegalStateException("Unable to lookup client side element local components", e);
078        }
079        
080        List<Script> scripts = new ArrayList<>();
081        for (Script script : super.getScripts(ignoreRights, contextParameters))
082        {
083            Script cloneScript = ClientSideElementHelper.cloneScript(script);
084            cloneScript.getScriptFiles().add(new ScriptFile("/plugins/forms/resources/js/Ametys/plugins/forms/controllers/FormController.js"));
085            scripts.add(cloneScript);
086        }
087        
088        return scripts;
089    }
090    
091    
092    private synchronized void _lazyInitializeContentViewGallery() throws ConfigurationException
093    {
094        if (!_galleryInitialized)
095        {
096            Map<I18nizableText, Set<FormQuestionType>> typesByGroup = _getQuestionTypeByGroup();
097            
098            if (typesByGroup.size() > 0)
099            {
100                GalleryItem galleryItem = new GalleryItem();
101                
102                for (I18nizableText groupLabel : typesByGroup.keySet())
103                {
104                    GalleryGroup galleryGroup = new GalleryGroup(groupLabel);
105                    galleryItem.addGroup(galleryGroup);
106                    
107                    Set<FormQuestionType> cTypes = typesByGroup.get(groupLabel);
108                    for (FormQuestionType questionType : cTypes)
109                    {
110                        String id = this.getId() + "-" + questionType.getId();
111                        
112                        Configuration conf = _getQuestionTypeItemConfiguration (id, questionType);
113                        _getGalleryItemManager().addComponent(_pluginName, null, id, StaticClientSideElement.class, conf);
114                        galleryGroup.addItem(new UnresolvedItem(id, true));
115                    }
116                }
117                _galleryItems.add(galleryItem);
118            }
119            
120            if (_galleryItems.size() > 0)
121            {
122                try
123                {
124                    _getGalleryItemManager().initialize();
125                }
126                catch (Exception e)
127                {
128                    throw new ConfigurationException("Unable to lookup parameter local components", e);
129                }
130            }
131        }
132        
133        _galleryInitialized = true;
134    }
135    
136    /**
137     * Get the configuration for a question type item
138     * @param itemId The item id
139     * @param questionType the question type
140     * @return The configuration
141     */
142    protected Configuration _getQuestionTypeItemConfiguration (String itemId, FormQuestionType questionType)
143    {
144        DefaultConfiguration conf = new DefaultConfiguration("extension");
145        conf.setAttribute("id", itemId);
146        
147        DefaultConfiguration classConf = new DefaultConfiguration("class");
148        classConf.setAttribute("name", "Ametys.plugins.forms.controllers.FormController");
149        
150        // Label
151        DefaultConfiguration labelConf = new DefaultConfiguration("label");
152        I18nizableText label = questionType.getLabel();
153        labelConf.setAttribute("i18n", "true");
154        labelConf.setValue(label.getCatalogue() + ":" + label.getKey());
155        classConf.addChild(labelConf);
156        
157        // Description
158        DefaultConfiguration descConf = new DefaultConfiguration("description");
159        I18nizableText desc = questionType.getDescription();
160        descConf.setAttribute("i18n", "true");
161        descConf.setValue(desc.getCatalogue() + ":" + desc.getKey());
162        classConf.addChild(descConf);
163
164        // Icons or glyph
165        DefaultConfiguration iconGlyphConf = new DefaultConfiguration("icon-glyph");
166        iconGlyphConf.setValue(questionType.getIconGlyph());
167        classConf.addChild(iconGlyphConf);
168        
169        // Common configuration
170        @SuppressWarnings("unchecked")
171        Map<String, Object> commonConfig = (Map<String, Object>) this._script.getParameters().get("items-config");
172        for (String tagName : commonConfig.keySet())
173        {
174            DefaultConfiguration c = new DefaultConfiguration(tagName);
175            Object value = commonConfig.get(tagName);
176            if (value instanceof I18nizableText i18nText)
177            {
178                c.setAttribute("i18n", String.valueOf(i18nText.isI18n()));
179                c.setValue(i18nText.isI18n() ? i18nText.getKey() : i18nText.getLabel());
180            }
181            else
182            {
183                c.setValue(String.valueOf(commonConfig.get(tagName)));
184            }
185            classConf.addChild(c);
186        }
187        
188        if (!(questionType instanceof RichTextQuestionType))
189        {
190            DefaultConfiguration noEntryOnlyConf = new DefaultConfiguration("enable-on-noentry-only");
191            noEntryOnlyConf.setValue("true");
192            classConf.addChild(noEntryOnlyConf);
193        }
194        
195        // Type Id
196        DefaultConfiguration typeIdConf = new DefaultConfiguration("type-id");
197        typeIdConf.setValue(questionType.getId());
198        classConf.addChild(typeIdConf);
199        
200        conf.addChild(classConf);
201        return conf;
202    }
203    
204    /**
205     * Get the list of question types classified by groups
206     * @return The map of form question types
207     */
208    protected Map<I18nizableText, Set<FormQuestionType>> _getQuestionTypeByGroup ()
209    {
210        Map<I18nizableText, Set<FormQuestionType>> groups = new TreeMap<>(new I18nizableTextKeyComparator());
211        
212        Set<String> questionTypesIds = _formQuestionTypeExtensionPoint.getExtensionsIds();
213        for (String questionTypeId : questionTypesIds)
214        {
215            FormQuestionType questionType = _formQuestionTypeExtensionPoint.getExtension(questionTypeId);
216            _script.getScriptFiles().addAll(questionType.getScripts());
217            addType (questionType, groups);
218        }
219        
220        return groups;
221    }
222    
223    /**
224     * Add question type to groups
225     * @param type The form question type
226     * @param groups The groups
227     */
228    protected void addType (FormQuestionType type, Map<I18nizableText, Set<FormQuestionType>> groups)
229    {
230        I18nizableText group = type.getCategory();
231        if (group.isI18n() && group.getKey().isEmpty()
232            || !group.isI18n() && group.getLabel().isEmpty())
233        {
234            group = new I18nizableText("plugin.forms", "PLUGINS_FORMS_QUESTION_TYPE_CATEGORY_90_OTHERS");
235        }
236        
237        if (!groups.containsKey(group))
238        {
239            groups.put(group, new TreeSet<>(new QuestionTypeComparator()));
240        }
241        
242        Set<FormQuestionType> types = groups.get(group);
243        types.add(type);
244    }
245    
246    class QuestionTypeComparator implements Comparator<FormQuestionType>
247    {
248        @Override
249        public int compare(FormQuestionType type1, FormQuestionType type2)
250        {
251            Integer order1 = type1.getDisplayOrder();
252            Integer order2 = type2.getDisplayOrder();
253            
254            int compareTo = order1.compareTo(order2);
255            if (compareTo == 0)
256            {
257                I18nizableText t1 = type1.getLabel();
258                I18nizableText t2 = type2.getLabel();
259                
260                String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel();
261                String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel();
262                compareTo = str1.toString().compareTo(str2.toString());
263                if (compareTo == 0)
264                {
265                    // Content types have same keys but there are not equals, so do not return 0 to add it in TreeSet
266                    // Indeed, in a TreeSet implementation two elements that are equal by the method compareTo are, from the standpoint of the set, equal 
267                    return 1;
268                }
269            }
270            return compareTo;
271        }
272    }
273}