001/*
002 *  Copyright 2019 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.odf.tree;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.avalon.framework.service.ServiceException;
023import org.apache.avalon.framework.service.ServiceManager;
024import org.apache.commons.lang3.StringUtils;
025
026import org.ametys.cms.repository.Content;
027import org.ametys.cms.repository.WorkflowAwareContent;
028import org.ametys.core.ui.Callable;
029import org.ametys.odf.ODFHelper;
030import org.ametys.odf.ProgramItem;
031import org.ametys.odf.course.Course;
032import org.ametys.odf.course.ShareableCourseHelper;
033import org.ametys.odf.course.ShareableCourseStatusHelper;
034import org.ametys.odf.course.ShareableCourseStatusHelper.ShareableStatus;
035import org.ametys.odf.courselist.CourseList;
036import org.ametys.odf.orgunit.OrgUnit;
037import org.ametys.plugins.contentstree.ContentsTreeHelper;
038import org.ametys.plugins.workflow.repository.WorkflowAwareAmetysObject;
039import org.ametys.plugins.workflow.support.WorkflowProvider;
040import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
041import org.ametys.runtime.i18n.I18nizableText;
042
043import com.opensymphony.workflow.loader.StepDescriptor;
044import com.opensymphony.workflow.loader.WorkflowDescriptor;
045
046/**
047 * Helper ODF contents tree
048 *
049 */
050public class ODFContentsTreeHelper extends ContentsTreeHelper
051{
052    /** The Avalon role */
053    @SuppressWarnings("hiding")
054    public static final String ROLE = ODFContentsTreeHelper.class.getName();
055 
056    /** The ODF helper */
057    protected ODFHelper _odfHelper;
058    /** The workflow provider */
059    protected WorkflowProvider _workflowProvider;
060    /** The shareable course status helper */
061    protected ShareableCourseStatusHelper _shareableStatusHelper;
062    /** The shareable course helper */
063    protected ShareableCourseHelper _shareableCourseHelper;
064    
065    @Override
066    public void service(ServiceManager smanager) throws ServiceException
067    {
068        super.service(smanager);
069        
070        _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE);
071        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
072        _shareableCourseHelper = (ShareableCourseHelper) smanager.lookup(ShareableCourseHelper.ROLE);
073        _shareableStatusHelper = (ShareableCourseStatusHelper) smanager.lookup(ShareableCourseStatusHelper.ROLE);
074    }
075    
076    @Override
077    @Callable
078    public Map<String, Object> getRootNodeInformations(String contentId)
079    {
080        Map<String, Object> infos = super.getRootNodeInformations(contentId);
081        infos.put("handleShareableCourse", _shareableCourseHelper.handleShareableCourse());
082        return infos;
083    }
084    
085    @Override
086    protected Map<String, Object> content2Json(Content content)
087    {
088        Map<String, Object> infos = super.content2Json(content);
089        if (content instanceof ProgramItem programItem)
090        {
091            boolean isShared = isShared(programItem);
092            boolean isSharedByParents = !isShared ? isSharedByParents(programItem) : false;
093            infos.put("code", getProgramItemDisplayCode((Content & ProgramItem) content));
094            infos.put("workflowStep", getWorkflowStep(content));
095            infos.put("isShared", isShared);
096            infos.put("isParentShared", isSharedByParents);
097            infos.put("isPublishable", programItem.isPublishable());
098            infos.put("isParentPublishable", _isParentPublishable(programItem));
099            
100            if (content instanceof Course)
101            {
102                infos.put("shareableStatus", getShareableStatus((Course) content));
103            }
104            else if (content instanceof CourseList)
105            {
106                CourseList courseList = (CourseList) content;
107                infos.put("courseList-type", courseList.getType());
108            }
109        }
110        else if (content instanceof OrgUnit)
111        {
112            infos.put("code", ((OrgUnit) content).getUAICode());
113        }
114        
115        return infos;
116    }
117    
118    private boolean _isParentPublishable(ProgramItem content)
119    {
120        List<ProgramItem> parents = _odfHelper.getParentProgramItems(content);
121        if (parents.isEmpty())
122        {
123            return true;
124        }
125        
126        return parents.stream()
127                .filter(ProgramItem::isPublishable)
128                .filter(this::_isParentPublishable)
129                .findAny()
130                .isPresent();
131    }
132    
133    /**
134     * Get the ProgramItem code we want to display.
135     * @param <T> The type of the program element, should be a subclasse of {@link ProgramItem} and {@link Content}
136     * @param programItem The program item
137     * @return The code to display
138     */
139    protected <T extends ProgramItem & Content> Object getProgramItemDisplayCode(T programItem)
140    {
141        return programItem.getCode();
142    }
143    
144    @Override
145    protected boolean isContentMatching(Content content, String value)
146    {
147        boolean matchTitle = super.isContentMatching(content, value);
148        if (!matchTitle)
149        {
150            if (content instanceof ProgramItem)
151            {
152                // Try with code
153                String code =  StringUtils.stripAccents(((ProgramItem) content).getCode().toLowerCase());
154                return code.contains(value);
155            }
156            else if (content instanceof OrgUnit)
157            {
158                // Try with code
159                String code =  StringUtils.stripAccents(((OrgUnit) content).getUAICode().toLowerCase());
160                return code.contains(value);
161            }
162        }
163        
164        return matchTitle;
165    }
166    
167    /**
168     * Get workflow step information
169     * @param content the content the content
170     * @return the workflow step information
171     */
172    protected Map<String, Object> getWorkflowStep(Content content)
173    {
174        Map<String, Object> workflowInfos = new HashMap<>();
175                
176        if (content instanceof WorkflowAwareAmetysObject)
177        {
178            WorkflowAwareContent waContent = (WorkflowAwareContent) content;
179            
180            long workflowId = waContent.getWorkflowId();
181            int currentStepId = Math.toIntExact(waContent.getCurrentStepId());
182        
183            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent);
184        
185            String workflowName = workflow.getWorkflowName(workflowId);
186            WorkflowDescriptor workflowDescriptor = workflow.getWorkflowDescriptor(workflowName);
187        
188            if (workflowDescriptor != null)
189            {
190                StepDescriptor stepDescriptor = workflowDescriptor.getStep(currentStepId);
191                if (stepDescriptor != null)
192                {
193                    I18nizableText workflowStepName = new I18nizableText("application", stepDescriptor.getName());
194                    
195                    workflowInfos.put("stepId", currentStepId);
196                    workflowInfos.put("name", workflowStepName);
197                    
198                    String[] icons = new String[] {"small", "medium", "large"};
199                    for (String icon : icons)
200                    {
201                        if ("application".equals(workflowStepName.getCatalogue()))
202                        {
203                            workflowInfos.put(icon + "Icon", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-" + icon + ".png");
204                        }
205                        else
206                        {
207                            String pluginName = workflowStepName.getCatalogue().substring("plugin.".length());
208                            workflowInfos.put(icon + "Icon", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-" + icon + ".png");
209                        }
210                    }
211                }
212            }
213        }
214        
215        return workflowInfos;
216    }
217    
218    /**
219     * Get the shared status of this program item
220     * @param programItem the program item
221     * @return true if the program item belongs to more than one program item
222     */
223    protected boolean isShared(ProgramItem programItem)
224    {
225        return _odfHelper.getParentProgramItems(programItem).size() > 1;
226    }
227    
228    /**
229     * Check if the parent of a program item is shared 
230     * @param programItem the program item
231     * @return true if the parent of the program item is shared
232     */
233    protected boolean isSharedByParents(ProgramItem programItem)
234    {
235        for (ProgramItem parent : _odfHelper.getParentProgramItems(programItem))
236        {
237            return _odfHelper.getParentProgramItems(parent).size() > 1 ? true : isSharedByParents(parent);
238        }
239        return false;
240    }
241
242    /**
243     * Get the shareable course status as String
244     * @param course the course
245     * @return the shareable status
246     */
247    protected String getShareableStatus(Course course)
248    {
249        if (_shareableCourseHelper.handleShareableCourse())
250        {
251            ShareableStatus shareableStatus = _shareableStatusHelper.getShareableStatus(course);
252            return shareableStatus.name().toLowerCase();
253        }
254        
255        return null;
256    }
257}