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.dao;
017
018import java.time.LocalDate;
019import java.time.ZonedDateTime;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import javax.jcr.Node;
029import javax.jcr.Repository;
030import javax.jcr.RepositoryException;
031
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.commons.lang.StringUtils;
037
038import org.ametys.core.observation.Event;
039import org.ametys.core.observation.ObservationManager;
040import org.ametys.core.right.RightManager;
041import org.ametys.core.right.RightManager.RightResult;
042import org.ametys.core.ui.Callable;
043import org.ametys.core.user.CurrentUserProvider;
044import org.ametys.core.user.UserIdentity;
045import org.ametys.core.util.DateUtils;
046import org.ametys.core.util.I18nUtils;
047import org.ametys.plugins.core.user.UserHelper;
048import org.ametys.plugins.forms.FormEvents;
049import org.ametys.plugins.forms.helper.ScheduleOpeningHelper;
050import org.ametys.plugins.forms.repository.Form;
051import org.ametys.plugins.forms.repository.FormDirectory;
052import org.ametys.plugins.forms.repository.FormEntry;
053import org.ametys.plugins.forms.repository.FormFactory;
054import org.ametys.plugins.forms.repository.FormQuestion;
055import org.ametys.plugins.repository.AmetysObject;
056import org.ametys.plugins.repository.AmetysObjectIterable;
057import org.ametys.plugins.repository.AmetysObjectResolver;
058import org.ametys.plugins.repository.ModifiableAmetysObject;
059import org.ametys.plugins.repository.UnknownAmetysObjectException;
060import org.ametys.plugins.repository.jcr.NameHelper;
061import org.ametys.plugins.repository.provider.AbstractRepository;
062import org.ametys.plugins.workflow.support.WorkflowHelper;
063import org.ametys.runtime.i18n.I18nizableText;
064import org.ametys.runtime.model.ElementDefinition;
065import org.ametys.runtime.plugin.component.AbstractLogEnabled;
066import org.ametys.web.parameters.view.ViewParametersManager;
067import org.ametys.web.repository.page.ModifiableZoneItem;
068import org.ametys.web.repository.page.Page;
069import org.ametys.web.repository.page.SitemapElement;
070import org.ametys.web.repository.page.ZoneDAO;
071import org.ametys.web.repository.page.ZoneItem;
072import org.ametys.web.repository.site.SiteManager;
073import org.ametys.web.service.Service;
074import org.ametys.web.service.ServiceExtensionPoint;
075
076/**
077 * The form DAO
078 */
079public class FormDAO extends AbstractLogEnabled implements Serviceable, Component
080{
081    /** The Avalon role */
082    public static final String ROLE = FormDAO.class.getName();
083    /** The right id to handle forms */
084    public static final String HANDLE_FORMS_RIGHT_ID = "Plugins_Forms_Right_Handle";
085
086    private static final String __FORM_NAME_PREFIX = "form-";
087    
088    /** The Ametys object resolver */
089    protected AmetysObjectResolver _resolver;
090    /** The current user provider */
091    protected CurrentUserProvider _userProvider;
092    /** I18n Utils */
093    protected I18nUtils _i18nUtils;
094    /** The form directory DAO */
095    protected FormDirectoryDAO _formDirectoryDAO;
096    /** The form page DAO */
097    protected FormPageDAO _formPageDAO;
098    /** The form entry DAO */
099    protected FormEntryDAO _formEntryDAO;
100    /** The user helper */
101    protected UserHelper _userHelper;
102    /** The JCR repository. */
103    protected Repository _repository;
104    /** The right manager */
105    protected RightManager _rightManager;
106    /** The service extension point */
107    protected ServiceExtensionPoint _serviceEP;
108    /** The zone DAO */
109    protected ZoneDAO _zoneDAO;
110    /** The schedule opening helper */
111    protected ScheduleOpeningHelper _scheduleOpeningHelper;
112    /** The workflow helper */
113    protected WorkflowHelper _workflowHelper;
114    /** The site manager */
115    protected SiteManager _siteManager;
116    /** Observer manager. */
117    protected ObservationManager _observationManager;
118    /** The current user provider. */
119    protected CurrentUserProvider _currentUserProvider;
120    
121    public void service(ServiceManager manager) throws ServiceException
122    {
123        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
124        _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
125        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
126        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
127        _formDirectoryDAO = (FormDirectoryDAO) manager.lookup(FormDirectoryDAO.ROLE);
128        _formPageDAO = (FormPageDAO) manager.lookup(FormPageDAO.ROLE);
129        _formEntryDAO = (FormEntryDAO) manager.lookup(FormEntryDAO.ROLE);
130        _repository = (Repository) manager.lookup(AbstractRepository.ROLE);
131        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
132        _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
133        _zoneDAO = (ZoneDAO) manager.lookup(ZoneDAO.ROLE);
134        _scheduleOpeningHelper = (ScheduleOpeningHelper) manager.lookup(ScheduleOpeningHelper.ROLE);
135        _workflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE);
136        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
137        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
138        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
139        
140    }
141
142    /**
143     * Check if a user have read rights on a form
144     * @param userIdentity the user
145     * @param form the form
146     * @return true if the user have read rights on a form
147     */
148    public boolean hasReadRightOnForm(UserIdentity userIdentity, Form form)
149    {
150        return _rightManager.hasReadAccess(userIdentity, form);
151    }
152    
153    /**
154     * Check if a user have write rights on a form element
155     * @param userIdentity the user
156     * @param formElement the form element
157     * @return true if the user have write rights on a form element
158     */
159    public boolean hasWriteRightOnForm(UserIdentity userIdentity, AmetysObject formElement)
160    {
161        return _rightManager.hasRight(userIdentity, HANDLE_FORMS_RIGHT_ID, formElement) == RightResult.RIGHT_ALLOW;
162    }
163    
164    /**
165     * Check if a user have write rights on a form
166     * @param userIdentity the user
167     * @param form the form
168     * @return true if the user have write rights on a form
169     */
170    public boolean hasRightAffectationRightOnForm(UserIdentity userIdentity, Form form)
171    {
172        return hasWriteRightOnForm(userIdentity, form) || _rightManager.hasRight(userIdentity, "Runtime_Rights_Rights_Handle", "/cms") == RightResult.RIGHT_ALLOW;
173    }
174    
175    /**
176     * Check rights for a form element as ametys object
177     * @param formElement the form element as ametys object
178     */
179    public void checkHandleFormRight(AmetysObject formElement)
180    {
181        UserIdentity user = _userProvider.getUser();
182        if (!hasWriteRightOnForm(user, formElement))
183        {
184            throw new IllegalAccessError("User '" + user + "' tried to handle forms without convenient right [" + HANDLE_FORMS_RIGHT_ID + "]");
185        }
186    }
187    
188    /**
189     * Get all forms from a site
190     * @param siteName the site name
191     * @return the list of form
192     */
193    public List<Form> getForms(String siteName)
194    {
195        String xpathQuery = "//element(" + siteName + ", ametys:site)//element(*, ametys:form)";
196        return _resolver.query(xpathQuery)
197            .stream()
198            .filter(Form.class::isInstance)
199            .map(Form.class::cast)
200            .collect(Collectors.toList());
201    }
202    
203    /**
204     * Get the form properties
205     * @param formId The form's id
206     * @param full <code>true</code> to get full information on form
207     * @param withRights <code>true</code> to have rights in the properties
208     * @return The form properties
209     */
210    @Callable
211    public Map<String, Object> getFormProperties (String formId, boolean full, boolean withRights)
212    {
213        try
214        {
215            Form form = _resolver.resolveById(formId);
216            return getFormProperties(form, full, true);
217        }
218        catch (UnknownAmetysObjectException e)
219        {
220            getLogger().warn("Can't find form with id: {}. It probably has just been deleted", formId, e);
221            Map<String, Object> infos = new HashMap<>();
222            infos.put("id", formId);
223            return infos;
224        }
225    }
226    
227    /**
228     * Get the form properties
229     * @param form The form
230     * @param full <code>true</code> to get full information on form
231     * @param withRights <code>true</code> to have rights in the properties
232     * @return The form properties
233     */
234    public Map<String, Object> getFormProperties (Form form, boolean full, boolean withRights)
235    {
236        Map<String, Object> infos = new HashMap<>();
237        
238        List<FormEntry> entries = form.getEntries();
239        List<SitemapElement> pages = getFormPage(form.getId(), form.getSiteName());
240        String workflowName = form.getWorkflowName();
241
242        infos.put("type", "root");
243        infos.put("isForm", true);
244        infos.put("author", _userHelper.user2json(form.getAuthor(), true)); 
245        infos.put("contributor", _userHelper.user2json(form.getContributor())); 
246        infos.put("lastModificationDate", DateUtils.zonedDateTimeToString(form.getLastModificationDate()));
247        infos.put("creationDate", DateUtils.zonedDateTimeToString(form.getCreationDate()));
248        infos.put("entriesAmount", entries.size());
249        infos.put("lastEntry", _getLastSubmissionDate(entries));
250        infos.put("workflowLabel", StringUtils.isNotBlank(workflowName) ? _workflowHelper.getWorkflowLabel(workflowName) : new I18nizableText("plugin.forms", "PLUGINS_FORMS_FORMS_EDITOR_WORKFLOW_NO_WORKFLOW"));
251        
252        /** Use in the bus message */
253        infos.put("id", form.getId());
254        infos.put("name", form.getName());
255        infos.put("title", form.getTitle());
256        infos.put("fullPath", getFormFullPath(form.getId()));
257        infos.put("pages", _getPagesInfos(pages));
258        infos.put("hasChildren", form.getPages().size() > 0);
259        infos.put("workflowName", workflowName);
260        
261        infos.put("isConfigured", isFormConfigured(form));
262        
263        UserIdentity currentUser = _userProvider.getUser();
264        if (withRights)
265        {
266            Set<String> userRights = _getUserRights(form);
267            infos.put("rights", userRights);
268            infos.put("canEditRight", userRights.contains(HANDLE_FORMS_RIGHT_ID) || _rightManager.hasRight(currentUser, "Runtime_Rights_Rights_Handle", "/cms") == RightResult.RIGHT_ALLOW);
269        }
270        else
271        {
272            boolean canWrite = hasWriteRightOnForm(currentUser, form);
273            infos.put("canWrite", canWrite);
274            infos.put("canEditRight", canWrite || _rightManager.hasRight(currentUser, "Runtime_Rights_Rights_Handle", "/cms") == RightResult.RIGHT_ALLOW);
275            infos.put("canRead", hasReadRightOnForm(currentUser, form));
276        }
277        
278        if (full)
279        {
280            infos.put("isPublished", !pages.isEmpty());
281            infos.put("hasEntries", form.hasEntries());
282            infos.put("nbEntries", form.getActiveEntries().size());
283            
284            FormDirectory formDirectoriesRoot = _formDirectoryDAO.getFormDirectoriesRootNode(form.getSiteName());
285            String parentId = form.getParent().getId().equals(formDirectoriesRoot.getId()) ? FormDirectoryDAO.ROOT_FORM_DIRECTORY_ID : form.getParent().getId();
286            infos.put("parentId", parentId);
287            
288            infos.put("isLimitedToOneEntryByUser", form.isLimitedToOneEntryByUser());
289            infos.put("isEntriesLimited", form.isEntriesLimited());
290            Optional<Long> maxEntries = form.getMaxEntries();
291            if (maxEntries.isPresent())
292            {
293                infos.put("maxEntries", maxEntries.get());
294            }
295            infos.put("isQueueEnabled", form.isQueueEnabled());
296            Optional<Long> queueSize = form.getQueueSize();
297            if (queueSize.isPresent())
298            {
299                infos.put("queueSize", queueSize.get());
300            }
301            
302            LocalDate startDate = form.getStartDate();
303            LocalDate endDate = form.getEndDate();
304            if (startDate != null || endDate != null)
305            {
306                infos.put("scheduleStatus", _scheduleOpeningHelper.getStatus(form));
307                if (startDate != null)
308                {
309                    infos.put("startDate", DateUtils.localDateToString(startDate));
310                }
311                if (endDate != null)
312                {
313                    infos.put("endDate", DateUtils.localDateToString(endDate));
314                }
315            }
316
317            infos.put("receiptAcknowledgement", form.hasValue(Form.RECEIPT_SENDER));
318            infos.put("isAnonymous", _rightManager.hasAnonymousReadAccess(form));
319        }
320        
321        return infos;
322    }
323    
324    /**
325     * Get the form title
326     * @param formId the form id
327     * @return the form title
328     */
329    @Callable
330    public String getFormTitle(String formId)
331    {
332        Form form = _resolver.resolveById(formId);
333        return form.getTitle();
334    }
335    
336    /**
337     * Get the form full path
338     * @param formId the form id
339     * @return the form full path
340     */
341    @Callable
342    public String getFormFullPath(String formId)
343    {
344        Form form = _resolver.resolveById(formId);
345        
346        String separator = " > ";
347        String fullPath = form.getTitle();
348        
349        FormDirectory parent = form.getParent();
350        if (!_formDirectoryDAO.isRoot(parent))
351        {
352            fullPath = _formDirectoryDAO.getFormDirectoryPath(parent, separator) + separator + fullPath;
353        }
354        
355        return fullPath;
356    }
357    
358    /**
359     * Get user rights for the given form
360     * @param form the form
361     * @return the set of rights
362     */
363    protected Set<String> _getUserRights (Form form)
364    {
365        UserIdentity user = _userProvider.getUser();
366        return _rightManager.getUserRights(user, form);
367    }
368    
369    /**
370     * Creates a {@link Form}.
371     * @param siteName The site name
372     * @param parentId The id of the parent.
373     * @param name  name The desired name for the new {@link Form}
374     * @return The id of the created form
375     * @throws Exception if an error occurs during the form creation process
376     */
377    @Callable
378    public Map<String, String> createForm (String siteName, String parentId, String name) throws Exception
379    {
380        Map<String, String> result = new HashMap<>();
381        
382        FormDirectory parentDirectory = _formDirectoryDAO.getFormDirectory(siteName, parentId);
383        _formDirectoryDAO.checkHandleFormDirectoriesRight(parentDirectory);
384        
385        String uniqueName = _findUniqueName(parentDirectory, name);
386        Form form = parentDirectory.createChild(uniqueName, FormFactory.FORM_NODETYPE);
387        
388        form.setTitle(name);
389        form.setAuthor(_userProvider.getUser());
390        form.setCreationDate(ZonedDateTime.now());
391        form.setLastModificationDate(ZonedDateTime.now());
392        
393        parentDirectory.saveChanges();
394        String formId = form.getId();
395        
396        _formPageDAO.createPage(formId, _i18nUtils.translate(new I18nizableText("plugin.forms", "PLUGINS_FORMS_CREATE_PAGE_DEFAULT_NAME")));
397        
398        result.put("id", formId);
399        result.put("name", form.getTitle());
400        
401        return result;
402    }
403    
404    private String _findUniqueName(FormDirectory parent, String name)
405    {
406        String legalName;
407        try
408        {
409            legalName = NameHelper.filterName(name);
410        }
411        catch (IllegalArgumentException e)
412        {
413            legalName = NameHelper.filterName(__FORM_NAME_PREFIX + name);
414        }
415        
416        // Find unique name from legal name
417        String uniqueName = legalName;
418        int index = 2;
419        while (parent.hasChild(uniqueName))
420        {
421            uniqueName = legalName + "-" + (index++);
422        }
423        
424        return uniqueName;
425    }
426    
427    /**
428     * Rename a {@link Form}
429     * @param id The id of the form 
430     * @param newName The new name for the form
431     * @return A result map
432     */
433    @Callable
434    public Map<String, String> renameForm (String id, String newName)
435    {
436        Map<String, String> results = new HashMap<>();
437        
438        Form form = _resolver.resolveById(id);
439        checkHandleFormRight(form);
440        
441        String uniqueName = _findUniqueName(form.getParent(), newName);
442        Node node = form.getNode();
443        try
444        {
445            form.setTitle(newName);
446            form.setContributor(_userProvider.getUser());
447            form.setLastModificationDate(ZonedDateTime.now());
448            
449            node.getSession().move(node.getPath(), node.getParent().getPath() + '/' + uniqueName);
450            node.getSession().save();
451            
452            results.put("newName", form.getTitle());
453        }
454        catch (RepositoryException e)
455        {
456            getLogger().warn("Form renaming failed.", e);
457            results.put("message", "cannot-rename");
458        }
459        
460        results.put("id", id);
461        return results;
462    }
463    
464    /**
465     * Copies and pastes a form.
466     * @param formDirectoryId The id of the form directory target of the copy
467     * @param formId The id of the form to copy
468     * @return The results
469     */
470    @Callable
471    public Map<String, String> copyForm(String formDirectoryId, String formId)
472    {
473        Map<String, String> result = new HashMap<>();
474        
475        Form originalForm = _resolver.resolveById(formId);
476        FormDirectory parentFormDirectory = _resolver.resolveById(formDirectoryId);
477        _formDirectoryDAO.checkHandleFormDirectoriesRight(parentFormDirectory);
478        
479        String uniqueName = _findUniqueName(parentFormDirectory, originalForm.getTitle());
480        
481        Form cForm = originalForm.copyTo(parentFormDirectory, uniqueName);
482        originalForm.copyTo(cForm);
483        
484        String copyTitle = _i18nUtils.translate(new I18nizableText("plugin.forms", "PLUGIN_FORMS_TREE_COPY_NAME_PREFIX")) + originalForm.getTitle();
485        cForm.setTitle(copyTitle);
486        cForm.setAuthor(_userProvider.getUser());
487        cForm.setCreationDate(ZonedDateTime.now());
488        cForm.setLastModificationDate(ZonedDateTime.now());
489        
490        originalForm.saveChanges();
491        
492        result.put("id", cForm.getId());
493        
494        return result;
495    }
496    
497    /**
498     * Deletes a {@link Form}.
499     * @param id The id of the form to delete
500     * @return The id of the form 
501     */
502    @Callable
503    public Map<String, String> deleteForm (String id)
504    {
505        Map<String, String> result = new HashMap<>();
506        
507        Form form = _resolver.resolveById(id);
508        checkHandleFormRight(form);
509        
510        List<SitemapElement> pages = getFormPage(form.getId(), form.getSiteName());
511        if (!pages.isEmpty())
512        {
513            throw new IllegalAccessError("Can't delete form ('" + form.getId() + "') which contains pages");
514        }
515
516        ModifiableAmetysObject parent = form.getParent();
517        form.remove();
518        parent.saveChanges();
519        
520        result.put("id", id);
521        return result;
522    }
523    
524    /**
525     * Moves a {@link Form}
526     * @param siteName name of the site
527     * @param id The id of the form
528     * @param newParentId The id of the new parent directory of the form. 
529     * @return A result map
530     */
531    @Callable
532    public Map<String, Object> moveForm(String siteName, String id, String newParentId)
533    {
534        Map<String, Object> results = new HashMap<>();
535        Form form = _resolver.resolveById(id);
536        FormDirectory directory = _formDirectoryDAO.getFormDirectory(siteName, newParentId);
537        
538        if (hasWriteRightOnForm(_userProvider.getUser(), form) && _formDirectoryDAO.hasWriteRightOnFormDirectory(_userProvider.getUser(), directory))
539        {
540            _formDirectoryDAO.move(form, siteName, newParentId, results);
541        }
542        else
543        {
544            results.put("message", "not-allowed");
545        }
546
547        results.put("id", form.getId());
548        return results;
549    }
550    
551    /**
552     * Change workflow of a {@link Form}
553     * @param formId The id of the form 
554     * @param workflowName The name of new workflow
555     * @return A result map
556     */
557    @Callable
558    public Map<String, String> setWorkflow (String formId, String workflowName)
559    {
560        Map<String, String> results = new HashMap<>();
561        
562        Form form = _resolver.resolveById(formId);
563        checkHandleFormRight(form);
564        
565        form.setWorkflowName(workflowName);
566        form.setContributor(_userProvider.getUser());
567        form.setLastModificationDate(ZonedDateTime.now());
568        
569        form.saveChanges();
570        
571        results.put("id", formId);
572
573        Map<String, Object> eventParams = new HashMap<>();
574        eventParams.put("form", form);
575        _observationManager.notify(new Event(FormEvents.FORM_MODIFIED, _currentUserProvider.getUser(), eventParams));
576        
577        return results;
578    }
579
580    /**
581     * Get the submission date of the last entry to the form
582     * @param entries A list of form entry
583     * @return the date of the last submission
584     */
585    protected ZonedDateTime _getLastSubmissionDate(List<FormEntry> entries)
586    {
587        ZonedDateTime lastSubmissionDate = null;
588        if (!entries.isEmpty())
589        {
590            lastSubmissionDate = entries.get(0).getSubmitDate();
591            for (FormEntry entry : entries)
592            {
593                ZonedDateTime submitDate = entry.getSubmitDate();
594                if (lastSubmissionDate.isBefore(submitDate))
595                {
596                    lastSubmissionDate = submitDate;
597                }
598            }
599        }
600        return lastSubmissionDate;
601    }
602
603    /**
604     * Get all zone items which contains the form
605     * @param formId the form id
606     * @param siteName the site name
607     * @return the zone items
608     */
609    public AmetysObjectIterable<ModifiableZoneItem> getFormZoneItems(String formId, String siteName)
610    {
611        String xpathQuery = "//element(" + siteName + ", ametys:site)//element(*, ametys:zoneItem)[@ametys-internal:service = 'org.ametys.forms.service.Display' and ametys:service_parameters/@ametys:formId = '" + formId + "']";
612        return _resolver.query(xpathQuery);
613    }
614    
615    /**
616     * Get the locale to use for a given form
617     * @param form the form
618     * @return the locale to use
619     */
620    public String getFormLocale(Form form)
621    {
622        List<SitemapElement> zoneItems = getFormPage(form.getId(), form.getSiteName());
623        
624        return zoneItems.stream()
625                .findFirst()
626                .get()
627                .getSitemapName();
628    }
629    
630    /**
631     * Get all the page where the form is published
632     * @param formId the form id
633     * @param siteName the site name
634     * @return the list of page
635     */
636    public List<SitemapElement> getFormPage(String formId, String siteName)
637    {
638        AmetysObjectIterable<ModifiableZoneItem> zoneItems = getFormZoneItems(formId, siteName);
639        
640        return zoneItems.stream()
641                .map(z -> z.getZone().getSitemapElement())
642                .collect(Collectors.toList());
643    }
644    
645    /**
646     * Get the page names
647     * @param pages the list of page
648     * @return the list of page name
649     */
650    protected List<Map<String, Object>> _getPagesInfos(List<SitemapElement> pages)
651    {
652        List<Map<String, Object>> pagesInfos = new ArrayList<>();
653        for (SitemapElement sitemapElement : pages)
654        {
655            Map<String, Object> info = new HashMap<>();
656            info.put("id", sitemapElement.getId());
657            info.put("title", sitemapElement.getTitle());
658            info.put("isPage", sitemapElement instanceof Page);
659            
660            pagesInfos.add(info);
661        }
662        
663        return pagesInfos;
664    }
665    
666    /**
667     * Get all the view available for the form display service
668     * @param formId the form identifier
669     * @param siteName the site name
670     * @param language the language
671     * @return the views as json
672     * @throws Exception if an error occurred
673     */
674    @Callable
675    public List<Map<String, Object>> getFormDisplayViews(String formId, String siteName, String language) throws Exception
676    {
677        List<Map<String, Object>> jsonifiedViews = new ArrayList<>();
678    
679        Service service = _serviceEP.getExtension("org.ametys.forms.service.Display");
680        ElementDefinition viewElementDefinition = (ElementDefinition) service.getParameters().getOrDefault(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME, null);
681        
682        String xpathQuery = "//element(" + siteName + ", ametys:site)/ametys-internal:sitemaps/" + language
683                + "//element(*, ametys:zoneItem)[@ametys-internal:service = 'org.ametys.forms.service.Display' and ametys:service_parameters/@ametys:formId = '" + formId + "']";
684        AmetysObjectIterable<ModifiableZoneItem> zoneItems = _resolver.query(xpathQuery);
685        
686        Optional<Object> existedServiceView = zoneItems.stream()
687            .map(ZoneItem::getServiceParameters)
688            .map(sp -> sp.getValue(ViewParametersManager.SERVICE_VIEW_DEFAULT_MODEL_ITEM_NAME))
689            .findFirst();
690        
691        Map<String, I18nizableText> typedEntries = viewElementDefinition.getEnumerator().getTypedEntries();
692        for (String id : typedEntries.keySet())
693        {
694            Map<String, Object> viewAsJson = new HashMap<>();
695            viewAsJson.put("id", id);
696            viewAsJson.put("label", typedEntries.get(id));
697            
698            Boolean isServiceView = existedServiceView.map(s -> s.equals(id)).orElse(false);
699            if (isServiceView || existedServiceView.isEmpty() && id.equals(viewElementDefinition.getDefaultValue()))
700            {
701                viewAsJson.put("isDefault", true);
702            }
703            
704            jsonifiedViews.add(viewAsJson);
705        }
706        
707        return jsonifiedViews;
708    }
709    
710    /**
711     * <code>true</code> if the form is well configured
712     * @param form the form
713     * @return <code>true</code> if the form is well configured
714     */
715    public boolean isFormConfigured(Form form)
716    {
717        List<FormQuestion> questions = form.getQuestions();
718        return !questions.isEmpty() && !questions.stream().anyMatch(q -> !q.getType().isQuestionConfigured(q));
719    }
720    
721    /**
722     * Get the dashboard URI
723     * @param siteName the site name
724     * @return the dashboard URI
725     */
726    public String getDashboardUri(String siteName)
727    {
728        String xpathQuery = "//element(" + siteName + ", ametys:site)//element(*, ametys:zoneItem)[@ametys-internal:service = 'org.ametys.plugins.forms.workflow.service.dashboard']";
729        AmetysObjectIterable<ModifiableZoneItem> zoneItems = _resolver.query(xpathQuery);
730        
731        Optional<Page> dashboardPage = zoneItems.stream()
732                .map(z -> z.getZone().getSitemapElement())
733                .filter(Page.class::isInstance)
734                .map(Page.class::cast)
735                .findAny();
736        
737        if (dashboardPage.isPresent())
738        {
739            Page page = dashboardPage.get();
740            String pagePath = page.getSitemap().getName() + "/" + page.getPathInSitemap() + ".html";
741            
742            String url = _siteManager.getSite(siteName).getUrl();
743            return url + "/" + pagePath;
744        }
745        
746        return StringUtils.EMPTY;
747    }
748    
749    /**
750     * Get the admin dashboard URI
751     * @param siteName the site name
752     * @return the admin dashboard URI
753     */
754    public String getAdminDashboardUri(String siteName)
755    {
756        String xpathQuery = "//element(" + siteName + ", ametys:site)//element(*, ametys:zoneItem)[@ametys-internal:service = 'org.ametys.plugins.forms.workflow.service.admin.dashboard']";
757        AmetysObjectIterable<ModifiableZoneItem> zoneItems = _resolver.query(xpathQuery);
758        
759        Optional<Page> dashboardPage = zoneItems.stream()
760                .map(z -> z.getZone().getSitemapElement())
761                .filter(Page.class::isInstance)
762                .map(Page.class::cast)
763                .findAny();
764        
765        if (dashboardPage.isPresent())
766        {
767            Page page = dashboardPage.get();
768            String pagePath = page.getSitemap().getName() + "/" + page.getPathInSitemap() + ".html";
769            
770            String url = _siteManager.getSite(siteName).getUrl();
771            return url + "/" + pagePath;
772        }
773        
774        return StringUtils.EMPTY;
775    }
776}