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}