001/* 002 * Copyright 2022 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.actions; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.LinkedHashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.parameters.Parameters; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.cocoon.ProcessingException; 028import org.apache.cocoon.acting.ServiceableAction; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Redirector; 031import org.apache.cocoon.environment.Request; 032import org.apache.cocoon.environment.SourceResolver; 033import org.apache.commons.lang3.StringUtils; 034import org.apache.commons.lang3.Strings; 035 036import org.ametys.cms.search.SearchResult; 037import org.ametys.cms.search.SearchResults; 038import org.ametys.cms.search.SortOrder; 039import org.ametys.cms.search.solr.SearcherFactory.SortDefinition; 040import org.ametys.core.cocoon.JSonReader; 041import org.ametys.core.user.UserManager; 042import org.ametys.core.util.I18nUtils; 043import org.ametys.core.util.JSONUtils; 044import org.ametys.plugins.core.user.UserHelper; 045import org.ametys.plugins.forms.dao.FormEntryDAO; 046import org.ametys.plugins.forms.helper.FormEntriesSearchHelper; 047import org.ametys.plugins.forms.helper.FormEntriesSearchHelper.FormEntryColumn; 048import org.ametys.plugins.forms.helper.LimitedEntriesHelper; 049import org.ametys.plugins.forms.question.types.impl.MatrixQuestionType; 050import org.ametys.plugins.forms.repository.Form; 051import org.ametys.plugins.forms.repository.FormEntry; 052import org.ametys.plugins.forms.repository.FormQuestion; 053import org.ametys.plugins.repository.AmetysObjectResolver; 054import org.ametys.plugins.repository.model.RepositoryDataContext; 055import org.ametys.plugins.workflow.support.WorkflowProvider; 056import org.ametys.runtime.model.Model; 057import org.ametys.runtime.model.ModelItem; 058import org.ametys.runtime.model.type.DataContext; 059 060/** 061 * Get the submitted entries of a form 062 * 063 */ 064public class GetFormEntriesAction extends ServiceableAction 065{ 066 /** Constant for whether an entry is in a queue or not */ 067 public static final String QUEUE_STATUS = "queue-status"; 068 069 /** The id of the column for entry active or not */ 070 public static final String FORM_ENTRY_ACTIVE = "active"; 071 072 /** The ametys object resolver. */ 073 protected AmetysObjectResolver _resolver; 074 075 /** The form entry DAO */ 076 protected FormEntryDAO _formEntryDAO; 077 078 /** The handle limited entries helper */ 079 protected LimitedEntriesHelper _handleLimitedEntriesHelper; 080 081 /** The user helper */ 082 protected UserHelper _userHelper; 083 084 /** The user manager */ 085 protected UserManager _userManager; 086 087 /** The workflow provider */ 088 protected WorkflowProvider _workflowProvider; 089 090 /** The json utils */ 091 protected JSONUtils _jsonUtils; 092 093 /** The I18n utils */ 094 protected I18nUtils _i18nUtils; 095 096 /** The form entries search helper */ 097 protected FormEntriesSearchHelper _formEntriesSearchHelper; 098 099 @Override 100 public void service(ServiceManager smanager) throws ServiceException 101 { 102 super.service(smanager); 103 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 104 _formEntryDAO = (FormEntryDAO) smanager.lookup(FormEntryDAO.ROLE); 105 _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE); 106 _userManager = (UserManager) smanager.lookup(UserManager.ROLE); 107 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 108 _handleLimitedEntriesHelper = (LimitedEntriesHelper) smanager.lookup(LimitedEntriesHelper.ROLE); 109 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 110 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 111 _formEntriesSearchHelper = (FormEntriesSearchHelper) smanager.lookup(FormEntriesSearchHelper.ROLE); 112 } 113 114 @Override 115 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 116 { 117 @SuppressWarnings("unchecked") 118 Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 119 120 String formId = (String) jsParameters.get("formId"); 121 Form form = _resolver.resolveById(formId); 122 if (form == null) 123 { 124 throw new ProcessingException("The form of ID '" + formId + " can't be found."); 125 } 126 127 Integer offset = _getIntValue(jsParameters, "start", 0); 128 Integer limit = _getIntValue(jsParameters, "limit", Integer.MAX_VALUE); 129 130 String sortInfo = (String) jsParameters.get("sort"); 131 String groupInfo = (String) jsParameters.get("group"); 132 List<SortDefinition> sorts = _getSorts(form, sortInfo, groupInfo); 133 134 Map<String, Object> matrixLabels = _getMatrixInfos(form); 135 136 Map<String, Object> result = new HashMap<>(); 137 List<Map<String, Object>> entries2json = new ArrayList<>(); 138 try 139 { 140 SearchResults<FormEntry> searchFormEntries = _formEntryDAO.searchFormEntries( 141 form.getSiteName(), 142 form, 143 false, // do not filter on active entries 144 false, // do not filter on entries with at least one available action for the current user 145 true, // only entries on which the current user has read rights 146 null, 147 List.of(), 148 sorts, 149 offset, 150 limit 151 ); 152 result.put("total", searchFormEntries.getTotalCount()); 153 for (SearchResult<FormEntry> searchResult : searchFormEntries.getResults()) 154 { 155 Map<String, Object> entryData = searchResultToJSON(searchResult, matrixLabels); 156 entries2json.add(entryData); 157 offset++; 158 } 159 } 160 catch (Exception e) 161 { 162 getLogger().error("Failed to get entries for form '" + form.getId() + "'.", e); 163 } 164 165 result.put("entries", entries2json); 166 167 Request request = ObjectModelHelper.getRequest(objectModel); 168 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 169 return EMPTY_MAP; 170 } 171 172 /** 173 * Transform a search result of form entry to a JSON map 174 * @param searchResult the search result of form entry 175 * @param matrixLabels the matrix labels 176 * @return the search result as JSON 177 * @throws Exception if an error occurs 178 */ 179 protected Map<String, Object> searchResultToJSON(SearchResult<FormEntry> searchResult, Map<String, Object> matrixLabels) throws Exception 180 { 181 FormEntry entry = searchResult.getObject(); 182 Form form = entry.getForm(); 183 184 Model entryModel = (Model) (entry.getModel().toArray())[0]; 185 Map<String, Object> entryData = new LinkedHashMap<>(); 186 for (ModelItem modelItem : entryModel.getModelItems()) 187 { 188 String name = modelItem.getName(); 189 FormQuestion question = form.getQuestion(name); 190 if (question != null) 191 { 192 // For question, call specific form API valueToJSONForClient 193 Object value = question.getType().valueToJSONForClient(entry.getValue(name), question, entry, modelItem); 194 if (value != null) 195 { 196 entryData.put(name, value); 197 } 198 } 199 else 200 { 201 DataContext context = RepositoryDataContext.newInstance() 202 .withObject(entry) 203 .withDataPath(modelItem.getPath()); 204 205 Object value = entry.dataToJSON(name, context); 206 if (value != null) 207 { 208 entryData.put(name, value); 209 } 210 } 211 212 if (matrixLabels.containsKey(name)) 213 { 214 entryData.put(name + "matrice-labels", matrixLabels.get(name)); 215 } 216 } 217 218 entryData.put(FormEntry.ATTRIBUTE_USER, _userHelper.user2json(entry.getUser(), true)); 219 220 if (form.isQueueEnabled() && entry.isActive()) 221 { 222 entryData.put(FormEntry.SYSTEM_ATTRIBUTE_PREFIX + QUEUE_STATUS, _handleLimitedEntriesHelper.isInQueue(entry)); 223 } 224 225 entryData.put(FormEntry.SYSTEM_ATTRIBUTE_PREFIX + FORM_ENTRY_ACTIVE, entry.isActive()); 226 entryData.put(FormEntry.SYSTEM_ATTRIBUTE_PREFIX + "entryId", entry.getId()); 227 return entryData; 228 } 229 230 /** 231 * Get sorts of search form entry 232 * @param form the form 233 * @param sortString the sort as string 234 * @param groupString the group as string 235 * @return the list of sort 236 */ 237 protected List<SortDefinition> _getSorts(Form form, String sortString, String groupString) 238 { 239 List<SortDefinition> sort = new ArrayList<>(); 240 241 List<Object> sortList = new ArrayList<>(_jsonUtils.convertJsonToList(sortString)); 242 if (StringUtils.isNotEmpty(groupString)) 243 { 244 // Grouping will be treated server side as a sort. It just needs to be before all the sorters 245 sortList.add(0, _jsonUtils.convertJsonToMap(groupString)); 246 } 247 248 String formId = form.getId(); 249 Map<String, FormEntryColumn> formEntryColumns = _formEntriesSearchHelper.getFormEntryColumns(form); 250 251 for (Object object : sortList) 252 { 253 if (object instanceof Map) 254 { 255 Map map = (Map) object; 256 String fieldId = (String) map.get("property"); 257 SortOrder order = "ASC".equals(map.get("direction")) ? SortOrder.ASC : SortOrder.DESC; 258 String questionName = Strings.CS.contains(fieldId, formId) ? StringUtils.substringAfter(fieldId, formId) : fieldId; 259 260 FormEntryColumn formEntryColumn = formEntryColumns.get(questionName); 261 if (formEntryColumn != null && formEntryColumn.sortFieldName() != null) 262 { 263 sort.add(new SortDefinition(formEntryColumn.sortFieldName(), order)); 264 } 265 } 266 } 267 268 return sort; 269 } 270 271 /** 272 * Get informations of matrix questions 273 * @param form the form 274 * @return the map of informations 275 */ 276 protected Map<String, Object> _getMatrixInfos(Form form) 277 { 278 Map<String, Object> matrixLabels = new HashMap<>(); 279 List<FormQuestion> matrixQuestions = form.getQuestions() 280 .stream() 281 .filter(q -> q.getType() instanceof MatrixQuestionType) 282 .toList(); 283 for (FormQuestion matrixQ : matrixQuestions) 284 { 285 MatrixQuestionType type = (MatrixQuestionType) matrixQ.getType(); 286 Map<String, String> columns = type.getColumns(matrixQ); 287 Map<String, String> rows = type.getRows(matrixQ); 288 Map<String, Map<String, String>> matrixInfo = (columns == null || rows == null) 289 ? Map.of() 290 : Map.of( 291 "columns", type.getColumns(matrixQ), 292 "rows", type.getRows(matrixQ) 293 ); 294 matrixLabels.put(matrixQ.getNameForForm(), matrixInfo); 295 } 296 return matrixLabels; 297 } 298 299 private int _getIntValue(Map<String, Object> values, String key, int defaultValue) 300 { 301 if (values.containsKey(key)) 302 { 303 return Integer.valueOf(values.get(key).toString()).intValue(); 304 } 305 306 return defaultValue; 307 } 308}