001/*
002 *  Copyright 2026 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.HashMap;
019import java.util.Map;
020
021import org.apache.avalon.framework.component.Component;
022import org.apache.avalon.framework.service.ServiceException;
023import org.apache.avalon.framework.service.ServiceManager;
024import org.apache.avalon.framework.service.Serviceable;
025
026import org.ametys.cms.data.type.indexing.SortableIndexableElementType;
027import org.ametys.cms.model.properties.AbstractIndexableStaticProperty;
028import org.ametys.cms.search.model.SystemProperty;
029import org.ametys.core.user.UserManager;
030import org.ametys.plugins.forms.FormEntrySystemPropertyExtensionPoint;
031import org.ametys.plugins.forms.dao.FormEntryDAO;
032import org.ametys.plugins.forms.question.FormQuestionType;
033import org.ametys.plugins.forms.question.types.MultipleAwareQuestionType;
034import org.ametys.plugins.forms.question.types.impl.ChoicesListQuestionType;
035import org.ametys.plugins.forms.question.types.impl.FileQuestionType;
036import org.ametys.plugins.forms.question.types.impl.MatrixQuestionType;
037import org.ametys.plugins.forms.question.types.impl.RichTextQuestionType;
038import org.ametys.plugins.forms.repository.Form;
039import org.ametys.plugins.forms.repository.FormEntry;
040import org.ametys.plugins.forms.repository.FormQuestion;
041import org.ametys.runtime.model.ModelItem;
042import org.ametys.runtime.model.type.DataContext;
043import org.ametys.runtime.model.type.ModelItemType;
044import org.ametys.runtime.plugin.component.AbstractLogEnabled;
045
046/**
047 * The helper for form entry search.
048 */
049public class FormEntriesSearchHelper extends AbstractLogEnabled implements Serviceable, Component
050{
051    /** Avalon ROLE. */
052    public static final String ROLE = FormEntriesSearchHelper.class.getName();
053    
054    /** The form entry DAO */
055    protected FormEntryDAO _formEntryDAO;
056    
057    /** The form entry system property extension point */
058    protected FormEntrySystemPropertyExtensionPoint _formEntrySystemPropertyEP;
059    
060    /** The user manager */
061    protected UserManager _userManager;
062    
063    public void service(ServiceManager manager) throws ServiceException
064    {
065        _formEntryDAO = (FormEntryDAO) manager.lookup(FormEntryDAO.ROLE);
066        _formEntrySystemPropertyEP = (FormEntrySystemPropertyExtensionPoint) manager.lookup(FormEntrySystemPropertyExtensionPoint.ROLE);
067        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
068    }
069    
070    /**
071     * Get all form entry columns for a given form
072     * @param form the given form
073     * @return the form entry columns
074     */
075    public Map<String, FormEntryColumn> getFormEntryColumns(Form form)
076    {
077        Map<String, FormEntryColumn> columns = new HashMap<>();
078        
079        // Then add all columns related to form entry data (questions and other model items)
080        for (ModelItem item : _formEntryDAO.getFormEntryModel(form).getModelItems())
081        {
082            FormQuestion question = form.getQuestion(item.getName());
083            if (question != null)
084            {
085                if (!question.getType().onlyForDisplay(question))
086                {
087                    columns.put(item.getName(), new FormEntryColumn(
088                        item, 
089                        question,
090                        _isSortable(question) ? _getSortFieldName(item) : null
091                    ));
092                }
093            }
094            else if (!_isExcludedFromColumns(item))
095            {
096                columns.put(item.getName(), new FormEntryColumn(
097                    item,
098                    null,
099                    _getSortFieldName(item)
100                ));
101            }
102        }
103        
104        return columns;
105    }
106    
107    private boolean _isExcludedFromColumns(ModelItem item)
108    {
109        // Private data store in the form entry.
110        // We do not want to display it as a column
111        return item.getName().equals(FormEntry.ATTRIBUTE_ACTIVE)
112            || item.getName().equals(FormEntry.ATTRIBUTE_ANONYMIZATION_DATE)
113            || item.getName().equals(FormEntry.ATTRIBUTE_IP)
114            || item.getName().equals(FormEntry.SYSTEM_PROPERTY_WORKFLOW_ALLOWED_ACTORS)
115            || item.getName().equals(FormEntry.SYSTEM_PROPERTY_WORKFLOW_DENIED_ACTORS)
116            || item.getName().equals(FormEntry.SYSTEM_PROPERTY_ANY_CONNECTED_WORKFLOW_ACTORS)
117            || item.getName().startsWith(ChoicesListQuestionType.OTHER_PREFIX_DATA_NAME);
118    }
119    
120    private boolean _isSortable(FormQuestion question)
121    {
122        FormQuestionType type = question.getType();
123        if (type instanceof MultipleAwareQuestionType multipleType && multipleType.isMultiple(question))
124        {
125            return false;
126        }
127        
128        if (type instanceof MatrixQuestionType || type instanceof RichTextQuestionType || type instanceof FileQuestionType)
129        {
130            return false;
131        }
132        
133        return true;
134    }
135    
136    private String _getSortFieldName(ModelItem item)
137    {
138        // First check if the item is a system property with a specific sort field name
139        if (_formEntrySystemPropertyEP.hasExtension(item.getName()))
140        {
141            SystemProperty systemProperty = _formEntrySystemPropertyEP.getExtension(item.getName());
142            if (systemProperty instanceof AbstractIndexableStaticProperty staticProperty)
143            {
144                return staticProperty.getSolrSortFieldName();
145            }
146        }
147        
148        // Then check if the item type is sortable and get the sort field suffix to build the sort field name
149        ModelItemType type = item.getType();
150        if (type instanceof SortableIndexableElementType sortableType)
151        {
152            return item.getName() + sortableType.getSortFieldSuffix(DataContext.newInstance());
153        }
154        
155        return null;
156    }
157    
158    /**
159     * A form entry column
160     * @param modelItem the model item
161     * @param question the form question (can be null if the model item is not a question)
162     * @param sortFieldName the sort field name (can be null if the column is not sortable)
163     */
164    public record FormEntryColumn(ModelItem modelItem, FormQuestion question, String sortFieldName) { /* */ }
165
166}