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.wall; 017 018import java.util.ArrayList; 019import java.util.List; 020import java.util.Set; 021import java.util.stream.Collectors; 022 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.commons.lang.IllegalClassException; 026 027import org.ametys.cms.repository.ContentQueryHelper; 028import org.ametys.cms.repository.ContentTypeExpression; 029import org.ametys.plugins.explorer.ExplorerNode; 030import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 031import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory; 032import org.ametys.plugins.repository.AmetysObject; 033import org.ametys.plugins.repository.AmetysRepositoryException; 034import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 035import org.ametys.plugins.repository.query.expression.AndExpression; 036import org.ametys.plugins.repository.query.expression.Expression; 037import org.ametys.plugins.repository.query.expression.Expression.Operator; 038import org.ametys.plugins.repository.query.expression.OrExpression; 039import org.ametys.plugins.repository.query.expression.StringExpression; 040import org.ametys.plugins.workspaces.AbstractWorkspaceModule; 041import org.ametys.plugins.workspaces.ObservationConstants; 042import org.ametys.plugins.workspaces.WorkspacesConstants; 043import org.ametys.plugins.workspaces.project.objects.Project; 044import org.ametys.runtime.i18n.I18nizableText; 045import org.ametys.web.repository.content.WebContentDAO; 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; 050import org.ametys.web.repository.page.ZoneDAO; 051import org.ametys.web.repository.page.ZoneItem; 052import org.ametys.web.repository.page.ZoneItem.ZoneType; 053import org.ametys.web.repository.site.Site; 054import org.ametys.web.repository.sitemap.Sitemap; 055 056import com.google.common.collect.ImmutableSet; 057 058/** 059 * Workspace module for wall content 060 */ 061public class WallContentModule extends AbstractWorkspaceModule 062{ 063 /** The id of calendar module */ 064 public static final String WALLCONTENT_MODULE_ID = WallContentModule.class.getName(); 065 066 /** The id of wall content service */ 067 public static final String WALLCONTENT_SERVICE_ID = "org.ametys.web.service.SearchService"; 068 069 /** Search service content types */ 070 protected static final String[] SEARCH_SERVICE_CONTENT_TYPES = new String[] {WorkspacesConstants.WALL_CONTENT_CONTENT_TYPE_ID}; 071 072 /** Search service returnables */ 073 protected static final String[] SEARCH_SERVICE_RETURNABLES = new String[] {"org.ametys.web.frontoffice.search.metamodel.impl.ContentReturnable"}; 074 075 /** Search service sorts */ 076 protected static final String[] SEARCH_SERVICE_SORTS = new String[] {"{\"name\":\"ContentReturnable$ContentSearchable$indexingField$org.ametys.plugins.workspaces.Content.wallContent$pinned\",\"sort\":\"DESC\"}", "{\"name\":\"ContentReturnable$ContentSearchable$systemProperty$creationDate\",\"sort\":\"DESC\"}"}; 077 078 /** Search service contexts */ 079 protected static final String[] SEARCH_SERVICE_CONTEXTS = new String[] {"{\"sites\":\"{\\\"context\\\":\\\"CURRENT_SITE\\\",\\\"sites\\\":[]}\",\"search-sitemap-context\":\"{\\\"context\\\":\\\"CURRENT_SITE\\\",\\\"page\\\":null}\",\"context-lang\":\"CURRENT\",\"tags\":[]}"}; 080 081 /** Search service xslt */ 082 protected static final String SEARCH_SERVICE_XSLT = "pages/services/search/wall-content.xsl"; 083 084 /** Workspaces wallcontent node name */ 085 private static final String __WORKSPACES_WALLCONTENT_NODE_NAME = "wallcontent"; 086 087 /** The zone DAO */ 088 protected ZoneDAO _zoneDAO; 089 /** The content DAO */ 090 protected WebContentDAO _contentDAO; 091 092 @Override 093 public void service(ServiceManager manager) throws ServiceException 094 { 095 super.service(manager); 096 _zoneDAO = (ZoneDAO) manager.lookup(ZoneDAO.ROLE); 097 _contentDAO = (WebContentDAO) manager.lookup(WebContentDAO.ROLE); 098 } 099 100 101 public String getId() 102 { 103 return WALLCONTENT_MODULE_ID; 104 } 105 106 public String getModuleName() 107 { 108 return __WORKSPACES_WALLCONTENT_NODE_NAME; 109 } 110 111 public int getOrder() 112 { 113 return ORDER_WALLCONTENT; 114 } 115 116 public Set<String> getAllowedEventTypes() 117 { 118 return ImmutableSet.of(ObservationConstants.EVENT_WALLCONTENT_ADDED); 119 } 120 121 public ModifiableResourceCollection getModuleRoot(Project project, boolean create) 122 { 123 // No root for this module, but root is still needed to handle rights on it 124 try 125 { 126 ExplorerNode projectRootNode = project.getExplorerRootNode(); 127 128 if (projectRootNode instanceof ModifiableResourceCollection) 129 { 130 ModifiableResourceCollection projectRootNodeRc = (ModifiableResourceCollection) projectRootNode; 131 return _getAmetysObject(projectRootNodeRc, __WORKSPACES_WALLCONTENT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 132 } 133 else 134 { 135 throw new IllegalClassException(ModifiableResourceCollection.class, projectRootNode.getClass()); 136 } 137 } 138 catch (AmetysRepositoryException e) 139 { 140 throw new AmetysRepositoryException("Error getting the wallcontent root node.", e); 141 } 142 } 143 144 @Override 145 protected String getModulePageName() 146 { 147 return "index"; 148 } 149 150 public I18nizableText getModuleTitle() 151 { 152 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_WALLCONTENT_LABEL"); 153 } 154 public I18nizableText getModuleDescription() 155 { 156 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_WALLCONTENT_DESCRIPTION"); 157 } 158 @Override 159 protected I18nizableText getModulePageTitle() 160 { 161 // The module page is the index page, which already exists 162 return null; 163 } 164 165 @Override 166 protected void initializeModulePage(ModifiablePage modulePage) 167 { 168 ModifiableZone defaultZone; 169 if (modulePage.hasZone("default")) 170 { 171 defaultZone = modulePage.getZone("default"); 172 } 173 else 174 { 175 defaultZone = modulePage.createZone("default"); 176 } 177 178 boolean hasService = defaultZone.getZoneItems().stream().anyMatch(zi -> WALLCONTENT_SERVICE_ID.equals(zi.getServiceId())); 179 180 if (!hasService) 181 { 182 ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem(); 183 defaultZoneItem.setType(ZoneType.SERVICE); 184 defaultZoneItem.setServiceId(WALLCONTENT_SERVICE_ID); 185 186 ModifiableModelAwareDataHolder serviceDataHolder = defaultZoneItem.getServiceParameters(); 187 serviceDataHolder.setValue("header", null); 188 serviceDataHolder.setValue("contentTypes", SEARCH_SERVICE_CONTENT_TYPES); 189 serviceDataHolder.setValue("returnables", SEARCH_SERVICE_RETURNABLES); 190 serviceDataHolder.setValue("initialSorts", SEARCH_SERVICE_SORTS); 191 serviceDataHolder.setValue("contexts", SEARCH_SERVICE_CONTEXTS); 192 serviceDataHolder.setValue("resultsPerPage", 15); 193 serviceDataHolder.setValue("rightCheckingMode", "none"); 194 serviceDataHolder.setValue("resultPlace", "ABOVE_CRITERIA"); 195 serviceDataHolder.setValue("launchSearchAtStartup", true); 196 serviceDataHolder.setValue("rss", false); 197 serviceDataHolder.setValue("contentView", "main"); 198 serviceDataHolder.setValue("xslt", SEARCH_SERVICE_XSLT); 199 } 200 } 201 202 /* 203 * Override because the page should already exist, the parent returns null in this case 204 */ 205 @Override 206 protected ModifiablePage _createModulePage(Project project, Sitemap sitemap, String name, I18nizableText pageTitle, String skinTemplate) 207 { 208 if (sitemap.hasChild(name)) 209 { 210 return sitemap.getChild(name); 211 } 212 else 213 { 214 return super._createModulePage(project, sitemap, name, pageTitle, skinTemplate); 215 } 216 } 217 218 @Override 219 protected void _deletePages(Project project) 220 { 221 // Nothing. Index page should not be deleted. 222 } 223 224 @Override 225 protected void _internalDeactivateModule(Project project) 226 { 227 // Remove wall service 228 _removeWallService(project); 229 } 230 231 @Override 232 protected void _internalDeleteData(Project project) 233 { 234 // Remove wall service 235 _removeWallService(project); 236 237 // Delete wall contents 238 _deleteWallContents(project); 239 } 240 241 private void _deleteWallContents(Project project) 242 { 243 Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, WorkspacesConstants.WALL_CONTENT_CONTENT_TYPE_ID); 244 245 List<Expression> siteExpr = new ArrayList<>(); 246 for (Site site : project.getSites()) 247 { 248 siteExpr.add(new StringExpression("site", Operator.EQ, site.getName())); 249 } 250 251 Expression expr = new AndExpression(cTypeExpr, new OrExpression(siteExpr.toArray(new Expression[siteExpr.size()]))); 252 253 String xPathQuery = ContentQueryHelper.getContentXPathQuery(expr); 254 255 List<String> contentIds = _resolver.query(xPathQuery) 256 .stream() 257 .map(AmetysObject::getId) 258 .collect(Collectors.toList()); 259 260 _contentDAO.deleteContents(contentIds, true); 261 } 262 263 private void _removeWallService(Project project) 264 { 265 List<Page> modulePages = _getModulePages(project); 266 for (Page page : modulePages) 267 { 268 if (page.hasZone("default")) 269 { 270 _projectManager.untagProjectPage((ModifiablePage) page, getModuleRoot(project, false)); 271 272 ModifiableZone defaultZone = ((ModifiablePage) page).getZone("default"); 273 274 Set<String> zoneItemIds = defaultZone.getZoneItems() 275 .stream() 276 .filter(zi -> WALLCONTENT_SERVICE_ID.equals(zi.getServiceId())) 277 .map(ZoneItem::getId) 278 .collect(Collectors.toSet()); 279 280 for (String zoneItemId : zoneItemIds) 281 { 282 _zoneDAO.removeZoneItem(zoneItemId); 283 } 284 } 285 } 286 } 287}