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.ArrayList;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.commons.collections.CollectionUtils;
030
031import org.ametys.cms.clientsideelement.relations.SetContentAttributeClientSideElement;
032import org.ametys.cms.contenttype.ContentAttributeDefinition;
033import org.ametys.cms.contenttype.ContentType;
034import org.ametys.cms.repository.Content;
035import org.ametys.core.ui.Callable;
036import org.ametys.plugins.repository.AmetysRepositoryException;
037import org.ametys.runtime.parameter.Validator;
038
039/**
040 * Set the "parent" attribute of a simple content
041 */
042public class SetParentContentClientSideElement extends SetContentAttributeClientSideElement
043{
044    /** The helper component for hierarchical simple contents */
045    protected HierarchicalReferenceTablesHelper _hierarchicalSimpleContentsHelper;
046    
047    @Override
048    public void service(ServiceManager manager) throws ServiceException
049    {
050        super.service(manager);
051        _hierarchicalSimpleContentsHelper = (HierarchicalReferenceTablesHelper) manager.lookup(HierarchicalReferenceTablesHelper.ROLE);
052    }
053    
054    /**
055     * Tells if the given simple contents can have their parent attribute changed for the given new simple content parent.
056     * @param contentIdsToMove The identifiers of the simple contents willing to change their parent attribute
057     * @param newParentContentId The identifier of the new parent simple content. Can be "root" if it is the root of simple contents
058     * @param leafContentTypeId If newParentContentId is equal to "root", is the identifier of the content type of the leaves of the tree. Otherwise, must be null
059     * @return A map with a key 'validContents' for the ids of valid contents for the move, a key 'invalidContents' for the ids of invalid contents for the move and key 'noRightContents' for the ids of contents with no read access, among contentIdsToMove
060     */
061    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
062    public Map<String, Object> areValidMoves(List<String> contentIdsToMove, String newParentContentId, String leafContentTypeId)
063    {
064        Map<String, Object> result = new HashMap<>();
065        List<Map<String, String>> validContents = new ArrayList<>();
066        result.put("validContents", validContents);
067        List<String> invalidContents = new ArrayList<>(); 
068        result.put("invalidContents", invalidContents);
069        List<String> noRightContents = new ArrayList<>(); 
070        result.put("noRightContents", noRightContents);
071        
072        if ("root".equals(newParentContentId))
073        {
074            ContentType leafContentType = _contentTypeExtensionPoint.getExtension(leafContentTypeId);
075            String topLevelType = _hierarchicalSimpleContentsHelper.getTopLevelType(leafContentType).getId();
076            
077            for (String contentIdToMove : contentIdsToMove)
078            {
079                try
080                {
081                    Content contentToMove = _resolver.resolveById(contentIdToMove);
082                    
083                    if (!_rightManager.currentUserHasReadAccess(contentToMove))
084                    {
085                        noRightContents.add(contentIdToMove);
086                        break;
087                    }
088                    
089                    ContentAttributeDefinition parentAttributeDefinition = _getParentAttributeDefinition(contentToMove);
090                    if (parentAttributeDefinition == null)
091                    {
092                        invalidContents.add(contentIdToMove);
093                        break;
094                    }
095                    
096                    // 1) As move towards the root node means removing the "parent" attribute, check it is not mandatory
097                    Validator validator = parentAttributeDefinition.getValidator();
098                    if (validator != null && (Boolean) validator.getConfiguration().get("mandatory"))
099                    {
100                        invalidContents.add(contentIdToMove);
101                        break;
102                    }
103                    
104                    // 2) Check the content is a potential child of root node, i.e. has good content type
105                    if (Arrays.asList(contentToMove.getTypes()).contains(topLevelType))
106                    {
107                        validContents.add(_validContentToMap(contentToMove, Collections.singletonList(topLevelType)));
108                    }
109                    else
110                    {
111                        invalidContents.add(contentIdToMove);
112                    }
113                }
114                catch (AmetysRepositoryException e)
115                {
116                    getLogger().error("An error occured while retrieving the content to move.", e);
117                    invalidContents.add(contentIdToMove);
118                }
119            }
120        }
121        else
122        {
123            Content newParent;
124            try
125            {
126                newParent = _resolver.resolveById(newParentContentId);
127            }
128            catch (AmetysRepositoryException e)
129            {
130                getLogger().error("An error occured while retrieving the new parent content.", e);
131                result.put("error", "unknown-parent");
132                return result;
133            }
134            List<String> childContentTypes = _hierarchicalSimpleContentsHelper.getChildContentTypes(newParent).stream()
135                                                                                                              .map(ContentType::getId)
136                                                                                                              .collect(Collectors.toList());
137            for (String contentIdToMove : contentIdsToMove)
138            {
139                try
140                {
141                    Content contentToMove = _resolver.resolveById(contentIdToMove);
142                    if (!_rightManager.currentUserHasReadAccess(contentToMove))
143                    {
144                        noRightContents.add(contentIdToMove);
145                    }
146                    else if (CollectionUtils.containsAny(Arrays.asList(contentToMove.getTypes()), childContentTypes))
147                    {
148                        // The content types of the content is valid with the new parent
149                        Map<String, String> validContent = _validContentToMap(contentToMove, childContentTypes);
150                        validContents.add(validContent);
151                    }
152                    else
153                    {
154                        invalidContents.add(contentIdToMove);
155                    }
156                }
157                catch (AmetysRepositoryException e)
158                {
159                    getLogger().error("An error occured while retrieving the content to move.", e);
160                    invalidContents.add(contentIdToMove);
161                }
162            }
163        }
164        
165        return result;
166    }
167    
168    private ContentAttributeDefinition _getParentAttributeDefinition(Content content)
169    {
170        for (String cTypeId : content.getTypes())
171        {
172            ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId);
173            Optional<ContentAttributeDefinition> parentAttribute = cType.getParentAttributeDefinition();
174            if (!parentAttribute.isEmpty())
175            {
176                return parentAttribute.get();
177            }
178        }
179        
180        return null;
181    }
182    
183    private Map<String, String> _validContentToMap(Content validContent, List<String> childContentTypes)
184    {
185        Map<String, String> contentMap = new HashMap<>();
186        contentMap.put("id", validContent.getId());
187        
188        List<String> types = Arrays.asList(validContent.getTypes());
189        for (String childContentTypeId : childContentTypes)
190        {
191            if (types.contains(childContentTypeId))
192            {
193                ContentType childContentType = _contentTypeExtensionPoint.getExtension(childContentTypeId);
194                childContentType.getParentAttributeDefinition()
195                    .ifPresent(parentAttribute -> contentMap.put("parentAttributeName", parentAttribute.getName()));
196            }
197        }
198        
199        contentMap.put("parentAttributeValue", _hierarchicalSimpleContentsHelper.getParent(validContent));
200        
201        return contentMap;
202    }
203}