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