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.plugins.frontedition;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021
022import javax.jcr.RepositoryException;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.context.Context;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.context.Contextualizable;
028import org.apache.avalon.framework.logger.AbstractLogEnabled;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.components.ContextHelper;
033import org.apache.cocoon.environment.Request;
034
035import org.ametys.cms.repository.Content;
036import org.ametys.core.ui.Callable;
037import org.ametys.plugins.repository.AmetysObject;
038import org.ametys.plugins.repository.AmetysObjectResolver;
039import org.ametys.plugins.repository.AmetysRepositoryException;
040import org.ametys.plugins.repository.RepositoryConstants;
041import org.ametys.plugins.repository.UnknownAmetysObjectException;
042import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
043import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
044import org.ametys.runtime.authentication.AccessDeniedException;
045import org.ametys.web.WebConstants;
046import org.ametys.web.repository.page.Page;
047import org.ametys.web.repository.page.SitemapElement;
048import org.ametys.web.repository.page.ZoneDAO;
049import org.ametys.web.repository.page.ZoneItem;
050import org.ametys.web.rights.PageRightAssignmentContext;
051
052/**
053 * Various helpers for front-edition plugin
054 */
055public class FrontEditionHelper extends AbstractLogEnabled implements Serviceable, Component, Contextualizable
056{
057    /** Avalon Role */
058    public static final String ROLE = FrontEditionHelper.class.getName();
059    
060    /** The Ametys object resolver */
061    private AmetysObjectResolver _ametysObjectResolver;
062    private ZoneDAO _zoneDAO;
063    /** Context */
064    private Context _context;
065    
066    public void service(ServiceManager manager) throws ServiceException
067    {
068        _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
069        _zoneDAO = (ZoneDAO) manager.lookup(ZoneDAO.ROLE);
070    }
071
072    public void contextualize(Context context) throws ContextException
073    {
074        _context = context;
075    }
076    
077    /**
078     * Get the first available parent for the current page, in the live workspace
079     * @param pageId id of the page that need to be checked
080     * @return path of the parent (can be this page if available in live)
081     */
082    @Callable (rights = AmetysFrontEditionHelper.FRONT_EDITION_RIGHT_ID, paramIndex = 0, rightContext = PageRightAssignmentContext.ID)
083    public String firstAvailableParentInLivePath(String pageId)
084    {
085        AmetysObject resolveById = _ametysObjectResolver.resolveById(pageId);
086        if (resolveById instanceof Page)
087        {
088            Page page = (Page) resolveById;
089            return firstAvailableParentInLivePath(page);
090        }
091        else
092        {
093            return "";
094        }
095    }
096    /**
097     * Get the first available parent for the requested page, in the live version
098     * @param page page to check
099     * @return path of the parent (can be this page if available in live)
100     */
101    public String firstAvailableParentInLivePath(Page page)
102    {
103        Request request = ContextHelper.getRequest(_context);
104        if (page == null)
105        {
106            return "";
107        }
108        AmetysObject available = page;
109        Page availablePage = null;
110        boolean found = false;
111        String forcedWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
112        try
113        {
114            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, "live");
115    
116            while (!found)
117            {
118                try
119                {
120                    AmetysObject resolveById = _ametysObjectResolver.resolveById(available.getId());
121                    if (resolveById != null)
122                    {
123                        found = true;
124                    }
125                    else
126                    {
127                        available = available.getParent();
128                    }
129                }
130                catch (UnknownAmetysObjectException e)
131                {
132                    available = available.getParent();
133                }
134            }
135
136            if (available instanceof Page)
137            {
138                availablePage = (Page) available;
139            }
140        }
141        finally
142        {
143            //reset workspace
144            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, forcedWorkspace);
145        }
146        return availablePage == null ? "" : availablePage.getPathInSitemap();
147    }
148    
149    /**
150     * Determines if the page exists
151     * @param pageId the page id
152     * @param editionMode true if we are in edition mode
153     * @return true if the page exists
154     */
155    @Callable (rights = Callable.NO_CHECK_REQUIRED)
156    public boolean pageExists(String pageId, boolean editionMode)
157    {
158        Request request = ContextHelper.getRequest(_context);
159        String currentWorkspace = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
160        try
161        {
162            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, editionMode ? RepositoryConstants.DEFAULT_WORKSPACE : WebConstants.LIVE_WORKSPACE);
163            return _ametysObjectResolver.hasAmetysObjectForId(pageId);
164        }
165        finally
166        {
167            //reset workspace
168            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWorkspace);
169        }
170    }
171    
172    /**
173     * Get the name of a workflow action by it's id
174     * @param contentId id of the content to check if action is available
175     * @param workflowName name of the workflow checked
176     * @param actionIds actions where the name needs to be retreived
177     * @return workwflow action name
178     */
179    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
180    public Map<Integer, String> getWorkflowActionName(String contentId, String workflowName, List<Integer> actionIds)
181    {
182        Map<Integer, String> result = new HashMap<>();
183        for (Integer actionId : actionIds)
184        {
185            boolean hasWorkflowRight = AmetysFrontEditionHelper.hasWorkflowRight(actionId, contentId, false);
186            if (hasWorkflowRight)
187            {
188                String translatedName = AmetysFrontEditionHelper.getWorkflowName(workflowName, actionId);
189                result.put(actionId, translatedName);
190            }
191        }
192        return result;
193    }
194    
195    /**
196     * Check if contents are modifiable and check all attributes restriction
197     * @param actionId the edit action id
198     * @param contentIds the id of contents
199     * @param checkEditionMode Check if we are in edition mode or not
200     * @return A Map with content id as key. The value is null if content is unmodifiable or a map of content informations as an array with path of unmodifiable attributes otherwise
201     */
202    @Callable (rights = Callable.NO_CHECK_REQUIRED)
203    public Map<String, Object> getModifiableContents(int actionId, List<String> contentIds, boolean checkEditionMode)
204    {
205        Map<String, Object> result = new HashMap<>();
206        for (String contentId : contentIds)
207        {
208            Content content = _ametysObjectResolver.resolveById(contentId);
209            
210            Map<String, Object> contentInfo = new HashMap<>();
211            contentInfo.put("unmodifiableAttributes", AmetysFrontEditionHelper.getUnmodifiableAttributes(content, List.of(actionId), checkEditionMode));
212            contentInfo.put("rights", AmetysFrontEditionHelper.getRightsForContent(content));
213            
214            result.put(contentId, contentInfo);
215        }
216        
217        return result;
218    }
219    
220    /**
221     * Move a zone item of a page before/after another zone item of the same page
222     * @param zoneItemId zone item to move
223     * @param zoneName name of the zone
224     * @param pageId page Id
225     * @param offset how many item back/forward to move ? (negative for up, positive to down)
226     * @return true if success
227     * @throws UnknownAmetysObjectException If an error occurred
228     * @throws AmetysRepositoryException If an error occurred
229     * @throws RepositoryException If an error occurred
230     * @throws AccessDeniedException If user is not authorized
231     */
232    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
233    public boolean moveZoneItemId(String zoneItemId, String zoneName, String pageId, int offset) throws UnknownAmetysObjectException, AmetysRepositoryException, RepositoryException, AccessDeniedException
234    {
235        // Web_Rights_Page_OrganizeZoneItem
236        ZoneItem zoneItem = _ametysObjectResolver.resolveById(zoneItemId);
237        AmetysObject parent = zoneItem.getParent();
238        if (parent instanceof DefaultTraversableAmetysObject)
239        {
240            SitemapElement page = zoneItem.getZone().getSitemapElement();
241            if (!AmetysFrontEditionHelper.hasFrontEditionRight("Web_Rights_Page_OrganizeZoneItem", page, false))
242            {
243                throw new AccessDeniedException("User try to move zone item without sufficient right");
244            }
245            DefaultTraversableAmetysObject traversableParent = (DefaultTraversableAmetysObject) parent;
246            long itemPosition = traversableParent.getChildPosition(zoneItem);
247            long targetPosition = itemPosition + offset;
248            ZoneItem targetZoneItem = traversableParent.getChildAt(targetPosition);
249            return _zoneDAO.moveZoneItemTo(zoneItemId, zoneName, offset < 0, targetZoneItem.getId(), pageId);
250        }
251        return false;
252    }
253}