001/*
002 *  Copyright 2017 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.cms.content.referencetable;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.Optional;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.cms.content.ContentHelper;
029import org.ametys.cms.contenttype.ContentAttributeDefinition;
030import org.ametys.cms.contenttype.ContentType;
031import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
032import org.ametys.cms.contenttype.ContentTypesHelper;
033import org.ametys.cms.data.type.ModelItemTypeConstants;
034import org.ametys.cms.repository.Content;
035import org.ametys.core.ui.Callable;
036import org.ametys.core.ui.StaticClientSideElement;
037import org.ametys.plugins.repository.AmetysObjectIterable;
038import org.ametys.plugins.repository.AmetysObjectResolver;
039import org.ametys.plugins.repository.AmetysRepositoryException;
040import org.ametys.runtime.model.ModelItem;
041
042import com.opensymphony.workflow.WorkflowException;
043
044/**
045 * This element creates a button enabling to do operation on reference table contents which can have a child.
046 */
047public class HierarchicalReferenceTableClientSideElement extends StaticClientSideElement
048{
049    /** The extension point for content types */
050    protected ContentTypeExtensionPoint _contentTypeEP;
051    /** The helper component for hierarchical reference tables */
052    protected HierarchicalReferenceTablesHelper _hierarchicalReferenceTableContentsHelper;
053    /** The Ametys Object resolver */
054    protected AmetysObjectResolver _resolver;
055    /** The content types helper */
056    protected ContentTypesHelper _cTypeHelper;
057    /** The content helper */
058    protected ContentHelper _contentHelper;
059    
060    @Override
061    public void service(ServiceManager smanager) throws ServiceException
062    {
063        super.service(smanager);
064        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
065        _hierarchicalReferenceTableContentsHelper = (HierarchicalReferenceTablesHelper) smanager.lookup(HierarchicalReferenceTablesHelper.ROLE);
066        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
067        _cTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
068        _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE);
069    }
070    
071    /**
072     * Returns true if the given content can have a child, according to its hierarchy. See {@link ContentType#getParentAttributeDefinition()}
073     * @param contentId The id of the content
074     * @return true if the given content can have a child, according to its hierarchy
075     */
076    @Callable
077    public boolean canHaveChild(String contentId)
078    {
079        try
080        {
081            Content content = _resolver.resolveById(contentId);
082            for (String cTypeId : content.getTypes())
083            {
084                ContentType cType = _contentTypeEP.getExtension(cTypeId);
085                if (cType != null && _hierarchicalReferenceTableContentsHelper.hasChildContentType(cType))
086                {
087                    return true;
088                }
089            }
090        }
091        catch (AmetysRepositoryException e)
092        {
093            // Ignore
094        }
095        
096        return false;
097    }
098    
099    /**
100     * Get the hierarchy properties of the content types tree
101     * @param leafContentTypeId The id of the leaf content type, which defines the hierarchy.
102     * @return the hierarchy properties
103     */
104    @Callable
105    public Map<String, Object> getReferenceTableProperties(String leafContentTypeId)
106    {
107        Map<String, Object> result = new HashMap<>();
108        
109        result.put("rootContentType", getContentTypeForRoot(leafContentTypeId));
110        result.put("supportedContentTypes", _hierarchicalReferenceTableContentsHelper.getHierarchicalContentTypes(leafContentTypeId));
111        ContentType leafContentType = _contentTypeEP.getExtension(leafContentTypeId);
112        result.put("allowCandidates", _hierarchicalReferenceTableContentsHelper.supportCandidates(leafContentType));
113        return result;
114    }
115    
116    /**
117     * Gets the content types ids for a child of the given content (must be non-leaf in a hierarchy of reference tables contents)
118     * @param parentContentId The id of the parent content
119     * @return the content types ids
120     */
121    @Callable
122    public List<String> getContentTypesForChild(String parentContentId)
123    {
124        Content parentContent = _resolver.resolveById(parentContentId);
125        List<ContentType> childContentTypes = _hierarchicalReferenceTableContentsHelper.getChildContentTypes(parentContent);
126        
127        return childContentTypes.stream().map(ContentType::getId).collect(Collectors.toList());
128    }
129    
130    /**
131     * Gets the content type id and the default title for a child of the root node of the hierarchy of reference table contents, defined by the given leaf content type
132     * @param leafContentTypeId The id of the leaf content type, which defines the hierarchy.
133     * @return the content type id for the root node of the hierarchy of reference table contents
134     */
135    @Callable
136    public String getContentTypeForRoot(String leafContentTypeId)
137    {
138        ContentType leafContentType = _contentTypeEP.getExtension(leafContentTypeId);
139        ContentType rootContentType = _hierarchicalReferenceTableContentsHelper.getTopLevelType(leafContentType);
140        
141        if (rootContentType != null)
142        {
143            return rootContentType.getId();
144        }
145        
146        return null;
147    }
148    
149    /**
150     * Return the leafContentType
151     * @param contentTypeId the content type id
152     * @param contentId the content id to get the leaf content type from
153     * @return the map containing the properties of the leaf content type
154     */
155    @Callable
156    public Map<String, Object> getLeafContentType(String contentTypeId, String contentId)
157    {
158        ContentType cType = _contentTypeEP.getExtension(contentTypeId);
159        if (_hierarchicalReferenceTableContentsHelper.isLeaf(cType))
160        {
161            return _cTypeHelper.getContentTypeProperties(cType);
162        }
163        else
164        {
165            AmetysObjectIterable<Content> contents = _hierarchicalReferenceTableContentsHelper.getDirectChildren(_resolver.resolveById(contentId));
166            for (Content content: contents)
167            {
168                return getLeafContentType(contentTypeId, content.getId());
169            }
170        }
171        return null;
172    }
173    
174    /**
175     * Get the path of attribute holding the parent
176     * @param contentId The id of content
177     * @return the path of parent attribute or null if not found
178     */
179    @Callable
180    public String getParentAttributePath(String contentId)
181    {
182        Content content = _resolver.resolveById(contentId);
183        List<ContentType> childrenContentTypes = _hierarchicalReferenceTableContentsHelper.getChildContentTypes(content);
184        for (ContentType contentType : childrenContentTypes)
185        {
186            Optional<ContentAttributeDefinition> parentAttribute = contentType.getParentAttributeDefinition();
187            if (!parentAttribute.isEmpty())
188            {
189                return parentAttribute.get().getPath();
190            }
191        }
192        return null;
193    }
194    
195    /**
196     * Get the path of attribute when root is selected
197     * @param leafContentTypeId The id of content
198     * @return the path of parent attribute or null if not found
199     */
200    @Callable
201    public String getParentAttributePathForRoot(String leafContentTypeId)
202    {
203        ContentType leafContentType = _contentTypeEP.getExtension(leafContentTypeId);
204        return leafContentType.getParentAttributeDefinition()
205                .map(ModelItem::getPath)
206                .filter(StringUtils::isNotEmpty)
207                .orElse(null);
208    }
209    
210    /** Validate the candidate by removing the mixin from the content
211     * @param contentId The id of the content
212     * @param actionId the id of the workflow action
213     * @return The result in a Map
214     * @throws WorkflowException The Workflow Exception
215     * @throws AmetysRepositoryException The Ametys Repository Exception
216     */
217    @Callable
218    public Map<String, Object> validateCandidate(String contentId, int actionId) throws AmetysRepositoryException, WorkflowException
219    {
220        return _contentHelper.removeMixinType(contentId, HierarchicalReferenceTablesHelper.CANDIDATE_CONTENT_TYPE, actionId);
221    }
222    
223    /**
224     * Get the title's properties for edition
225     * @param contentId The id of content being renamed
226     * @return the title's properties
227     */
228    @Callable
229    public Map<String, Object> getTitleForEdition(String contentId)
230    {
231        Content content = _resolver.resolveById(contentId);
232        
233        Map<String, Object> titleInfos = new HashMap<>();
234        
235        ModelItem titleDef = content.getDefinition(Content.ATTRIBUTE_TITLE);
236        titleInfos.put("type", titleDef.getType().getId());
237        titleInfos.put("label", titleDef.getLabel());
238        
239        if (ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(titleDef.getType().getId()))
240        {
241            titleInfos.put("value", _contentHelper.getTitleVariants(content));
242        }
243        else
244        {
245            titleInfos.put("value", _contentHelper.getTitle(content));
246        }
247        
248        return titleInfos;
249    }
250}