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