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}