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