View Javadoc
1   /*
2    * Copyright (c) 2002-2025, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.forms.service.provider;
35  
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.Comparator;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  
43  import javax.servlet.http.HttpServletRequest;
44  
45  import fr.paris.lutece.plugins.forms.business.Form;
46  import fr.paris.lutece.plugins.forms.business.FormDisplay;
47  import fr.paris.lutece.plugins.forms.business.FormDisplayHome;
48  import fr.paris.lutece.plugins.forms.business.FormHome;
49  import fr.paris.lutece.plugins.forms.business.FormQuestionResponse;
50  import fr.paris.lutece.plugins.forms.business.FormQuestionResponseHome;
51  import fr.paris.lutece.plugins.forms.business.FormResponse;
52  import fr.paris.lutece.plugins.forms.business.Group;
53  import fr.paris.lutece.plugins.forms.business.GroupHome;
54  import fr.paris.lutece.plugins.forms.business.Question;
55  import fr.paris.lutece.plugins.forms.business.QuestionHome;
56  import fr.paris.lutece.plugins.forms.business.Step;
57  import fr.paris.lutece.plugins.forms.business.StepHome;
58  import fr.paris.lutece.plugins.forms.business.Transition;
59  import fr.paris.lutece.plugins.forms.business.TransitionHome;
60  import fr.paris.lutece.plugins.forms.service.StepService;
61  import fr.paris.lutece.plugins.forms.service.entrytype.EntryTypeAutomaticFileReading;
62  import fr.paris.lutece.plugins.forms.service.entrytype.EntryTypeCamera;
63  import fr.paris.lutece.plugins.forms.service.entrytype.EntryTypeComment;
64  import fr.paris.lutece.plugins.forms.service.entrytype.EntryTypeFile;
65  import fr.paris.lutece.plugins.forms.service.entrytype.EntryTypeGalleryImage;
66  import fr.paris.lutece.plugins.forms.service.entrytype.EntryTypeImage;
67  import fr.paris.lutece.plugins.forms.util.FormsConstants;
68  import fr.paris.lutece.plugins.genericattributes.business.Response;
69  import fr.paris.lutece.plugins.genericattributes.service.entrytype.EntryTypeServiceManager;
70  import fr.paris.lutece.plugins.genericattributes.service.entrytype.IEntryTypeService;
71  import fr.paris.lutece.plugins.workflowcore.service.provider.InfoMarker;
72  import fr.paris.lutece.portal.business.file.File;
73  import fr.paris.lutece.portal.business.file.FileHome;
74  import fr.paris.lutece.portal.business.physicalfile.PhysicalFile;
75  import fr.paris.lutece.portal.business.physicalfile.PhysicalFileHome;
76  import fr.paris.lutece.portal.service.file.FileService;
77  import fr.paris.lutece.portal.service.file.FileServiceException;
78  import fr.paris.lutece.portal.service.file.IFileStoreServiceProvider;
79  import fr.paris.lutece.portal.service.util.AppLogService;
80  import fr.paris.lutece.portal.service.util.AppPathService;
81  import fr.paris.lutece.portal.service.util.AppPropertiesService;
82  import fr.paris.lutece.util.url.UrlItem;
83  
84  public abstract class GenericFormsProvider {
85  
86  	// PROPERTY
87  	private static final String PROPERTY_EXPORT_IMAGES_IN_BASE64 = "forms.export.image.base64";
88  
89  	// MARKS
90  	private static final String MARK_POSITION = "position_";
91  	private static final String MARK_POSITION_ITERATION = "_";
92  	private static final String MARK_URL_ADMIN_RESPONSE = "url_admin_forms_response_detail";
93  	private static final String MARK_URL_FO_RESPONSE = "url_fo_forms_response_detail";
94  	private static final String MARK_CREATION_DATE = "creation_date";
95  	private static final String MARK_UPDATE_DATE = "update_date";
96  	private static final String MARK_STATUS = "status";
97  	private static final String MARK_STATUS_UPDATE_DATE = "update_date_status";
98  	private static final String MARK_BASE_URL = "base_url";
99  	private static final String MARKER_DESCRIPTION_BASE64 = "base64";
100 	private static final String MARKER_FORM_TITLE = "form_title";
101 	private static final String MARK_URL_FO_FILES_LINK = "url_fo_forms_files_link";
102 
103 	// URL PARAMETERS
104 	private static final String PARAMETER_VIEW_FORM_RESPONSE_DETAILS = "view_form_response_details";
105 	private static final String PARAMETER_VIEW_FORM_RESPONSE_DETAILS_FO = "formResponseView";
106 	private static final String PARAMETER_ID_FORM_RESPONSES = "id_form_response";
107 	private static final String PARAMETER_ID_FORM_RESPONSES_FO = "id_response";
108 	private static final String PARAMETER_PAGE_FORM_RESPONSE = "formsResponse";
109 	private static final String PARAMETER_VIEW_FORM_FILES_LINK_FO = "formFileView";
110 
111 	// Infomarkers description I18n keys
112 	private static final String MESSAGE_I18N_DESCRIPTION = "forms.marker.provider.url.admin.detail.reponse.description";
113 	private static final String MESSAGE_I18N_FO_DESCRIPTION = "forms.marker.provider.url.fo.detail.reponse.description";
114 	private static final String MESSAGE_I18N_CREATION_DATE = "forms.marker.provider.url.detail.reponse.creation_date";
115 	private static final String MESSAGE_I18N_UPDATE_DATE = "forms.marker.provider.url.detail.reponse.update_date";
116 	private static final String MESSAGE_I18N_STATUS = "forms.marker.provider.url.detail.reponse.status";
117 	private static final String MESSAGE_I18N_STATUS_UPDATE_DATE = "forms.marker.provider.url.detail.reponse.status_update_date";
118 	private static final String MESSAGE_I18N_FORM = "forms.marker.provider.url.detail.reponse.form";
119 	private static final String MESSAGE_I18N_FO_FILE_LINK = "forms.marker.provider.url.fo.file.link";
120 
121 	/**
122 	 * provide forms values as model (map)
123 	 *
124 	 * @param formResponse
125 	 * @param request
126 	 * @return the model map
127 	 */
128 	public static Map<String, Object> getValuesModel( FormResponse formResponse, HttpServletRequest request )
129 	{
130 		Map<String, Object> model = new HashMap<>( );
131 
132 		List<FormQuestionResponse> listFormQuestionResponse = FormQuestionResponseHome.getFormQuestionResponseListByFormResponse( formResponse.getId( ) );
133 		Boolean bExportImageBase64 = AppPropertiesService.getPropertyBoolean( PROPERTY_EXPORT_IMAGES_IN_BASE64, false);
134 
135 		for ( FormQuestionResponse formQuestionResponse : listFormQuestionResponse )
136 		{
137 			IEntryTypeService entryTypeService = EntryTypeServiceManager.getEntryTypeService(formQuestionResponse.getQuestion().getEntry());
138 
139 			if ( entryTypeService instanceof EntryTypeComment || entryTypeService instanceof EntryTypeImage || entryTypeService instanceof EntryTypeCamera || entryTypeService instanceof EntryTypeGalleryImage || entryTypeService instanceof EntryTypeFile || entryTypeService instanceof EntryTypeAutomaticFileReading)
140 			{
141 				List<fr.paris.lutece.plugins.genericattributes.business.Response> responses = formQuestionResponse.getEntryResponse( );
142 				for (int i = 0; i < responses.size(); i++)
143 				{
144 					if (responses.get(i).getFile() != null)
145 					{
146 						File file = FileHome.findByPrimaryKey( responses.get(i).getFile().getIdFile( ) );
147 						if (entryTypeService instanceof EntryTypeImage || entryTypeService instanceof EntryTypeCamera || entryTypeService instanceof EntryTypeGalleryImage)
148 						{
149 							// if property forms.export.image.base64 is true
150 							if (bExportImageBase64)
151 							{
152 								PhysicalFile physicalFile = PhysicalFileHome.findByPrimaryKey(file.getPhysicalFile().getIdPhysicalFile());
153 								byte[] bytes = physicalFile.getValue();
154 								String strBase64 = java.util.Base64.getEncoder().encodeToString(bytes);
155 								responses.get(i).setResponseValue(strBase64);
156 							}
157 						} else if (entryTypeService instanceof EntryTypeFile) {
158 
159 							if ( file != null && file.getPhysicalFile( ) == null )
160 							{
161 								try {
162 									IFileStoreServiceProvider fss = FileService.getInstance( ).getFileStoreServiceProvider( file.getOrigin() );
163 									file = fss.getFile(file.getFileKey() );
164 
165 									file.setUrl(fss.getFileDownloadUrlFO( file.getFileKey()));
166 
167 								}
168 								catch (FileServiceException e) {
169 									AppLogService.error("Error getting file from file store service provider", e);
170 								}
171 							}
172 						}
173 						responses.get(i).setFile(file);
174 					}
175 				}
176 			}
177 
178 			// in case of multiple FormQuestionResponse for the same question (when there is an iteration),
179 			// we merge them into one FormQuestionResponse and update the marker
180 			String strMultipleFormQuestionResponseKey = MARK_POSITION + formQuestionResponse.getQuestion().getId( ) ;
181 			if (model.containsKey( strMultipleFormQuestionResponseKey ) )
182 			{
183 				FormQuestionResponse existingFormQuestionResponse = (FormQuestionResponse) model.get( strMultipleFormQuestionResponseKey );
184 
185 				List<Response> existingResponses = existingFormQuestionResponse.getEntryResponse();
186 				List<Response> newResponses = formQuestionResponse.getEntryResponse();
187 				List<Response> allResponses = new ArrayList<>();
188 
189 				allResponses.addAll(existingResponses);
190 				allResponses.addAll(newResponses);
191 
192 				existingFormQuestionResponse.setEntryResponse(allResponses);
193 				model.replace( strMultipleFormQuestionResponseKey, existingFormQuestionResponse);
194 			}
195 			else
196 			{
197 				model.put( strMultipleFormQuestionResponseKey , formQuestionResponse);
198 			}
199 		}
200 
201 		// Additional markers
202 		model.put( MARKER_DESCRIPTION_BASE64, bExportImageBase64 );
203 		model.put( MARK_CREATION_DATE, formResponse.getCreation( )  );
204 		model.put( MARK_UPDATE_DATE, formResponse.getUpdate( ) );
205 		model.put( MARK_STATUS, formResponse.isPublished( ) );
206 		model.put( MARK_STATUS_UPDATE_DATE, formResponse.getUpdateStatus( )  );
207 		model.put( MARKER_FORM_TITLE, FormHome.findByPrimaryKey( formResponse.getFormId( ) ).getTitle( ) );
208 
209 		// request may be null in case of daemon execution
210 		if ( request != null )
211 		{
212 			model.put( MARK_BASE_URL, AppPathService.getBaseUrl( request ) );
213 
214 			UrlItem adminUrl = new UrlItem( AppPathService.getProdUrl( request ) + AppPathService.getAdminMenuUrl( ) );
215 			adminUrl.addParameter( FormsConstants.PARAMETER_TARGET_VIEW, PARAMETER_VIEW_FORM_RESPONSE_DETAILS );
216 			adminUrl.addParameter( PARAMETER_ID_FORM_RESPONSES, formResponse.getId( ) );
217 			model.put( MARK_URL_ADMIN_RESPONSE, adminUrl.getUrl( )  );
218 
219 			UrlItem url = new UrlItem( AppPathService.getProdUrl( request ) + AppPathService.getPortalUrl( ) );
220 			url.addParameter( FormsConstants.PARAMETER_PAGE, PARAMETER_PAGE_FORM_RESPONSE );
221 			url.addParameter( FormsConstants.PARAMETER_TARGET_VIEW, PARAMETER_VIEW_FORM_RESPONSE_DETAILS_FO );
222 			url.addParameter( PARAMETER_ID_FORM_RESPONSES_FO, formResponse.getId( ) );
223 			model.put( MARK_URL_FO_RESPONSE, url.getUrl( ) );
224 
225 			UrlItem urlFilesLinkFo = new UrlItem( AppPathService.getProdUrl( request ) + AppPathService.getPortalUrl( ) );
226 			urlFilesLinkFo.addParameter( FormsConstants.PARAMETER_PAGE, PARAMETER_PAGE_FORM_RESPONSE );
227 			urlFilesLinkFo.addParameter( FormsConstants.PARAMETER_TARGET_VIEW, PARAMETER_VIEW_FORM_FILES_LINK_FO );
228 			urlFilesLinkFo.addParameter( PARAMETER_ID_FORM_RESPONSES_FO, formResponse.getId( ) );
229 			model.put( MARK_URL_FO_FILES_LINK, url.getUrl( ) );
230 		}
231 
232 		return model;
233 	}
234 
235 
236 	/**
237 	 * Get the question titles as model, for the given form
238 	 *
239 	 * @param form
240 	 *            The form
241 	 * @return the map of markers
242 	 */
243 	public static Map<String, Object> getTitlesModel( Form form )
244 	{
245 		Map<String, Object> model = new HashMap<>( );
246 
247 		if (form != null )
248 		{
249 			List<Question> listFormQuestions = QuestionHome.getListQuestionByIdForm( form.getId( ) );
250 
251 			for ( Question formQuestion : listFormQuestions )
252 			{
253 				model.put( MARK_POSITION + formQuestion.getId( ), formQuestion.getTitle( ) );
254 			}
255 		}
256 
257 		return model;
258 	}
259 
260 	/**
261 	 * Get the reference list of available InfoMarkers
262 	 *
263 	 * @param form
264 	 *            The form
265 	 * @return the collection of the Markers
266 	 */
267 	public static Collection<InfoMarker> getProviderMarkerDescriptions( Form form )
268 	{
269 		Collection<InfoMarker> descriptionMarkersList = new ArrayList<>( );
270 
271 		if ( form != null )
272 		{
273 
274 			List<Step> listSteps = StepHome.getStepsListByForm( form.getId() );
275 			List<Transition> listTransitions = TransitionHome.getTransitionsListFromForm( form.getId() );
276 			listSteps = StepService.sortStepsWithTransitions( listSteps, listTransitions );
277 
278 			for ( Step step : listSteps ){
279 				List<FormDisplay> listFormDisplay = FormDisplayHome.getFormDisplayListByParent( step.getId(), 0 );
280 				listFormDisplay.sort( Comparator.comparingInt( FormDisplay::getDisplayOrder ) );
281 
282 				for ( FormDisplay composite : listFormDisplay )
283 				{
284 					if( FormsConstants.MARK_QUESTION.equals( composite.getCompositeType() ) )
285 					{
286 						descriptionMarkersList.addAll( buildMarkerForQuestion( QuestionHome.findByPrimaryKey( composite.getCompositeId() ) , null ));
287 					}
288 					else if( FormsConstants.MARK_GROUP.equals(composite.getCompositeType()) )
289 					{
290 						List<FormDisplay> listFormDisplayGroup = FormDisplayHome.getFormDisplayListByParent( composite.getStepId(), composite.getId() );
291 						listFormDisplayGroup.sort( Comparator.comparingInt( FormDisplay::getDisplayOrder ) );
292 
293 						Group group = GroupHome.findByPrimaryKey( composite.getCompositeId() );
294 
295 						for ( FormDisplay compositeGroup : listFormDisplayGroup)
296 						{
297 							if( FormsConstants.MARK_QUESTION.equals(compositeGroup.getCompositeType()) )
298 							{
299 								descriptionMarkersList.addAll( buildMarkerForQuestion( QuestionHome.findByPrimaryKey( compositeGroup.getCompositeId() ) , group ));
300 							}
301 						}
302 					}
303 				}
304 			}
305 		}
306 
307 		InfoMarker markerAdminURl = new InfoMarker( MARK_URL_ADMIN_RESPONSE );
308 		markerAdminURl.setDescription( MESSAGE_I18N_DESCRIPTION );
309 		InfoMarker markerFoUrl = new InfoMarker( MARK_URL_FO_RESPONSE );
310 		markerFoUrl.setDescription( MESSAGE_I18N_FO_DESCRIPTION );
311 		InfoMarker creationDateMarker = new InfoMarker( MARK_CREATION_DATE );
312 		creationDateMarker.setDescription( MESSAGE_I18N_CREATION_DATE );
313 		InfoMarker updateDateMarker = new InfoMarker( MARK_UPDATE_DATE );
314 		updateDateMarker.setDescription( MESSAGE_I18N_UPDATE_DATE );
315 		InfoMarker statusMarker = new InfoMarker( MARK_STATUS );
316 		statusMarker.setDescription( MESSAGE_I18N_STATUS );
317 		InfoMarker updateStatusDateMarker = new InfoMarker( MARK_STATUS_UPDATE_DATE );
318 		updateStatusDateMarker.setDescription( MESSAGE_I18N_STATUS_UPDATE_DATE );
319 		InfoMarker formTitleMarker = new InfoMarker( MARKER_FORM_TITLE );
320 		formTitleMarker.setDescription( MESSAGE_I18N_FORM );
321 		InfoMarker markerFoFileUrl = new InfoMarker( MARK_URL_FO_FILES_LINK );
322 		markerFoFileUrl.setDescription( MESSAGE_I18N_FO_FILE_LINK );
323 
324 		descriptionMarkersList.add( markerAdminURl );
325 		descriptionMarkersList.add( markerFoUrl );
326 		descriptionMarkersList.add( creationDateMarker );
327 		descriptionMarkersList.add( updateDateMarker );
328 		descriptionMarkersList.add( statusMarker );
329 		descriptionMarkersList.add( updateStatusDateMarker );
330 		descriptionMarkersList.add( formTitleMarker );
331 		descriptionMarkersList.add( markerFoFileUrl );
332 
333 		return descriptionMarkersList;
334 	}
335 
336 	private static Collection<InfoMarker> buildMarkerForQuestion(Question question, Group group)
337 	{
338 		Collection<InfoMarker> descriptionMarkersList = new ArrayList<>( );
339 
340 		if( group == null || group.getIterationMax() == 1 )
341 		{
342 			InfoMarker marker = new InfoMarker( MARK_POSITION + question.getId( ) );
343 			marker.setDescription( question.getTitle( ) );
344 			descriptionMarkersList.add(marker);
345 		}
346 		else
347 		{
348 			for (int i = 0 ; i < group.getIterationMax(); i++ )
349 			{
350 				InfoMarker marker = new InfoMarker( MARK_POSITION + question.getId( ) + MARK_POSITION_ITERATION + i );
351 				marker.setDescription( question.getTitle( ) + " " + ( i + 1 ) );
352 				descriptionMarkersList.add(marker);
353 			}
354 		}
355 		return descriptionMarkersList;
356 	}
357 
358 }