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.helper; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.Comparator; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Optional; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.cocoon.environment.Request; 031import org.apache.commons.lang.StringUtils; 032 033import org.ametys.core.observation.Event; 034import org.ametys.core.observation.ObservationManager; 035import org.ametys.core.ui.Callable; 036import org.ametys.core.user.CurrentUserProvider; 037import org.ametys.core.user.UserIdentity; 038import org.ametys.plugins.forms.FormEvents; 039import org.ametys.plugins.forms.dao.FormDAO; 040import org.ametys.plugins.forms.repository.Form; 041import org.ametys.plugins.forms.repository.FormEntry; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.runtime.config.Config; 044import org.ametys.runtime.plugin.component.AbstractLogEnabled; 045 046/** 047 * The helper to handler limited entries 048 */ 049public class LimitedEntriesHelper extends AbstractLogEnabled implements Serviceable, Component 050{ 051 /** Avalon ROLE. */ 052 public static final String ROLE = LimitedEntriesHelper.class.getName(); 053 054 /** Ametys object resolver. */ 055 protected AmetysObjectResolver _resolver; 056 057 /** The form mail helper */ 058 protected FormMailHelper _formMailHelper; 059 060 /** The form DAO */ 061 protected FormDAO _formDAO; 062 063 /** The current user provider */ 064 protected CurrentUserProvider _currentUserProvider; 065 066 /** Observer manager. */ 067 protected ObservationManager _observationManager; 068 069 public void service(ServiceManager manager) throws ServiceException 070 { 071 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 072 _formMailHelper = (FormMailHelper) manager.lookup(FormMailHelper.ROLE); 073 _formDAO = (FormDAO) manager.lookup(FormDAO.ROLE); 074 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 075 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 076 } 077 078 /** 079 * Get the form properties relevant for limiting entries number 080 * @param formId Id of the current form 081 * @return a map of the form properties 082 */ 083 @Callable 084 public Map<String, Object> getLimitedEntriesProperties(String formId) 085 { 086 Map<String, Object> limitProperties = new HashMap<> (); 087 088 Form form = _resolver.resolveById(formId); 089 _formDAO.checkHandleFormRight(form); 090 091 limitProperties.put(Form.LIMIT_TO_ONE_ENTRY_BY_USER, form.isLimitedToOneEntryByUser()); 092 limitProperties.put(Form.LIMIT_ENTRIES_ENABLED, form.isEntriesLimited()); 093 _addOptionalProperty(Form.MAX_ENTRIES, form.getMaxEntries(), limitProperties, null); 094 _addOptionalProperty(Form.REMAINING_MSG, form.getRemainingMessage(), limitProperties, null); 095 _addOptionalProperty(Form.CLOSED_MSG, form.getClosedMessage(), limitProperties, null); 096 097 limitProperties.put(Form.QUEUE_ENABLED, form.isQueueEnabled()); 098 _addOptionalProperty(Form.QUEUE_SIZE, form.getQueueSize(), limitProperties, null); 099 _addOptionalProperty(Form.QUEUE_CLOSED_MSG, form.getClosedQueueMessage(), limitProperties, null); 100 _addOptionalProperty(Form.QUEUE_SENDER, form.getQueueMailSender(), limitProperties, Config.getInstance().getValue("smtp.mail.from")); 101 _addOptionalProperty(Form.QUEUE_RECEIVER, form.getQueueMailReceiver(), limitProperties, FormMailHelper.RECEIVER_COMBOBOX_ENTRY_USER_VALUE); 102 _addOptionalProperty(Form.QUEUE_SUBJECT, form.getQueueMailSubject(), limitProperties, null); 103 _addOptionalProperty(Form.QUEUE_BODY, form.getQueueMailBody(), limitProperties, null); 104 105 return limitProperties; 106 } 107 108 private void _addOptionalProperty(String propertyName, Optional<? extends Object> value, Map<String, Object> limitProperties, Object defaultValue) 109 { 110 if (value.isPresent()) 111 { 112 limitProperties.put(propertyName, value.get()); 113 } 114 else if (defaultValue != null) 115 { 116 limitProperties.put(propertyName, defaultValue); 117 } 118 } 119 120 /** 121 * Set the form properties relevant for limiting entries number 122 * @param limitParameters the limit parameters 123 * @return the map of results 124 */ 125 @Callable 126 public Map<String, Object> setEntriesLimitProperties (Map<String, Object> limitParameters) 127 { 128 Map<String, Object> result = new HashMap<>(); 129 130 Form form = _resolver.resolveById((String) limitParameters.get("formId")); 131 _formDAO.checkHandleFormRight(form); 132 133 form.limitToOneEntryByUser((boolean) limitParameters.get(Form.LIMIT_TO_ONE_ENTRY_BY_USER)); 134 if ((boolean) limitParameters.get(Form.LIMIT_ENTRIES_ENABLED)) 135 { 136 form.limitEntries(true); 137 form.setMaxEntries(((Integer) limitParameters.get(Form.MAX_ENTRIES)).longValue()); 138 form.setRemainingMessage((String) limitParameters.get(Form.REMAINING_MSG)); 139 form.setClosedMessage((String) limitParameters.get(Form.CLOSED_MSG)); 140 141 if ((boolean) limitParameters.get(Form.QUEUE_ENABLED)) 142 { 143 form.enableQueue(true); 144 Object queueSize = limitParameters.get(Form.QUEUE_SIZE); 145 if (queueSize != null) 146 { 147 form.setQueueSize(((Integer) queueSize).longValue()); 148 } 149 else 150 { 151 form.removeValue(Form.QUEUE_SIZE); 152 } 153 form.setClosedQueueMessage((String) limitParameters.get(Form.QUEUE_CLOSED_MSG)); 154 form.setQueueMailtReceiver((String) limitParameters.get(Form.QUEUE_RECEIVER)); 155 form.setQueueMailSender((String) limitParameters.get(Form.QUEUE_SENDER)); 156 form.setQueueMailSubject((String) limitParameters.get(Form.QUEUE_SUBJECT)); 157 form.setQueueMailBody((String) limitParameters.get(Form.QUEUE_BODY)); 158 } 159 else 160 { 161 form.enableQueue(false); 162 } 163 } 164 else 165 { 166 form.limitEntries(false); 167 } 168 form.saveChanges(); 169 170 Map<String, Object> eventParams = new HashMap<>(); 171 eventParams.put("form", form); 172 _observationManager.notify(new Event(FormEvents.FORM_MODIFIED, _currentUserProvider.getUser(), eventParams)); 173 174 return result; 175 } 176 177 /** 178 * <code>true</code> if the user can submit the form depending of the form limitation 179 * @param form the form 180 * @param user the user. Can be null if the form is anonymous 181 * @param clientIp the client ip of the user 182 * @return <code>true</code> if the user can submit the form depending of the form limitation 183 */ 184 public boolean canUserSubmit(Form form, UserIdentity user, String clientIp) 185 { 186 // Check if the form is limited to one entry by user and if the user has already answer 187 if (form.isLimitedToOneEntryByUser() && hasUserAlreadyAnswer(form, user, clientIp)) 188 { 189 return false; 190 } 191 192 // Check if the number of entries is limited 193 Optional<Long> maxEntriesOpt = form.getMaxEntries(); 194 if (form.isEntriesLimited() && maxEntriesOpt.isPresent()) 195 { 196 Optional<Long> queueSize = form.getQueueSize(); 197 int entriesSize = form.getActiveEntries().size(); 198 // If there are a queue ... 199 if (form.isQueueEnabled()) 200 { 201 // ... return true if the queue has no limit 202 // or check the entries size 203 return queueSize.isEmpty() || entriesSize < (maxEntriesOpt.get() + queueSize.get()); 204 } 205 else 206 { 207 // Just check the entries size with the limit of entries 208 return entriesSize < maxEntriesOpt.get(); 209 } 210 } 211 212 // Return true if no limitation 213 return true; 214 } 215 216 /** 217 * <code>true</code> if the user has already answer to the form 218 * @param form the form 219 * @param user the user. Can be null if the form is anonymous 220 * @param clientIp the client ip of the user 221 * @return <code>true</code> if the user has already answer to the form 222 */ 223 public boolean hasUserAlreadyAnswer(Form form, UserIdentity user, String clientIp) 224 { 225 return form.getEntries() 226 .stream() 227 .anyMatch(e -> user != null && user.equals(e.getUser()) || user == null && clientIp != null && clientIp.equals(e.getIP())); 228 } 229 230 /** 231 * Get a forwarded client IP address if available. 232 * @param request The servlet request object. 233 * @return The HTTP <code>X-Forwarded-For</code> header value if present, 234 * or the default remote address if not. 235 */ 236 public String getClientIp(Request request) 237 { 238 String ip = ""; 239 String forwardedIpHeader = request.getHeader("X-Forwarded-For"); 240 241 if (StringUtils.isNotEmpty(forwardedIpHeader)) 242 { 243 String[] forwardedIps = forwardedIpHeader.split("[\\s,]+"); 244 String forwardedIp = forwardedIps[forwardedIps.length - 1]; 245 if (StringUtils.isNotBlank(forwardedIp)) 246 { 247 ip = forwardedIp.trim(); 248 } 249 } 250 251 if (StringUtils.isBlank(ip)) 252 { 253 ip = request.getRemoteAddr(); 254 } 255 return ip; 256 } 257 258 /** 259 * <code>true</code> if the entry is in queue 260 * @param entry the entry 261 * @return <code>true</code> if the entry is in queue 262 */ 263 public boolean isInQueue(FormEntry entry) 264 { 265 Form form = entry.getForm(); 266 return form.isQueueEnabled() && _getQueue(form).contains(entry); 267 } 268 269 /** 270 * Deactivate an entry handling the form limitation 271 * @param entryId the entry id 272 */ 273 public void deactivateEntry(String entryId) 274 { 275 FormEntry entryToDeactivate = _resolver.resolveById(entryId); 276 if (entryToDeactivate.isActive()) 277 { 278 Form form = entryToDeactivate.getForm(); 279 if (form.isQueueEnabled()) 280 { 281 List<FormEntry> queue = _getQueue(form); 282 // The entry is on the main list 283 if (!queue.isEmpty() && !queue.contains(entryToDeactivate)) 284 { 285 _formMailHelper.sendOutOfQueueMail(form, queue.get(0)); 286 } 287 } 288 289 // Deactivate the entry after calculating queue 290 entryToDeactivate.setActive(false); 291 } 292 } 293 294 /** 295 * Get the waiting list sort by the submiting date 296 * @param form the form 297 * @return the waiting list 298 */ 299 protected List<FormEntry> _getQueue(Form form) 300 { 301 List<FormEntry> queuedEntries = new ArrayList<>(); 302 303 Optional<Long> maxEntries = form.getMaxEntries(); 304 List<FormEntry> activeEntries = form.getActiveEntries(); 305 Collections.sort(activeEntries, Comparator.comparing(e -> e.getSubmitDate())); 306 307 for (int i = maxEntries.get().intValue(); i < activeEntries.size(); i++) 308 { 309 queuedEntries.add(activeEntries.get(i)); 310 } 311 312 return queuedEntries; 313 } 314 315 /** 316 * <code>true</code> if the form limit is reach 317 * @param form the form 318 * @return <code>true</code> if the form limit is reach 319 */ 320 public boolean isFormLimitIsReached(Form form) 321 { 322 Optional<Long> maxEntries = form.getMaxEntries(); 323 if (form.isEntriesLimited() && maxEntries.isPresent()) 324 { 325 List<FormEntry> activeEntries = form.getActiveEntries(); 326 return activeEntries.size() >= maxEntries.get(); 327 } 328 329 return false; 330 } 331}