001/* 002 * Copyright 2020 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.workspaces.minisite; 017 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021import java.util.Objects; 022import java.util.Set; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.commons.lang.StringUtils; 027 028import org.ametys.cms.data.Binary; 029import org.ametys.cms.repository.Content; 030import org.ametys.cms.repository.ModifiableWorkflowAwareContent; 031import org.ametys.cms.search.content.ContentSearcherFactory; 032import org.ametys.core.observation.Event; 033import org.ametys.plugins.repository.AmetysObjectIterable; 034import org.ametys.plugins.repository.jcr.NameHelper; 035import org.ametys.plugins.workflow.AbstractWorkflowComponent; 036import org.ametys.plugins.workflow.support.WorkflowProvider; 037import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 038import org.ametys.plugins.workspaces.AbstractWorkspaceModule; 039import org.ametys.plugins.workspaces.ObservationConstants; 040import org.ametys.plugins.workspaces.WorkspacesConstants; 041import org.ametys.plugins.workspaces.project.ProjectConstants; 042import org.ametys.plugins.workspaces.project.objects.Project; 043import org.ametys.plugins.workspaces.util.StatisticColumn; 044import org.ametys.plugins.workspaces.util.StatisticsColumnType; 045import org.ametys.runtime.i18n.I18nizableText; 046import org.ametys.web.repository.page.ModifiablePage; 047import org.ametys.web.repository.page.ModifiableZone; 048import org.ametys.web.repository.page.ModifiableZoneItem; 049import org.ametys.web.repository.page.Page.PageType; 050import org.ametys.web.repository.page.ZoneItem.ZoneType; 051import org.ametys.web.repository.site.Site; 052import org.ametys.web.search.query.SiteQuery; 053import org.ametys.web.workflow.CreateContentFunction; 054 055import com.google.common.collect.ImmutableSet; 056import com.opensymphony.workflow.WorkflowException; 057 058/** 059 * Workspaces module for editorial pages 060 */ 061public class MiniSiteWorkspaceModule extends AbstractWorkspaceModule 062{ 063 /** Avalon ROLE */ 064 public static final String MINISITE_MODULE_ID = MiniSiteWorkspaceModule.class.getName(); 065 066 /** Workspaces tasks list node name */ 067 private static final String __WORKSPACES_MINISITE_NODE_NAME = "minisite"; 068 069 private static final int __INITIAL_WORKFLOW_ACTION_ID = 13; 070 071 private static final String __ARTICLE_NUMBER_HEADER_ID = __WORKSPACES_MINISITE_NODE_NAME + "$article_number"; 072 073 /** The workflow provider */ 074 protected WorkflowProvider _workflowProvider; 075 076 /** The content searcher factory */ 077 protected ContentSearcherFactory _contentSearcherFactory; 078 079 @Override 080 public void service(ServiceManager manager) throws ServiceException 081 { 082 super.service(manager); 083 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 084 _contentSearcherFactory = (ContentSearcherFactory) manager.lookup(ContentSearcherFactory.ROLE); 085 } 086 087 @Override 088 public String getId() 089 { 090 return MINISITE_MODULE_ID; 091 } 092 093 public boolean isUnactivatedByDefault() 094 { 095 return true; 096 } 097 098 @Override 099 public String getModuleName() 100 { 101 return __WORKSPACES_MINISITE_NODE_NAME; 102 } 103 104 public int getOrder() 105 { 106 return ORDER_MINISITE; 107 } 108 109 @Override 110 protected String getModulePageName() 111 { 112 return "minisite"; 113 } 114 115 public I18nizableText getModuleTitle() 116 { 117 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_MINISITE_LABEL"); 118 } 119 public I18nizableText getModuleDescription() 120 { 121 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_MINISITE_DESCRIPTION"); 122 } 123 @Override 124 protected I18nizableText getModulePageTitle() 125 { 126 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_MINISITE_TITLE"); 127 } 128 129 /** 130 * Get the name of the first section of minisite 131 * @return the section's name 132 */ 133 protected String getFirstSectionName() 134 { 135 return "section"; 136 } 137 138 /** 139 * Get the title of the first section of minisite 140 * @return the section's title 141 */ 142 protected I18nizableText getFirstSectionTitle() 143 { 144 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_MINISITE_FIRST_SECTION_TITLE"); 145 } 146 147 @Override 148 protected String getModulePageTemplate() 149 { 150 // the module page is a NODE page 151 return null; 152 } 153 154 @Override 155 protected void initializeModulePage(ModifiablePage modulePage) 156 { 157 modulePage.untag("SECTION"); 158 159 ModifiablePage firstSection = _createAndInitializeFirstSectionPage(modulePage, getFirstSectionName(), getFirstSectionTitle()); 160 if (firstSection != null) 161 { 162 _initializeFirstSectionDefaultZone(firstSection); 163 } 164 } 165 166 /** 167 * Create the first section of minisite if does not exist 168 * @param modulePage the module page 169 * @param name the name of section to create 170 * @param pageTitle the page title 171 * @return the created page or null if already exist 172 */ 173 protected ModifiablePage _createAndInitializeFirstSectionPage(ModifiablePage modulePage, String name, I18nizableText pageTitle) 174 { 175 if (!modulePage.hasChild(name)) 176 { 177 ModifiablePage page = modulePage.createChild(name, "ametys:defaultPage"); 178 179 // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language 180 // to prevent a non-user-friendly error and still generate the project workspace. 181 page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(pageTitle, modulePage.getSitemapName()), "Missing title")); 182 page.setType(PageType.CONTAINER); 183 page.setSiteName(modulePage.getSiteName()); 184 page.setSitemapName(modulePage.getSitemapName()); 185 page.setTemplate(ProjectConstants.MINISITE_TEMPLATE); 186 page.tag("SECTION"); 187 188 page.saveChanges(); 189 190 Map<String, Object> eventParams = new HashMap<>(); 191 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 192 _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams)); 193 194 return page; 195 } 196 else 197 { 198 return null; 199 } 200 } 201 202 /** 203 * Initialize the default zone for the first section of mini site 204 * @param page The first page of minisite 205 */ 206 protected void _initializeFirstSectionDefaultZone(ModifiablePage page) 207 { 208 ModifiableZone defaultZone = page.createZone("default"); 209 210 ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem(); 211 defaultZoneItem.setType(ZoneType.CONTENT); 212 213 try 214 { 215 ModifiableWorkflowAwareContent content = _createDefaultContent(page.getSite(), page.getSitemapName(), page.getTitle()); 216 defaultZoneItem.setContent(content); 217 content.saveChanges(); 218 } 219 catch (WorkflowException e) 220 { 221 getLogger().error("Unable to initialize the first page for minisite module, the page will not be editable until the content is manually created in the BackOffice", e); 222 } 223 } 224 225 /** 226 * Create a new content for a minisite page of the minisite module 227 * @param site The site 228 * @param sitemapName the name of the sitemap 229 * @param title The content title 230 * @return The content 231 * @throws WorkflowException if an error occurred 232 */ 233 protected ModifiableWorkflowAwareContent _createDefaultContent(Site site, String sitemapName, String title) throws WorkflowException 234 { 235 Map<String, Object> inputs = new HashMap<>(); 236 inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_TITLE_KEY, title); 237 inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_NAME_KEY, NameHelper.filterName(title)); 238 inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_TYPES_KEY, new String[] {WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE}); 239 inputs.put(org.ametys.cms.workflow.CreateContentFunction.CONTENT_LANGUAGE_KEY, sitemapName); 240 inputs.put(CreateContentFunction.SITE_KEY, site.getName()); 241 242 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(); 243 workflow.initialize(WorkspacesConstants.CONTENT_WORKFLOW_NAME, __INITIAL_WORKFLOW_ACTION_ID, inputs); 244 245 @SuppressWarnings("unchecked") 246 Map<String, Object> results = (Map<String, Object>) inputs.get(AbstractWorkflowComponent.RESULT_MAP_KEY); 247 ModifiableWorkflowAwareContent content = (ModifiableWorkflowAwareContent) results.get(Content.class.getName()); 248 249 return content; 250 } 251 252 @Override 253 public Set<String> getAllowedEventTypes() 254 { 255 return ImmutableSet.of(ObservationConstants.EVENT_MINISITE_PAGE_CREATED, 256 ObservationConstants.EVENT_MINISITE_PAGE_UPDATED, 257 ObservationConstants.EVENT_MINISITE_PAGE_RENAMED, 258 ObservationConstants.EVENT_MINISITE_PAGE_DELETED); 259 } 260 261 @Override 262 public Map<String, Object> _getInternalStatistics(Project project, boolean isActive) 263 { 264 if (isActive) 265 { 266 Map<String, Object> statistics = new HashMap<>(); 267 try 268 { 269 AmetysObjectIterable<Content> results = _contentSearcherFactory.create(WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE) 270 .search(new SiteQuery(project.getName())); 271 statistics.put(__ARTICLE_NUMBER_HEADER_ID, results.getSize()); 272 } 273 catch (Exception e) 274 { 275 getLogger().error("Error searching wall content in project " + project.getId(), e); 276 } 277 return statistics; 278 } 279 else 280 { 281 return Map.of(__ARTICLE_NUMBER_HEADER_ID, __SIZE_INACTIVE); 282 } 283 } 284 285 @Override 286 public List<StatisticColumn> _getInternalStatisticModel() 287 { 288 return List.of(new StatisticColumn(__ARTICLE_NUMBER_HEADER_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MINISITE_ARTICLE_NUMBER")) 289 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderElements") 290 .withType(StatisticsColumnType.LONG) 291 .withGroup(GROUP_HEADER_ELEMENTS_ID)); 292 } 293 294 @Override 295 protected long _getModuleSize(Project project) 296 { 297 try 298 { 299 AmetysObjectIterable<Content> resultsIterable = _contentSearcherFactory.create(WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE) 300 .search(new SiteQuery(project.getName())); 301 302 return resultsIterable.stream() 303 .map(content -> content.getValue("illustration/image")) 304 .filter(Objects::nonNull) 305 .filter(Binary.class::isInstance) 306 .map(Binary.class::cast) 307 .mapToLong(Binary::getLength) 308 .sum(); 309 } 310 catch (Exception e) 311 { 312 getLogger().error("Error searching minisite content images in project " + project.getId(), e); 313 return __SIZE_ERROR; 314 } 315 } 316 317 @Override 318 protected boolean _showModuleSize() 319 { 320 return true; 321 } 322}