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.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.HashMap; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.stream.Collectors; 029 030import javax.jcr.RepositoryException; 031 032import org.apache.avalon.framework.context.Context; 033import org.apache.avalon.framework.context.ContextException; 034import org.apache.avalon.framework.context.Contextualizable; 035import org.apache.avalon.framework.logger.LogEnabled; 036import org.apache.avalon.framework.logger.Logger; 037import org.apache.avalon.framework.service.ServiceException; 038import org.apache.avalon.framework.service.ServiceManager; 039import org.apache.avalon.framework.service.Serviceable; 040import org.apache.cocoon.components.ContextHelper; 041import org.apache.cocoon.environment.Request; 042import org.apache.commons.lang3.ArrayUtils; 043import org.apache.commons.lang3.StringUtils; 044import org.apache.commons.lang3.tuple.Pair; 045import org.w3c.dom.NodeList; 046 047import org.ametys.cms.CmsConstants; 048import org.ametys.cms.content.RootContentHelper; 049import org.ametys.cms.contenttype.AttributeDefinition; 050import org.ametys.cms.contenttype.ContentType; 051import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 052import org.ametys.cms.contenttype.ContentTypesHelper; 053import org.ametys.cms.repository.Content; 054import org.ametys.cms.repository.WorkflowAwareContent; 055import org.ametys.cms.workflow.ContentWorkflowHelper; 056import org.ametys.core.right.RightManager; 057import org.ametys.core.right.RightManager.RightResult; 058import org.ametys.core.ui.ClientSideElement; 059import org.ametys.core.ui.ClientSideElement.Script; 060import org.ametys.core.ui.ClientSideElement.ScriptFile; 061import org.ametys.core.ui.widgets.richtext.RichTextConfiguration; 062import org.ametys.core.ui.widgets.richtext.RichTextConfigurationExtensionPoint; 063import org.ametys.core.util.I18nizableSerializer; 064import org.ametys.core.util.JSONUtils; 065import org.ametys.plugins.core.ui.minimize.HashCache; 066import org.ametys.plugins.explorer.resources.actions.ExplorerResourcesDAO; 067import org.ametys.plugins.repository.AmetysObject; 068import org.ametys.plugins.repository.AmetysObjectIterable; 069import org.ametys.plugins.repository.AmetysObjectResolver; 070import org.ametys.plugins.repository.AmetysRepositoryException; 071import org.ametys.plugins.repository.RepositoryConstants; 072import org.ametys.plugins.repository.UnknownAmetysObjectException; 073import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 074import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 075import org.ametys.plugins.repository.version.VersionableAmetysObject; 076import org.ametys.plugins.workflow.support.WorkflowHelper; 077import org.ametys.plugins.workflow.support.WorkflowProvider; 078import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 079import org.ametys.runtime.i18n.I18nizableText; 080import org.ametys.runtime.model.ModelItem; 081import org.ametys.runtime.model.ModelItemGroup; 082import org.ametys.runtime.plugin.PluginsManager; 083import org.ametys.web.cache.PageHelper; 084import org.ametys.web.renderingcontext.RenderingContext; 085import org.ametys.web.repository.page.ModifiablePage; 086import org.ametys.web.repository.page.Page; 087import org.ametys.web.repository.page.SitemapElement; 088import org.ametys.web.repository.page.Zone; 089import org.ametys.web.repository.page.ZoneItem; 090import org.ametys.web.repository.site.Site; 091import org.ametys.web.repository.site.SiteManager; 092import org.ametys.web.repository.sitemap.Sitemap; 093import org.ametys.web.transformation.xslt.AmetysXSLTHelper; 094 095import com.opensymphony.workflow.loader.ActionDescriptor; 096import com.opensymphony.workflow.loader.StepDescriptor; 097import com.opensymphony.workflow.loader.WorkflowDescriptor; 098import com.opensymphony.workflow.spi.Step; 099 100/** 101 * Helper for preparing the Ametys Edition 102 */ 103public class AmetysFrontEditionHelper implements Serviceable, Contextualizable, LogEnabled 104{ 105 /** The right's id from front-edition access */ 106 public static final String FRONT_EDITION_RIGHT_ID = "Front_Edition_Access_Right"; 107 108 private static AmetysObjectResolver _ametysObjectResolver; 109 private static HashCache _hashCache; 110 private static RichTextConfigurationExtensionPoint _richTextConfigurationExtensionPoint; 111 private static RightManager _rightManager; 112 private static ContentWorkflowHelper _contentWorkflowHelper; 113 private static PageHelper _pageHelper; 114 private static WorkflowHelper _workflowHelper; 115 private static JSONUtils _jsonUtils; 116 private static SiteManager _siteManager; 117 private static WorkflowProvider _workflowProvider; 118 private static ContentTypesHelper _contentTypesHelper; 119 private static ContentTypeExtensionPoint _contentTypesEP; 120 private static RootContentHelper _rootContentHelper; 121 122 private static Context _context; 123 124 private static Logger _logger; 125 126 public void service(ServiceManager manager) throws ServiceException 127 { 128 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 129 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 130 _hashCache = (HashCache) manager.lookup(HashCache.ROLE); 131 _richTextConfigurationExtensionPoint = (RichTextConfigurationExtensionPoint) manager.lookup(RichTextConfigurationExtensionPoint.ROLE); 132 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 133 _contentWorkflowHelper = (ContentWorkflowHelper) manager.lookup(ContentWorkflowHelper.ROLE); 134 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 135 _contentTypesEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 136 _pageHelper = (PageHelper) manager.lookup(PageHelper.ROLE); 137 _workflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE); 138 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 139 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 140 _rootContentHelper = (RootContentHelper) manager.lookup(RootContentHelper.ROLE); 141 } 142 143 @Override 144 public void contextualize(Context context) throws ContextException 145 { 146 _context = context; 147 } 148 149 @Override 150 public void enableLogging(Logger logger) 151 { 152 _logger = logger; 153 } 154 155 /** 156 * Prepare a hashcode for js files 157 * @param locale The language code to use 158 * @param theme The url for the theme 159 * @return The new hashcode to read the minimified concatened file 160 */ 161 public static String prepareJSFiles(String locale, String theme) 162 { 163 String themeName = StringUtils.substringAfterLast(theme, "/"); 164 165 Map<String, String> filesInfos = new HashMap<>(); 166 filesInfos.put("media", ""); 167 filesInfos.put("tag", "script"); 168 169 Map<String, Map<String, String>> files = new LinkedHashMap<>(); 170 files.put("/plugins/extjs7/resources/ext-all.js", filesInfos); 171 files.put("/plugins/extjs7/resources/classic/locale/locale-" + locale + ".js", filesInfos); 172 files.put(theme + "/" + themeName + ".js", filesInfos); 173 174 files.put("/plugins/core-ui/resources/js/Ext.fixes." + locale + ".js", filesInfos); 175 files.put("/plugins/core-ui/resources/js/Ext.enhancements." + locale + ".js", filesInfos); 176 177 files.put("/plugins/core-ui/resources/js/Ametys." + locale + ".js", filesInfos); 178 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys." + locale + ".js", filesInfos); 179 files.put("/plugins/core-ui/resources/js/Ametys/mask/GlobalLoadMask." + locale + ".js", filesInfos); 180 181 files.put("/plugins/core-ui/resources/js/Ametys/log/Logger." + locale + ".js", filesInfos); 182 files.put("/plugins/core-ui/resources/js/Ametys/log/Logger/Entry." + locale + ".js", filesInfos); 183 files.put("/plugins/core-ui/resources/js/Ametys/log/LoggerFactory." + locale + ".js", filesInfos); 184 files.put("/plugins/core-ui/resources/js/Ametys/log/ErrorDialog." + locale + ".js", filesInfos); 185 186 files.put("/plugins/core-ui/resources/js/Ametys/window/DialogBox." + locale + ".js", filesInfos); 187 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/window/DialogBox." + locale + ".js", filesInfos); 188 files.put("/plugins/core-ui/resources/js/Ametys/window/MessageBox." + locale + ".js", filesInfos); 189 190 files.put("/plugins/core-ui/resources/js/Ametys/form/AbstractField." + locale + ".js", filesInfos); 191 files.put("/plugins/core-ui/resources/js/Ametys/form/AbstractFieldsWrapper." + locale + ".js", filesInfos); 192 files.put("/plugins/core-ui/resources/js/Ametys/form/AbstractQueryableComboBox." + locale + ".js", filesInfos); 193 files.put("/plugins/core-ui/resources/js/Ametys/form/AbstractQueryableComboBox/SplitterTracker." + locale + ".js", filesInfos); 194 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/form/widget/AbstractQueryableComboBox." + locale + ".js", filesInfos); 195 files.put("/plugins/core-ui/resources/js/Ametys/form/field/DateTime." + locale + ".js", filesInfos); 196 files.put("/plugins/core-ui/resources/js/Ametys/form/field/StringTime." + locale + ".js", filesInfos); 197 files.put("/plugins/core-ui/resources/js/Ametys/form/field/Password." + locale + ".js", filesInfos); 198 files.put("/plugins/core-ui/resources/js/Ametys/form/field/ChangePassword." + locale + ".js", filesInfos); 199 files.put("/plugins/core-ui/resources/js/Ametys/form/field/ReferencedNumberField." + locale + ".js", filesInfos); 200 files.put("/plugins/core-ui/resources/js/Ametys/form/field/RichText." + locale + ".js", filesInfos); 201 files.put("/plugins/cms/resources/js/Ametys/cms/form/field/RichText." + locale + ".js", filesInfos); 202 files.put("/plugins/core-ui/resources/js/Ametys/form/field/RichText/SplitterTracker." + locale + ".js", filesInfos); 203 files.put("/plugins/core-ui/resources/js/Ametys/form/field/TextArea." + locale + ".js", filesInfos); 204 files.put("/plugins/core-ui/resources/js/Ametys/form/field/ColorSelector." + locale + ".js", filesInfos); 205 files.put("/plugins/core-ui/resources/js/Ametys/form/field/SelectUserDirectory." + locale + ".js", filesInfos); 206 files.put("/plugins/core-ui/resources/js/Ametys/form/field/SelectGroupDirectories." + locale + ".js", filesInfos); 207 files.put("/plugins/core-ui/resources/js/Ametys/form/field/Code." + locale + ".js", filesInfos); 208 209 files.put("/plugins/core-ui/resources/js/Ametys/helper/ChooseLocationLeaflet." + locale + ".js", filesInfos); 210 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/GeoCode." + locale + ".js", filesInfos); 211 files.put("/plugins/core-ui/resources/js/Ametys/grid/GridColumnHelper." + locale + ".js", filesInfos); 212 files.put("/plugins/tiny_mce/resources/js/tinymce.js", filesInfos); 213 files.put("/plugins/codemirror/resources/js/codemirror.js", filesInfos); 214 files.put("/plugins/codemirror/resources/js/addon/edit/matchbrackets.js", filesInfos); 215 files.put("/plugins/codemirror/resources/js/addon/selection/active-line.js", filesInfos); 216 files.put("/plugins/codemirror/resources/js/mode/xml/xml.js", filesInfos); 217 files.put("/plugins/codemirror/resources/js/mode/javascript/javascript.js", filesInfos); 218 files.put("/plugins/codemirror/resources/js/mode/css/css.js", filesInfos); 219 files.put("/plugins/codemirror/resources/js/mode/htmlmixed/htmlmixed.js", filesInfos); 220 221 files.put("/plugins/core-ui/resources/js/Ametys/data/ServerCaller." + locale + ".js", filesInfos); 222 files.put("/plugins/core-ui/resources/js/Ametys/data/ServerComm." + locale + ".js", filesInfos); 223 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/data/ServerComm." + locale + ".js", filesInfos); 224 files.put("/plugins/core-ui/resources/js/Ametys/data/ServerCommProxy." + locale + ".js", filesInfos); 225 files.put("/plugins/core-ui/resources/js/Ametys/data/ServerComm/TimeoutDialog." + locale + ".js", filesInfos); 226 227 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/RichText/RichTextConfigurationBase." + locale + ".js", filesInfos); 228 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/RichText/RichTextConfiguration." + locale + ".js", filesInfos); 229 230 files.put("/plugins/core-ui/resources/js/Ametys/plugins/coreui/system/devmode/StacktraceHelper." + locale + ".js", filesInfos); 231 232 files.put("/plugins/front-edition/resources/js/front-comm." + locale + ".js", filesInfos); 233 files.put("/plugins/front-edition/resources/js/front-widget." + locale + ".js", filesInfos); 234// files.put("/plugins/front-edition/resources/js/front-page." + locale + ".js", filesInfos); 235 files.put("/plugins/core-ui/resources/js/Ametys/helper/EnterURL." + locale + ".js", filesInfos); 236 files.put("/plugins/core-ui/resources/js/Ametys/helper/FileUpload." + locale + ".js", filesInfos); 237 files.put("/plugins/web/resources/js/Ametys/web/helper/ChoosePage." + locale + ".js", filesInfos); 238 files.put("/plugins/web/resources/js/Ametys/web/helper/ContextToolbar." + locale + ".js", filesInfos); 239 files.put("/plugins/web/resources/js/Ametys/web/sitemap/SitemapTree." + locale + ".js", filesInfos); 240 files.put("/plugins/web/resources/js/Ametys/web/sitemap/SitemapTree/Page." + locale + ".js", filesInfos); 241 files.put("/plugins/web/resources/js/Ametys/web/sitemap/SitemapTree/Sitemap." + locale + ".js", filesInfos); 242 files.put("/plugins/web/resources/js/Ametys/web/site/SitesTree/Site." + locale + ".js", filesInfos); 243 files.put("/plugins/cms/resources/js/Ametys/cms/editor/LinkHandler." + locale + ".js", filesInfos); 244 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/editor/Links." + locale + ".js", filesInfos); 245 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/editor/Links/LinkHandler." + locale + ".js", filesInfos); 246 files.put("/plugins/web/resources/js/Ametys/plugins/web/editor/Links." + locale + ".js", filesInfos); 247 files.put("/plugins/web/resources/js/Ametys/plugins/web/editor/PageLinkHandler." + locale + ".js", filesInfos); 248 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/editor/Images." + locale + ".js", filesInfos); 249 250 files.put("/plugins/cms/resources/js/tinymce/basicstyles." + locale + ".js", filesInfos); 251 files.put("/plugins/cms/resources/js/tinymce/lists." + locale + ".js", filesInfos); 252 files.put("/plugins/cms/resources/js/tinymce/links." + locale + ".js", filesInfos); 253 files.put("/plugins/web/resources/js/tinymce/links." + locale + ".js", filesInfos); 254 files.put("/plugins/front-edition/resources/js/tinymce/links." + locale + ".js", filesInfos); 255 files.put("/plugins/cms/resources/js/tinymce/styles." + locale + ".js", filesInfos); 256 files.put("/plugins/cms/resources/js/tinymce/images." + locale + ".js", filesInfos); 257 files.put("/plugins/front-edition/resources/js/tinymce/images." + locale + ".js", filesInfos); 258 259 if (PluginsManager.getInstance().getPluginNames().contains("inlinemedia")) 260 { 261 262 files.put("/plugins/inlinemedia/resources/js/Ametys/plugins/inlinemedia/Media." + locale + ".js", filesInfos); 263 files.put("/plugins/inlinemedia/resources/js/Ametys/plugins/inlinemedia/InsertMediaHelper." + locale + ".js", filesInfos); 264 files.put("/plugins/inlinemedia/resources/js/tinymce/images." + locale + ".js", filesInfos); 265 } 266 267 files.put("/plugins/front-edition/resources/js/tinymce/save." + locale + ".js", filesInfos); 268 269 //TinyMCE Tables 270 files.put("/plugins/cms/resources/js/tinymce/tables." + locale + ".js", filesInfos); 271 files.put("/plugins/front-edition/resources/js/tinymce/tables." + locale + ".js", filesInfos); 272 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/editor/Tables." + locale + ".js", filesInfos); 273 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/editor/BasicActions." + locale + ".js", filesInfos); 274 //TinyMCE Images 275 files.put("/plugins/cms/resources/js/Ametys/cms/uihelper/ChooseResource." + locale + ".js", filesInfos); 276 files.put("/plugins/explorer/resources/js/Ametys/explorer/tree/ExplorerTree." + locale + ".js", filesInfos); 277 files.put("/plugins/explorer/resources/js/Ametys/explorer/tree/ExplorerTree/NodeEntry." + locale + ".js", filesInfos); 278 files.put("/plugins/explorer/resources/js/Ametys/explorer/Resource." + locale + ".js", filesInfos); 279 files.put("/plugins/explorer/resources/js/Ametys/explorer/ExplorerNodeDAO." + locale + ".js", filesInfos); 280 files.put("/plugins/explorer/resources/js/Ametys/explorer/ExplorerNode." + locale + ".js", filesInfos); 281 files.put("/plugins/web/resources/js/Ametys/plugins/web/explorer/ExplorerNode." + locale + ".js", filesInfos); 282 files.put("/plugins/cms/resources/js/Ametys/cms/attach/AttachmentsExplorerTree." + locale + ".js", filesInfos); 283 files.put("/plugins/cms/resources/js/Ametys/cms/uihelper/ChooseAttachmentFile." + locale + ".js", filesInfos); 284 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/cms/uihelper/ChooseAttachmentFile." + locale + ".js", filesInfos); 285 files.put("/plugins/explorer/resources/js/Ametys/explorer/resources/actions/FileActions." + locale + ".js", filesInfos); 286 files.put("/plugins/explorer/resources/js/Ametys/explorer/resources/helper/ResourceUpload." + locale + ".js", filesInfos); 287 files.put("/plugins/core-ui/resources/js/Ametys/file/AbstractFileExplorerTree/FileNode." + locale + ".js", filesInfos); 288 files.put("/plugins/core-ui/resources/js/Ametys/file/AbstractFileExplorerTree." + locale + ".js", filesInfos); 289 290 //Empty Message class 291 files.put("/plugins/core-ui/resources/js/Ametys/message/Message." + locale + ".js", filesInfos); 292 files.put("/plugins/core-ui/resources/js/Ametys/message/MessageTarget." + locale + ".js", filesInfos); 293 files.put("/plugins/core-ui/resources/js/Ametys/message/MessageTargetFactory." + locale + ".js", filesInfos); 294 files.put("/plugins/core-ui/resources/js/Ametys/message/factory/DefaultMessageTargetFactory." + locale + ".js", filesInfos); 295 files.put("/plugins/front-edition/resources/js/Ametys/message/MessageTargetHelper." + locale + ".js", filesInfos); 296 //add page 297 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard." + locale + ".js", filesInfos); 298 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/plugins/web/page/AddPageWizard." + locale + ".js", filesInfos); 299 files.put("/plugins/web/resources/js/Ametys/web/sitemap/SitemapDAO." + locale + ".js", filesInfos); 300 files.put("/plugins/web/resources/js/Ametys/web/sitemap/Sitemap." + locale + ".js", filesInfos); 301 files.put("/plugins/web/resources/js/Ametys/web/page/PageDAO." + locale + ".js", filesInfos); 302 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/web/page/PageDAO." + locale + ".js", filesInfos); 303 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard/Card." + locale + ".js", filesInfos); 304 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard/CreatePageCard." + locale + ".js", filesInfos); 305 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard/PageContentCard." + locale + ".js", filesInfos); 306 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard/ContentPropertiesCard." + locale + ".js", filesInfos); 307 files.put("/plugins/core-ui/resources/js/Ametys/form/Widget." + locale + ".js", filesInfos); 308 files.put("/plugins/core-ui/resources/js/Ametys/form/ConfigurableFormPanel." + locale + ".js", filesInfos); 309 files.put("/plugins/front-edition/resources/js/Ametys/userprefs/UserPrefsDAO." + locale + ".js", filesInfos); 310 files.put("/plugins/core-ui/resources/js/Ametys/form/ConfigurableFormPanel/Repeater." + locale + ".js", filesInfos); 311 files.put("/plugins/core-ui/resources/js/Ametys/form/ConfigurableFormPanel/FieldCheckersManager." + locale + ".js", filesInfos); 312 files.put("/plugins/front-edition/resources/js/Ametys/message/MessageBus." + locale + ".js", filesInfos); 313 files.put("/plugins/core-ui/resources/js/Ametys/form/WidgetManager." + locale + ".js", filesInfos); 314 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/DefaultWidgets." + locale + ".js", filesInfos); 315 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/Date." + locale + ".js", filesInfos); 316 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/DateTime." + locale + ".js", filesInfos); 317 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/form/WidgetManager." + locale + ".js", filesInfos); 318 319 files.put("/plugins/cms/resources/js/Ametys/cms/content/ContentDAO." + locale + ".js", filesInfos); 320 files.put("/plugins/web/resources/js/Ametys/web/content/ContentDAO." + locale + ".js", filesInfos); 321 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/cms/content/ContentDAO." + locale + ".js", filesInfos); 322 files.put("/plugins/cms/resources/js/Ametys/cms/content/Content." + locale + ".js", filesInfos); 323 files.put("/plugins/core-ui/resources/js/Ametys/navhistory/HistoryDAO." + locale + ".js", filesInfos); 324 files.put("/plugins/core-ui/resources/js/Ametys/window/MessageBox." + locale + ".js", filesInfos); 325 files.put("/plugins/front-edition/resources/js/Ametys/tool/ToolsManager." + locale + ".js", filesInfos); 326 327 files.put("/plugins/cms/resources/js/Ametys/cms/form/widget/RichText." + locale + ".js", filesInfos); 328 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/cms/form/widget/SelectContent/ContentEntry." + locale + ".js", filesInfos); 329 files.put("/plugins/cms/resources/js/Ametys/cms/form/widget/SelectContent." + locale + ".js", filesInfos); 330 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/cms/form/widget/SelectContent." + locale + ".js", filesInfos); 331 files.put("/plugins/cms/resources/js/Ametys/cms/form/widget/SelectReferenceTableContent." + locale + ".js", filesInfos); 332 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/cms/form/widget/SelectReferenceTableContent." + locale + ".js", filesInfos); 333 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard/PageTypeCard." + locale + ".js", filesInfos); 334 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/plugins/web/page/AddPageWizard/PageTypeCard." + locale + ".js", filesInfos); 335 files.put("/plugins/web/resources/js/Ametys/web/form/widget/SelectPage." + locale + ".js", filesInfos); 336 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/web/form/widget/SelectPage." + locale + ".js", filesInfos); 337 files.put("/plugins/web/resources/js/Ametys/web/form/widget/SelectSite." + locale + ".js", filesInfos); 338 files.put("/plugins/web/resources/js/Ametys/web/helper/ChooseSite." + locale + ".js", filesInfos); 339 files.put("/plugins/web/resources/js/Ametys/web/site/SitesTree." + locale + ".js", filesInfos); 340 files.put("/plugins/web/resources/js/Ametys/web/site/SitesTree/Site." + locale + ".js", filesInfos); 341 files.put("/plugins/web/resources/js/Ametys/web/helper/ContextToolbar." + locale + ".js", filesInfos); 342 files.put("/plugins/web/resources/js/Ametys/web/site/SitesTree/Site." + locale + ".js", filesInfos); 343 files.put("/plugins/web/resources/js/Ametys/web/form/widget/SelectPage/PageEntry." + locale + ".js", filesInfos); 344 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard/TagsCard." + locale + ".js", filesInfos); 345 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/AddPageWizard/TagsCardLite." + locale + ".js", filesInfos); 346 files.put("/plugins/web/resources/js/Ametys/web/page/Page." + locale + ".js", filesInfos); 347 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/tag/TagsTreePanel." + locale + ".js", filesInfos); 348 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/tag/TagsTreePanel/TagNode." + locale + ".js", filesInfos); 349 350 //delete page 351 files.put("/plugins/web/resources/js/Ametys/plugins/web/sitemap/SitemapActions." + locale + ".js", filesInfos); 352 353 //page tags 354 files.put("/plugins/web/resources/js/Ametys/plugins/web/tag/AffectTagAction." + locale + ".js", filesInfos); 355 files.put("/plugins/cms/resources/js/Ametys/cms/uihelper/ChooseTagHelper." + locale + ".js", filesInfos); 356 files.put("/plugins/cms/resources/js/Ametys/cms/uihelper/ChooseTag." + locale + ".js", filesInfos); 357 files.put("/plugins/web/resources/js/Ametys/web/helper/ChooseTag." + locale + ".js", filesInfos); 358 files.put("/plugins/cms/resources/js/Ametys/plugins/cms/tag/TagActions." + locale + ".js", filesInfos); 359 files.put("/plugins/web/resources/js/Ametys/plugins/web/tag/TagsTreePanel." + locale + ".js", filesInfos); 360 361 // scheduled publication 362 files.put("/plugins/web/resources/js/Ametys/plugins/web/page/SchedulePublication." + locale + ".js", filesInfos); 363 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/plugins/web/page/SchedulePublication." + locale + ".js", filesInfos); 364 365 // ping 366 files.put("/plugins/core-ui/resources/js/Ametys/plugins/coreui/system/StartTimeChecker." + locale + ".js", filesInfos); 367 368 // actions on content 369 files.put("/plugins/cms/resources/js/Ametys/cms/uihelper/EditContent." + locale + ".js", filesInfos); 370 files.put("/plugins/web/resources/js/Ametys/plugins/web/zone/ZoneActions." + locale + ".js", filesInfos); 371 files.put("/plugins/web/resources/js/Ametys/plugins/web/zone/ZoneDAO." + locale + ".js", filesInfos); 372 373 // file widget (+cropping for image filter) 374 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/File." + locale + ".js", filesInfos); 375 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/form/widget/File." + locale + ".js", filesInfos); 376 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/File/FileSource." + locale + ".js", filesInfos); 377 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/File/External." + locale + ".js", filesInfos); 378 files.put("/plugins/cms/resources/js/Ametys/cms/form/widget/File/Resource." + locale + ".js", filesInfos); 379 files.put("/plugins/core-ui/resources/js/Ametys/helper/crop/CropDialog." + locale + ".js", filesInfos); 380 files.put("/plugins/core-ui/resources/js/Jcrop/jquery.Jcrop." + locale + ".js", filesInfos); 381 files.put("/plugins/core-ui/resources/js/Ametys/form/widget/Image." + locale + ".js", filesInfos); 382 files.put("/plugins/front-edition/resources/js/Ametys/override/Ametys/form/widget/Image." + locale + ".js", filesInfos); 383 384 // Let's add all richtextconfiguration files 385 _addRichTextConfigurationFiles(files, filesInfos, locale, true); 386 387 return _hashCache.createHash(files, locale); 388 } 389 390 /** 391 * Prepare a hashcode for css files 392 * @param locale The language code to use 393 * @param theme The url for the theme 394 * @return The new hashcode to read the minimified concatened file 395 */ 396 public static String prepareCSSFiles(String locale, String theme) 397 { 398 Map<String, String> filesInfos = new HashMap<>(); 399 filesInfos.put("media", ""); 400 filesInfos.put("tag", "link"); 401 402 Map<String, Map<String, String>> files = new LinkedHashMap<>(); 403 String themeName = StringUtils.substringAfterLast(theme, "/"); 404 files.put(theme + "/" + themeName + "-all.css", filesInfos); 405 files.put("/plugins/front-edition/resources/css/theme-ametys-base-light.css", filesInfos); 406 files.put("/plugins/core-ui/resources/font/ametys/AmetysIcon.css", filesInfos); 407 files.put("/plugins/core-ui/resources/css/Ametys/form/image.css", filesInfos); 408 files.put("/plugins/core-ui/resources/css/Jcrop/jquery.Jcrop.css", filesInfos); 409 files.put("/plugins/web/resources/css/pages/addpage.css", filesInfos); 410 files.put("/plugins/cms/resources/css/selectcontent.css", filesInfos); 411 files.put("/plugins/codemirror/resources/css/codemirror.css", filesInfos); 412 files.put("/plugins/codemirror/resources/js/addon/hint/show-hint.css", filesInfos); 413 414 // Let's add all richtextconfiguration files 415 _addRichTextConfigurationFiles(files, filesInfos, locale, false); 416 417 return _hashCache.createHash(files, locale); 418 } 419 420 private static List<ScriptFile> _getRichTextConfigurationFiles(boolean scripts, Map<String, Object> contextParameters) 421 { 422 List<ScriptFile> files = new ArrayList<>(); 423 424 for (String richTextConfigurationId : _richTextConfigurationExtensionPoint.getExtensionsIds()) 425 { 426 RichTextConfiguration richTextConfiguration = _richTextConfigurationExtensionPoint.getExtension(richTextConfigurationId); 427 for (String category : richTextConfiguration.getCategories()) 428 { 429 Set<ClientSideElement> convertors = richTextConfiguration.getConvertors(category, contextParameters); 430 if (convertors != null) 431 { 432 for (ClientSideElement convertor : convertors) 433 { 434 List<Script> scriptsToAdd = convertor.getScripts(Collections.EMPTY_MAP); 435 if (scriptsToAdd != null) 436 { 437 for (Script script : scriptsToAdd) 438 { 439 List<ScriptFile> scriptFiles = scripts ? script.getScriptFiles() : script.getCSSFiles(); 440 if (scriptFiles != null) 441 { 442 files.addAll(scriptFiles); 443 } 444 } 445 } 446 } 447 } 448 449 Set<ClientSideElement> validators = richTextConfiguration.getValidators(category, contextParameters); 450 if (validators != null) 451 { 452 for (ClientSideElement validator : validators) 453 { 454 List<Script> scriptsToAdd = validator.getScripts(Collections.EMPTY_MAP); 455 if (scriptsToAdd != null) 456 { 457 for (Script script : scriptsToAdd) 458 { 459 List<ScriptFile> scriptFiles = scripts ? script.getScriptFiles() : script.getCSSFiles(); 460 if (scriptFiles != null) 461 { 462 files.addAll(scriptFiles); 463 } 464 } 465 } 466 } 467 } 468 } 469 } 470 471 return files; 472 } 473 474 private static void _addRichTextConfigurationFiles(Map<String, Map<String, String>> files, Map<String, String> filesInfos, String locale, boolean scripts) 475 { 476 Map<String, Object> contextParameters = new HashMap<>(); 477 contextParameters.put("siteName", AmetysXSLTHelper.site()); 478 479 List<ScriptFile> richTextConfigurationFiles = _getRichTextConfigurationFiles(scripts, contextParameters); 480 for (ScriptFile file : richTextConfigurationFiles) 481 { 482 String uri = null; 483 if (file.isLangSpecific()) 484 { 485 if (file.getLangPaths().containsKey(locale)) 486 { 487 uri = file.getLangPaths().get(locale); 488 } 489 else if (file.getLangPaths().containsKey(file.getDefaultLang())) 490 { 491 uri = file.getLangPaths().get(file.getDefaultLang()); 492 } 493 } 494 else 495 { 496 uri = file.getPath(); 497 } 498 499 if (uri != null) 500 { 501 files.put(uri, filesInfos); 502 } 503 } 504 } 505 /** 506 * Check if we can display the front edition button for a specific right 507 * Checks : 508 * * Rendering context == front 509 * * Edition mode 510 * * Front_Edition_Access_Right on current page 511 * * RightId available on object 512 * @param rightId right to check (can be null) 513 * @param objectId id of page/content to check. Can be null or empty to get the current page 514 * @return true if all is OK, false if at least one is false 515 * @deprecated Use the version with three arguments 516 */ 517 @Deprecated 518 public static boolean hasFrontEditionRight(String rightId, String objectId) 519 { 520 return hasFrontEditionRight(rightId, objectId, true); 521 } 522 /** 523 * Check if we can display the front edition button for a specific right 524 * Checks : 525 * * Rendering context == front 526 * * Edition mode 527 * * Front_Edition_Access_Right on current page 528 * * RightId available on object 529 * @param rightId right to check (can be null) 530 * @param objectId id of page/content to check. Can be null or empty to get the current page 531 * @param editionModeOnly Check if the user is in edition mode 532 * @return true if all is OK, false if at least one is false 533 */ 534 public static boolean hasFrontEditionRight(String rightId, String objectId, boolean editionModeOnly) 535 { 536 String aoId = objectId; 537 if (StringUtils.isEmpty(aoId)) 538 { 539 aoId = AmetysXSLTHelper.pageId(); 540 } 541 542 if (StringUtils.isEmpty(aoId) // No page or content to check 543 || !RenderingContext.FRONT.toString().equals(AmetysXSLTHelper.renderingContext()) // We are no on front 544 || editionModeOnly && !AmetysXSLTHelper.isEditionMode() // Not in edition mode 545 || !hasFrontEditionRight()) // The user has the front edition right on current page 546 { 547 548 return false; 549 } 550 551 Request request = ContextHelper.getRequest(_context); 552 553 // Retrieve the current workspace. 554 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 555 556 try 557 { 558 // Force default workspace (rights have to be checked on default workspace) 559 // The target objectId can be a node page, available of front only when a first subpage will be created 560 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 561 562 if (StringUtils.contains(aoId, ";")) 563 { 564 String[] pageIds = StringUtils.split(aoId, ";"); 565 for (String pid : pageIds) 566 { 567 if (_rightManager.currentUserHasRight(rightId, _ametysObjectResolver.resolveById(pid)) == RightResult.RIGHT_ALLOW) // Check right on page (If this page does not exists an exception will be thrown... this is fine) 568 { 569 return true; 570 } 571 // else check the next one 572 } 573 return false; // No right inside the list 574 } 575 else 576 { 577 return StringUtils.isEmpty(rightId) // There is no right to check OR the user has the right 578 || _rightManager.currentUserHasRight(rightId, _ametysObjectResolver.resolveById(aoId)) == RightResult.RIGHT_ALLOW; // Check right on page (If this page does not exists an exception will be thrown... this is fine) 579 } 580 } 581 finally 582 { 583 // Restore workspace 584 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 585 } 586 } 587 588 /** 589 * Check if current user has right on Ametys object for front edition 590 * Checks : 591 * * Rendering context == front 592 * * Edition mode 593 * * Front_Edition_Access_Right on current page 594 * * RightId available on object 595 * @param rightId right to check (can be null) 596 * @param ao The ametys object check. Cannot be null 597 * @param editionModeOnly Check if the user is in edition mode 598 * @return true if all is OK, false if at least one is false 599 */ 600 public static boolean hasFrontEditionRight(String rightId, AmetysObject ao, boolean editionModeOnly) 601 { 602 if (!RenderingContext.FRONT.toString().equals(AmetysXSLTHelper.renderingContext()) // We are no on front 603 || editionModeOnly && !AmetysXSLTHelper.isEditionMode() // Not in edition mode 604 || !hasFrontEditionRight()) // The user has the front edition right on current page 605 { 606 return false; 607 } 608 609 Request request = ContextHelper.getRequest(_context); 610 611 // Retrieve the current workspace. 612 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 613 614 try 615 { 616 // Force default workspace (rights have to be checked on default workspace) 617 // The target objectId can be a node page, available of front only when a first subpage will be created 618 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 619 620 return StringUtils.isEmpty(rightId) // There is no right to check OR the user has the right 621 || _rightManager.currentUserHasRight(rightId, ao) == RightResult.RIGHT_ALLOW; // Check right on page (If this page does not exists an exception will be thrown... this is fine) 622 } 623 finally 624 { 625 // Restore workspace 626 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 627 } 628 } 629 630 /** 631 * Check if the current user has the Front_Edition_Access_Right right 632 * @return true if right granted 633 */ 634 public static boolean hasFrontEditionRight() 635 { 636 Request request = ContextHelper.getRequest(_context); 637 638 // Retrieve the current workspace. 639 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 640 641 try 642 { 643 // Force default workspace (rights have to be checked on default workspace) 644 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 645 646 String pageId = AmetysXSLTHelper.pageId(); 647 if (StringUtils.isNotEmpty(pageId)) 648 { 649 Page page = _ametysObjectResolver.resolveById(pageId); 650 return _rightManager.currentUserHasRight(FRONT_EDITION_RIGHT_ID, page) == RightResult.RIGHT_ALLOW; 651 } 652 653 String lang = AmetysXSLTHelper.lang(); 654 if (StringUtils.isEmpty(lang)) 655 { 656 lang = (String) request.getAttribute("lang"); 657 } 658 String siteName = AmetysXSLTHelper.site(); 659 660 if (StringUtils.isNotEmpty(siteName) && StringUtils.isNoneEmpty(lang)) 661 { 662 Site site = _siteManager.getSite(siteName); 663 Sitemap sitemap = site.getSitemap(lang); 664 return _rightManager.currentUserHasRight(FRONT_EDITION_RIGHT_ID, sitemap) == RightResult.RIGHT_ALLOW; 665 } 666 667 return false; 668 } 669 finally 670 { 671 // Restore workspace 672 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 673 } 674 675 676 } 677 /** 678 * Check if the current workflow action is available on this content 679 * @param actionId action to check 680 * @param contentId content to check 681 * @param checkEditionMode check also if we are in edition mode 682 * @return true if right is granted 683 */ 684 public static boolean hasWorkflowRight(int actionId, String contentId, boolean checkEditionMode) 685 { 686 List<Integer> actionIds = new ArrayList<>(); 687 actionIds.add(actionId); 688 return hasWorkflowRight(actionIds, contentId, checkEditionMode); 689 } 690 /** 691 * Check if a list of workflow actions are available on this content 692 * @param actionIds list of action ids 693 * @param contentId id of the content to check 694 * @param checkEditionMode check also if we are in edition mode 695 * @return true if all actions are available 696 */ 697 public static boolean hasWorkflowRight(List<Integer> actionIds, String contentId, boolean checkEditionMode) 698 { 699 Content content = _ametysObjectResolver.resolveById(contentId); 700 return hasWorkflowRight(actionIds, content, checkEditionMode); 701 } 702 /** 703 * Check if a list of workflow actions are available on this content 704 * @param actionIds list of action ids 705 * @param content content to check 706 * @param checkEditionMode check also if we are in edition mode 707 * @return true if all actions are available 708 */ 709 public static boolean hasWorkflowRight(List<Integer> actionIds, Content content, boolean checkEditionMode) 710 { 711 if (checkEditionMode) 712 { 713 boolean editionModeOk = AmetysXSLTHelper.isEditionMode(); 714 if (!editionModeOk) 715 { 716 return false; 717 } 718 } 719 boolean result = false; 720 if (content instanceof WorkflowAwareContent) 721 { 722 WorkflowAwareContent wcontent = (WorkflowAwareContent) content; 723 int[] availableActions = _contentWorkflowHelper.getAvailableActions(wcontent); 724 for (int actionId : actionIds) 725 { 726 result = result || ArrayUtils.contains(availableActions, actionId); 727 if (result) 728 { 729 break; 730 } 731 } 732 } 733 return result; 734 } 735 736 /** 737 * Get the toolbar configuration for page: retrieve the user rights, the parent and page position 738 * @param pageId The page id 739 * @param addContentsConfigAsJson The items configuration for adding contents as JSON string 740 * @param editionModeOnly true to check edition mode 741 * @return the user's rights, parent and page position 742 * @throws UnknownAmetysObjectException if content is unknown 743 * @throws AmetysRepositoryException if an error occurred 744 * @throws RepositoryException if an error occurred 745 */ 746 public static String getPageToolbarConfig(String pageId, String addContentsConfigAsJson, boolean editionModeOnly) throws UnknownAmetysObjectException, AmetysRepositoryException, RepositoryException 747 { 748 Map<String, Object> result = new HashMap<>(); 749 750 Page page = _ametysObjectResolver.resolveById(pageId); 751 SitemapElement parent = page.getParent(); 752 long position = _getPagePosition(page); 753 754 List<Object> addContentsConfig = _jsonUtils.convertJsonToList(addContentsConfigAsJson); 755 756 @SuppressWarnings("unchecked") 757 Set<Object> allowedContentTypes = addContentsConfig.stream() 758 .filter(Map.class::isInstance) 759 .filter(cfg -> _hasContentCreationRight((Map) cfg)) 760 .map(cfg -> (String) ((Map) cfg).get("contentType")) 761 .collect(Collectors.toSet()); 762 result.put("allowedContentTypes", allowedContentTypes); 763 764 result.put("title", page.getTitle()); 765 result.put("parentId", parent.getId()); 766 result.put("position", position); 767 768 // Get the active status for each action depending on user's rights, page position, FO edition mode, ... 769 result.put("actionStatus", _getActionStatusOnPage(page, position, parent.getChildrenPages().getSize(), editionModeOnly)); 770 771 return _jsonUtils.convertObjectToJson(result); 772 } 773 774 private static boolean _hasContentCreationRight(Map<String, Object> config) 775 { 776 if (config.containsKey("contentType")) 777 { 778 String cTypeId = (String) config.get("contentType"); 779 780 ContentType contentType = _contentTypesEP.getExtension(cTypeId); 781 if (contentType == null) 782 { 783 _logger.warn("Unknown content type '" + cTypeId + "'"); 784 return false; 785 } 786 787 String right = contentType.getRight(); 788 if (StringUtils.isEmpty(right)) 789 { 790 return true; 791 } 792 793 return _rightManager.currentUserHasRight(right, "/cms") == RightResult.RIGHT_ALLOW || _rightManager.currentUserHasRight(right, _rootContentHelper.getRootContent()) == RightResult.RIGHT_ALLOW; 794 } 795 else 796 { 797 _logger.warn("Configuration to add content is invalid: missing 'contentType'. It will be ignored"); 798 return false; 799 } 800 } 801 802 private static long _getPagePosition(Page page) throws AmetysRepositoryException 803 { 804 SitemapElement parent = page.getParent(); 805 AmetysObjectIterable< ? extends Page> children = parent.getChildrenPages(); 806 807 long count = 1; 808 for (Page child : children) 809 { 810 if (child.getId().equals(page.getId())) 811 { 812 return count; 813 } 814 count++; 815 } 816 817 // Not found 818 return -1; 819 } 820 821 private static Map<String, Boolean> _getActionStatusOnPage(Page page, long position, long size, boolean editionModeOnly) throws UnknownAmetysObjectException, AmetysRepositoryException 822 { 823 Map<String, Boolean> rights = new HashMap<>(); 824 825 if (page instanceof ModifiablePage) 826 { 827 rights.put("add-content", hasFrontEditionRight("Web_Rights_Page_AddContent", page, editionModeOnly)); 828 rights.put("add-page", hasFrontEditionRight("Web_Rights_Page_Create", page, editionModeOnly)); 829 rights.put("tag", hasFrontEditionRight("Web_Rights_Page_Tag", page, editionModeOnly)); 830 rights.put("rename", hasFrontEditionRight("Web_Rights_Page_Rename", page, editionModeOnly)); 831 rights.put("schedule-publication", hasFrontEditionRight("Web_Rights_Page_Schedule", page, editionModeOnly)); 832 rights.put("delete", hasFrontEditionRight("Web_Rights_Page_Delete", page, editionModeOnly)); 833 834 boolean canMove = hasFrontEditionRight("Web_Rights_Page_Create", page.getParent().getId(), editionModeOnly); 835 boolean moveUp = canMove && position > 1; 836 boolean moveDown = canMove && position < size; 837 838 rights.put("move-up", moveUp); 839 rights.put("move-down", moveDown); 840 rights.put("move", moveDown || moveUp); 841 } 842 843 return rights; 844 } 845 846 /** 847 * Get the toolbar configuration for content: retrieve user rights, available workflow actions, content current step and status history 848 * @param contentId The content id 849 * @param zoneItemId The zone item id 850 * @param restrictedActions The actions ids to retrieve. Set to null or empty to get all available workflow actions with no restriction 851 * @param jsonStepConfig The step configuration 852 * @param locale the locale for translation 853 * @param editionModeOnly true to check edition mode 854 * @return the user's rights, available workflow actions and content current step 855 * @throws UnknownAmetysObjectException if content is unknown 856 * @throws AmetysRepositoryException if an error occurred 857 * @throws RepositoryException if an error occurred 858 */ 859 public static String getContentToolbarConfig(String contentId, String zoneItemId, String restrictedActions, String jsonStepConfig, String locale, boolean editionModeOnly) throws UnknownAmetysObjectException, AmetysRepositoryException, RepositoryException 860 { 861 Map<String, Object> result = new HashMap<>(); 862 863 Content content = _ametysObjectResolver.resolveById(contentId); 864 865 // Get the active status of each action depending on user rights, FO mode, content's position, ... 866 result.put("actionStatus", _getActionStatusOnContent(content, zoneItemId, editionModeOnly)); 867 868 if (content instanceof WorkflowAwareContent) 869 { 870 WorkflowDescriptor workflowDescriptor = _getWorkflowDescriptor((WorkflowAwareContent) content); 871 872 WorkflowAwareContent wcontent = (WorkflowAwareContent) content; 873 874 Map<String, Object> step = new HashMap<>(); 875 long currentStepId = wcontent.getCurrentStepId(); 876 877 // Current step 878 StepDescriptor stepDescriptor = _getStepDescriptor(workflowDescriptor, currentStepId); 879 step.put("id", currentStepId); 880 String name = stepDescriptor.getName(); 881 step.put("label", new I18nizableText(StringUtils.substringBefore(name, ":"), StringUtils.substringAfter(name, ":"))); 882 result.put("step", step); 883 884 Map<String, Object> stepConfig = StringUtils.isEmpty(jsonStepConfig) ? null : _jsonUtils.convertJsonToMap(jsonStepConfig); 885 result.put("statusHistory", _getStatusHistory(wcontent, currentStepId, stepConfig, workflowDescriptor)); 886 887 List<Integer> availableActions = Arrays.stream(_contentWorkflowHelper.getAvailableActions(wcontent)).boxed().collect(Collectors.toList()); 888 result.put("availableWorkflowActions", availableActions); 889 890 if (!editionModeOnly || AmetysXSLTHelper.isEditionMode()) 891 { 892 List<Object> restrictedActionsAsList = StringUtils.isEmpty(restrictedActions) ? null : _jsonUtils.convertJsonToList(restrictedActions); 893 894 List<Object> restrictedActionIds = restrictedActionsAsList == null ? null : restrictedActionsAsList.stream() 895 .map(o -> o instanceof Map ? ((Map) o).get("id") : o) 896 .collect(Collectors.toList()); 897 898 @SuppressWarnings("unchecked") 899 Map<Object, Map<String, Object>> workflowActions = availableActions.stream() 900 .filter(actionId -> restrictedActionIds == null || restrictedActionIds.contains(actionId)) 901 .map(actionId -> { 902 Map<String, Object> wa = new HashMap<>(); 903 904 _addDefaultWorkflowActionProperties(wa, workflowDescriptor, actionId); 905 906 if (restrictedActionIds != null && restrictedActionsAsList != null && restrictedActionIds.contains(actionId)) 907 { 908 for (Object object : restrictedActionsAsList) 909 { 910 if (object instanceof Map && ((Map) object).get("id") == actionId) 911 { 912 // get or override properties from map sent by client side 913 wa.putAll((Map<String, Object>) object); 914 break; 915 } 916 } 917 918 } 919 return Pair.of(actionId, wa); 920 }) 921 .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); 922 923 result.put("workflowActions", workflowActions); 924 } 925 } 926 927 Request request = ContextHelper.getRequest(_context); 928 try 929 { 930 request.setAttribute(I18nizableSerializer.REQUEST_ATTR_LOCALE, locale); 931 return _jsonUtils.convertObjectToJson(result); 932 } 933 finally 934 { 935 request.removeAttribute(I18nizableSerializer.REQUEST_ATTR_LOCALE); 936 } 937 } 938 939 private static Map<String, Boolean> _getActionStatusOnContent(Content content, String zoneItemId, boolean editionModeOnly) throws UnknownAmetysObjectException, AmetysRepositoryException 940 { 941 Map<String, Boolean> rights = new HashMap<>(); 942 943 ZoneItem zoneItem = _ametysObjectResolver.resolveById(zoneItemId); 944 Zone zone = zoneItem.getZone(); 945 SitemapElement sitemapElement = zone.getSitemapElement(); 946 947 rights.put("tag", hasFrontEditionRight("CMS_Rights_Content_Tag", content, editionModeOnly)); 948 rights.put("remove", sitemapElement instanceof ModifiablePage && hasFrontEditionRight("Web_Rights_Page_DeleteZoneItem", sitemapElement, editionModeOnly)); 949 rights.put("delete", hasFrontEditionRight("CMS_Rights_DeleteContent", content, editionModeOnly)); 950 951 boolean moveUp = false; 952 boolean moveDown = false; 953 954 long position = getZoneItemPosition(zoneItem); 955 long size = getZoneSize(zone); 956 957 if (position != -1 && sitemapElement instanceof ModifiablePage && hasFrontEditionRight("Web_Rights_Page_OrganizeZoneItem", sitemapElement, editionModeOnly)) 958 { 959 if (position > 0) 960 { 961 moveUp = true; 962 } 963 964 if (position + 1 < size) 965 { 966 moveDown = true; 967 } 968 } 969 rights.put("move-up", moveUp); 970 rights.put("move-down", moveDown); 971 rights.put("move", moveDown || moveUp); 972 973 return rights; 974 } 975 976 private static Map<Integer, Map<String, Object>> _getStatusHistory(WorkflowAwareContent content, long currentStepId, Map<String, Object> stepsConfig, WorkflowDescriptor workflowDescriptor) 977 { 978 List<Step> historyStatus = new ArrayList<>(); 979 980 if (stepsConfig.containsKey(String.valueOf(currentStepId))) 981 { 982 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content); 983 984 long workflowId = content.getWorkflowId(); 985 List<Step> historySteps = workflow.getHistorySteps(workflowId); 986 987 // Sort by start date descendant 988 Collections.sort(historySteps, new Comparator<Step>() 989 { 990 public int compare(Step s1, Step s2) 991 { 992 return -s1.getStartDate().compareTo(s2.getStartDate()); 993 } 994 }); 995 996 @SuppressWarnings("unchecked") 997 Map<String, Object> currentStepCfg = (Map<String, Object>) stepsConfig.get(String.valueOf(currentStepId)); 998 String currentStepType = currentStepCfg != null ? (String) currentStepCfg.get("type") : null; 999 1000 boolean stop = false; 1001 for (Step step : historySteps) 1002 { 1003 if (stop) 1004 { 1005 break; 1006 } 1007 1008 long stepId = step.getStepId(); 1009 @SuppressWarnings("unchecked") 1010 Map<String, Object> stepCfg = (Map<String, Object>) stepsConfig.get(String.valueOf(stepId)); 1011 String stepType = stepCfg != null ? (String) stepCfg.get("type") : null; 1012 1013 stop = "positive".equals(currentStepType) && !"positive".equals(stepType) 1014 || ("negative".equals(currentStepType) || "neutral".equals(currentStepType)) && !"negative".equals(stepType) 1015 || currentStepId == stepId; 1016 1017 if (!stop) 1018 { 1019 historyStatus.add(step); 1020 } 1021 } 1022 } 1023 1024 Map<Integer, Map<String, Object>> jsonHistoryStatus = new LinkedHashMap<>(); 1025 for (Step step : historyStatus) 1026 { 1027 if (!jsonHistoryStatus.containsKey(step.getStepId())) 1028 { 1029 StepDescriptor stepDescriptor = _getStepDescriptor(workflowDescriptor, step.getStepId()); 1030 String name = stepDescriptor.getName(); 1031 I18nizableText label = new I18nizableText(StringUtils.substringBefore(name, ":"), StringUtils.substringAfter(name, ":")); 1032 jsonHistoryStatus.put(step.getStepId(), Map.of("label", label)); 1033 } 1034 } 1035 1036 return jsonHistoryStatus; 1037 } 1038 1039 private static void _addDefaultWorkflowActionProperties(Map<String, Object> wa, WorkflowDescriptor workflowDescriptor, int actionId) 1040 { 1041 // Add workflow action label 1042 ActionDescriptor wAction = workflowDescriptor.getAction(actionId); 1043 String name = wAction.getName(); 1044 wa.put("label", new I18nizableText(StringUtils.substringBefore(name, ":"), StringUtils.substringAfter(name, ":"))); 1045 1046 // Default workflow icon 1047 switch (actionId) 1048 { 1049 case 3: 1050 wa.put("icon", "fas fa-hand-point-up"); // Propose 1051 break; 1052 case 4: 1053 wa.put("icon", "fas fa-check"); // Validate 1054 break; 1055 case 7: 1056 wa.put("icon", "fas fa-times"); // Refuse 1057 break; 1058 case 10: 1059 wa.put("icon", "fas fa-ban"); // Unpublish 1060 break; 1061 default: 1062 break; 1063 } 1064 } 1065 1066 private static WorkflowDescriptor _getWorkflowDescriptor(WorkflowAwareContent content) 1067 { 1068 long workflowId = content.getWorkflowId(); 1069 1070 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content); 1071 1072 String workflowName = workflow.getWorkflowName(workflowId); 1073 return workflow.getWorkflowDescriptor(workflowName); 1074 } 1075 1076 private static StepDescriptor _getStepDescriptor(WorkflowDescriptor workflowDescriptor, long stepId) 1077 { 1078 if (workflowDescriptor != null) 1079 { 1080 StepDescriptor stepDescriptor = workflowDescriptor.getStep((int) stepId); 1081 if (stepDescriptor != null) 1082 { 1083 return stepDescriptor; 1084 } 1085 } 1086 1087 return null; 1088 } 1089 1090 /** 1091 * Get a string representing a json map of the contentIds with true/false if they can/can't be modified 1092 * @param actionId action to check 1093 * @param inputPageId page (can be null) 1094 * @param checkEditionMode check also if we are in edition mode 1095 * @return {contentId : true/false, ....} 1096 */ 1097 public static String getModifiableContents(int actionId, String inputPageId, boolean checkEditionMode) 1098 { 1099 Map<String, Object> jsonObject = new HashMap<>(); 1100 String pageId = inputPageId; 1101 if (StringUtils.isEmpty(inputPageId)) 1102 { 1103 pageId = AmetysXSLTHelper.pageId(); 1104 } 1105 1106 if (!StringUtils.isEmpty(pageId)) 1107 { 1108 AmetysObject resolveById = _ametysObjectResolver.resolveById(pageId); 1109 if (resolveById instanceof Page) 1110 { 1111 Page page = (Page) resolveById; 1112 List<Content> contents = _pageHelper.getAllContents(page); 1113 for (Content content : contents) 1114 { 1115 Map<String, Object> contentInfo = new HashMap<>(); 1116 contentInfo.put("unmodifiableAttributes", AmetysFrontEditionHelper.getUnmodifiableAttributes(content, List.of(actionId), checkEditionMode)); 1117 contentInfo.put("rights", AmetysFrontEditionHelper.getRightsForContent(content)); 1118 1119 jsonObject.put(content.getId(), contentInfo); 1120 } 1121 } 1122 } 1123 1124 return _jsonUtils.convertObjectToJson(jsonObject); 1125 } 1126 1127 /** 1128 * Get path of unmodifiable attributes. Check if content can be modified and the restrictions on attributes 1129 * @param content the content 1130 * @param actionIds the workflow action ids to check 1131 * @param checkEditionMode to check edition mode 1132 * @return the list with the path of unmodifiable attributes (regarding of restrictions) or null if the content cannot be modified 1133 */ 1134 public static List<String> getUnmodifiableAttributes(Content content, List<Integer> actionIds, boolean checkEditionMode) 1135 { 1136 try 1137 { 1138 boolean hasWorkflowRight = hasWorkflowRight(actionIds, content, checkEditionMode); 1139 if (!hasWorkflowRight) 1140 { 1141 // content is not editable 1142 return null; 1143 } 1144 1145 Collection<? extends ModelItem> modelItems = _contentTypesHelper.getModelItems(content.getTypes()); 1146 List<AttributeDefinition> attributes = _getAttributeDefinitionsRecursively(modelItems); 1147 return attributes.stream() 1148 .filter(mi -> !mi.canWrite(content)) 1149 .map(mi -> mi.getPath()) 1150 .collect(Collectors.toList()); 1151 } 1152 catch (IllegalArgumentException e) 1153 { 1154 _logger.error("Fail to get restricted model items for content " + content.getId(), e); 1155 return List.of(); 1156 } 1157 } 1158 1159 /** 1160 * Get a map of chosen rights for a content 1161 * @param content the content 1162 * @return the chosen rights for a content 1163 */ 1164 public static Map<String, Object> getRightsForContent(Content content) 1165 { 1166 Map<String, Object> rights = new HashMap<>(); 1167 rights.put("hasAddFileRight", _rightManager.currentUserHasRight(ExplorerResourcesDAO.RIGHTS_RESOURCE_ADD, content) == RightResult.RIGHT_ALLOW); 1168 return rights; 1169 } 1170 1171 private static List<AttributeDefinition> _getAttributeDefinitionsRecursively(Collection<? extends ModelItem> modelItems) 1172 { 1173 List<AttributeDefinition> attributeDefs = new ArrayList<>(); 1174 1175 for (ModelItem modelItem : modelItems) 1176 { 1177 if (modelItem instanceof AttributeDefinition) 1178 { 1179 attributeDefs.add((AttributeDefinition) modelItem); 1180 } 1181 else if (modelItem instanceof ModelItemGroup) 1182 { 1183 attributeDefs.addAll(_getAttributeDefinitionsRecursively(((ModelItemGroup) modelItem).getModelItems())); 1184 } 1185 } 1186 1187 return attributeDefs; 1188 } 1189 1190 /** 1191 * Get the name of a workflow action 1192 * @param workflowName workflow name 1193 * @param actionId action id in the workflow 1194 * @return name of the workflow action 1195 */ 1196 public static String getWorkflowName(String workflowName, int actionId) 1197 { 1198 String workflowNameKey = _workflowHelper.getActionName(workflowName, actionId); 1199 String translatedName = org.ametys.core.util.AmetysXSLTHelper.translate(workflowNameKey); 1200 return translatedName; 1201 } 1202 /** 1203 * Get the status (validated/draft) of all contents in a page 1204 * @param pageId id of the page to check 1205 * @return "" if not a page, "draft" if all contents are drafts, "validated" if all contents are validated, and "mixed" if there are both 1206 */ 1207 public static String getPageStatus(String pageId) 1208 { 1209 boolean hasDraft = false; 1210 boolean hasValidated = false; 1211 AmetysObject resolveById = _ametysObjectResolver.resolveById(pageId); 1212 if (resolveById instanceof Page) 1213 { 1214 Page page = (Page) resolveById; 1215 List<Content> contents = _pageHelper.getAllContents(page); 1216 for (Content content : contents) 1217 { 1218 if (isContentLive(content)) 1219 { 1220 hasValidated = true; 1221 } 1222 else 1223 { 1224 hasDraft = true; 1225 } 1226 } 1227 String result = "none"; 1228 if (hasDraft && !hasValidated) 1229 { 1230 result = "draft"; 1231 } 1232 else if (!hasDraft && hasValidated) 1233 { 1234 result = "validated"; 1235 } 1236 else if (hasDraft && hasValidated) 1237 { 1238 result = "mixed"; 1239 } 1240 return result; 1241 } 1242 else 1243 { 1244 return ""; 1245 } 1246 } 1247 1248 /** 1249 * Get the workflow step id of a content 1250 * @param contentId id of the content 1251 * @return the step Id 1252 */ 1253 public static long getContentWorkflowId(String contentId) 1254 { 1255 AmetysObject resolvedById = _ametysObjectResolver.resolveById(contentId); 1256 if (resolvedById instanceof Content) 1257 { 1258 Content content = (Content) resolvedById; 1259 return getContentWorkflowId(content); 1260 } 1261 else 1262 { 1263 return 0; 1264 } 1265 } 1266 /** 1267 * Get the workflow step id of a content 1268 * @param content content 1269 * @return the step Id 1270 */ 1271 public static long getContentWorkflowId(Content content) 1272 { 1273 WorkflowAwareContent wContent = (WorkflowAwareContent) content; 1274 return wContent.getCurrentStepId(); 1275 } 1276 1277 /** 1278 * check if a content is published ot not 1279 * @param contentId id of the content 1280 * @return "validated" or "draft", "" if not found 1281 */ 1282 public static String getContentStatus(String contentId) 1283 { 1284 AmetysObject resolvedById = _ametysObjectResolver.resolveById(contentId); 1285 if (resolvedById instanceof Content) 1286 { 1287 Content content = (Content) resolvedById; 1288 return getContentStatus(content); 1289 } 1290 else 1291 { 1292 return ""; 1293 } 1294 } 1295 /** 1296 * check if a content is published ot not 1297 * @param content content 1298 * @return "validated" or "draft" 1299 */ 1300 public static String getContentStatus(Content content) 1301 { 1302 return isContentLive(content) ? "validated" : "draft"; 1303 } 1304 /** 1305 * check if a content is published ot not 1306 * @param contentId id of the content 1307 * @return true if validated 1308 */ 1309 public static boolean isContentLive(String contentId) 1310 { 1311 AmetysObject resolvedById = _ametysObjectResolver.resolveById(contentId); 1312 if (resolvedById instanceof Content) 1313 { 1314 Content content = (Content) resolvedById; 1315 return isContentLive(content); 1316 } 1317 else 1318 { 1319 return false; 1320 } 1321 } 1322 /** 1323 * check if a content is published ot not 1324 * @param content content 1325 * @return true if validated 1326 */ 1327 public static boolean isContentLive(Content content) 1328 { 1329 return Arrays.asList(((VersionableAmetysObject) content).getLabels()).contains(CmsConstants.LIVE_LABEL); 1330 } 1331 /** 1332 * Get the position of this zoneItem in the zone, or -1 if not found 1333 * @param zoneItemId zoneitem to search 1334 * @return zero based position, -1 if not found 1335 * @throws UnknownAmetysObjectException If zone item is not found 1336 * @throws AmetysRepositoryException If an error occurred 1337 */ 1338 public static long getZoneItemPosition(String zoneItemId) throws UnknownAmetysObjectException, AmetysRepositoryException 1339 { 1340 ZoneItem zoneItem = _ametysObjectResolver.resolveById(zoneItemId); 1341 return getZoneItemPosition(zoneItem); 1342 } 1343 1344 /** 1345 * Get the position of this zoneItem in its parent zone, or -1 if not found 1346 * @param zoneItem zoneitem 1347 * @return zero based position, -1 if not found 1348 */ 1349 public static long getZoneItemPosition(ZoneItem zoneItem) 1350 { 1351 AmetysObject parent = zoneItem.getParent(); 1352 if (parent instanceof DefaultTraversableAmetysObject) 1353 { 1354 return ((DefaultTraversableAmetysObject) parent).getChildPosition(zoneItem); 1355 } 1356 return -1; // virtual page 1357 } 1358 1359 /** 1360 * Return the size of a zone 1361 * @param zoneName zone name 1362 * @param pageId page Id 1363 * @return size of the zone (-1 if not found) 1364 */ 1365 public static long getZoneSize(String zoneName, String pageId) 1366 { 1367 Page page = _ametysObjectResolver.resolveById(pageId); 1368 Zone zone = page.getZone(zoneName); 1369 return getZoneSize(zone); 1370 } 1371 1372 /** 1373 * Return the size of a zone 1374 * @param zone the zone 1375 * @return size of the zone or -1 is zone is null 1376 */ 1377 public static long getZoneSize(Zone zone) 1378 { 1379 if (zone != null) 1380 { 1381 return zone.getZoneItems().getSize(); 1382 } 1383 return -1; 1384 } 1385 /** 1386 * Test if a page is modifiable 1387 * @param pageId id of the page to check 1388 * @return true if the page is modifiable, false otherwise 1389 */ 1390 public static boolean isPageModifiable(String pageId) 1391 { 1392 try 1393 { 1394 AmetysObject resolveById = _ametysObjectResolver.resolveById(pageId); 1395 return resolveById instanceof ModifiablePage; 1396 } 1397 catch (AmetysRepositoryException e) 1398 { 1399 // Nothing, this is not a page 1400 } 1401 1402 return false; 1403 } 1404 1405 /** 1406 * Returns the ids of the pages tagged with the specified tag in default workspace 1407 * @param tag The tag id 1408 * @return Array of pages ids 1409 */ 1410 public static NodeList findPagesIdsByTagInDefaultWorkspace(String tag) 1411 { 1412 return findPagesIdsByTagInDefaultWorkspace(tag, false); 1413 } 1414 1415 /** 1416 * Returns the ids of the pages tagged with the specified tag in default workspace 1417 * @param tag The tag id 1418 * @param checkReadAccess true to return only pages with read access for current user 1419 * @return Array of pages ids 1420 */ 1421 public static NodeList findPagesIdsByTagInDefaultWorkspace(String tag, boolean checkReadAccess) 1422 { 1423 Request request = ContextHelper.getRequest(_context); 1424 1425 // Retrieve the current workspace. 1426 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1427 1428 try 1429 { 1430 // Force default workspace 1431 // The tagged page can be a node page, available of front only when a first subpage will be created 1432 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 1433 return AmetysXSLTHelper.findPagesIdsByTag(tag, checkReadAccess); 1434 } 1435 finally 1436 { 1437 // Restore workspace 1438 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1439 } 1440 } 1441 /** 1442 * Returns the ids of the pages tagged with the specified tag in default workspace 1443 * @param sitename The site id 1444 * @param lang The language code 1445 * @param tag The tag id 1446 * @return Array of pages ids 1447 */ 1448 public static NodeList findPagesIdsByTagInDefaultWorkspace(String sitename, String lang, String tag) 1449 { 1450 return findPagesIdsByTagInDefaultWorkspace(sitename, lang, tag, false); 1451 } 1452 1453 /** 1454 * Returns the ids of the pages tagged with the specified tag in default workspace 1455 * @param sitename The site id 1456 * @param lang The language code 1457 * @param tag The tag id 1458 * @param checkReadAccess true to return only pages with read access for current user 1459 * @return Array of pages ids 1460 */ 1461 public static NodeList findPagesIdsByTagInDefaultWorkspace(String sitename, String lang, String tag, boolean checkReadAccess) 1462 { 1463 Request request = ContextHelper.getRequest(_context); 1464 1465 // Retrieve the current workspace. 1466 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 1467 1468 try 1469 { 1470 // Force default workspace 1471 // The tagged page can be a node page, available of front only when a first subpage will be created 1472 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE); 1473 return AmetysXSLTHelper.findPagesIdsByTag(sitename, lang, tag, checkReadAccess); 1474 } 1475 finally 1476 { 1477 // Restore workspace 1478 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 1479 } 1480 } 1481}