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