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.news; 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.search.content.ContentSearcherFactory; 031import org.ametys.plugins.repository.AmetysObjectIterable; 032import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 033import org.ametys.plugins.workspaces.AbstractWorkspaceModule; 034import org.ametys.plugins.workspaces.ObservationConstants; 035import org.ametys.plugins.workspaces.WorkspacesConstants; 036import org.ametys.plugins.workspaces.project.objects.Project; 037import org.ametys.plugins.workspaces.util.StatisticColumn; 038import org.ametys.plugins.workspaces.util.StatisticsColumnType; 039import org.ametys.runtime.i18n.I18nizableText; 040import org.ametys.web.repository.page.ModifiablePage; 041import org.ametys.web.repository.page.ModifiableZone; 042import org.ametys.web.repository.page.ModifiableZoneItem; 043import org.ametys.web.repository.page.Page.PageType; 044import org.ametys.web.repository.page.ZoneItem.ZoneType; 045import org.ametys.web.repository.sitemap.Sitemap; 046import org.ametys.web.search.query.SiteQuery; 047 048/** 049 * Workspaces module for news 050 */ 051public class NewsWorkspaceModule extends AbstractWorkspaceModule 052{ 053 /** The id of calendar module */ 054 public static final String NEWS_MODULE_ID = NewsWorkspaceModule.class.getName(); 055 056 /** The id of news service */ 057 public static final String NEWS_SERVICE_ID = "org.ametys.web.service.SearchService"; 058 059 /** Name of news's template */ 060 public static final String NEWS_PAGE_TEMPLATE = "news"; 061 062 /** Search service content types */ 063 protected static final String[] SEARCH_SERVICE_CONTENT_TYPES = new String[] {WorkspacesConstants.PROJECT_NEWS_CONTENT_TYPE_ID}; 064 065 /** Search service returnables */ 066 protected static final String[] SEARCH_SERVICE_RETURNABLES = new String[] {"org.ametys.web.frontoffice.search.metamodel.impl.ContentReturnable"}; 067 068 /** Search service sorts */ 069 protected static final String[] SEARCH_SERVICE_SORTS = new String[] {"{\"name\":\"ContentReturnable$ContentSearchable$indexingField$org.ametys.plugins.workspaces.Content.abstractNews$start-date\",\"sort\":\"DESC\"}", "{\"name\":\"ContentReturnable$ContentSearchable$indexingField$org.ametys.plugins.workspaces.Content.abstractNews$end-date\",\"sort\":\"DESC\"}"}; 070 071 /** Search service contexts */ 072 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\":[]}"}; 073 074 /** Search service content view */ 075 protected static final String SEARCH_SEARCH_CONTENT_VIEW = "portlet"; 076 077 /** Search service xslt */ 078 protected static final String SEARCH_SERVICE_XSLT = "pages/services/search/project-news.xsl"; 079 080 /** Workspaces calendars node name */ 081 private static final String __WORKSPACES_NEWS_NODE_NAME = "news"; 082 083 private static final String __NEWS_NUMBER_HEADER_ID = __WORKSPACES_NEWS_NODE_NAME + "$news_number"; 084 085 private ContentSearcherFactory _contentSearcherFactory; 086 087 088 @Override 089 public void service(ServiceManager smanager) throws ServiceException 090 { 091 super.service(smanager); 092 _contentSearcherFactory = (ContentSearcherFactory) smanager.lookup(ContentSearcherFactory.ROLE); 093 } 094 095 public String getId() 096 { 097 return NEWS_MODULE_ID; 098 } 099 100 public int getOrder() 101 { 102 return ORDER_NEWS; 103 } 104 105 public String getModuleName() 106 { 107 return __WORKSPACES_NEWS_NODE_NAME; 108 } 109 110 public Set<String> getAllowedEventTypes() 111 { 112 return Set.of(ObservationConstants.EVENT_PROJECT_NEWS_PUBLISHED, org.ametys.cms.ObservationConstants.EVENT_CONTENT_COMMENT_VALIDATED); 113 } 114 115 @Override 116 protected String getModulePageName() 117 { 118 return "news"; 119 } 120 121 public I18nizableText getModuleTitle() 122 { 123 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_MODULE_NEWS_LABEL"); 124 } 125 public I18nizableText getModuleDescription() 126 { 127 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_MODULE_NEWS_DESCRIPTION"); 128 } 129 130 @Override 131 protected I18nizableText getModulePageTitle() 132 { 133 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_NEWS_TITLE"); 134 } 135 136 @Override 137 protected ModifiablePage _createModulePage(Project project, Sitemap sitemap, String name, I18nizableText pageTitle, String skinTemplate) 138 { 139 if (!sitemap.hasChild(name)) 140 { 141 ModifiablePage page = sitemap.createChild(name, "ametys:defaultPage"); 142 143 // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language 144 // to prevent a non-user-friendly error and still generate the project workspace. 145 page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(pageTitle, sitemap.getName()), "Missing title")); 146 page.setType(PageType.CONTAINER); 147 page.setTemplate(NEWS_PAGE_TEMPLATE); 148 page.setSiteName(sitemap.getSiteName()); 149 page.setSitemapName(sitemap.getName()); 150 151 sitemap.saveChanges(); 152 153 return page; 154 } 155 else 156 { 157 return null; 158 } 159 } 160 161 @Override 162 protected void initializeModulePage(ModifiablePage modulePage) 163 { 164 modulePage.untag("SECTION"); 165 166 ModifiableZone defaultZone; 167 if (modulePage.hasZone("default")) 168 { 169 defaultZone = modulePage.getZone("default"); 170 } 171 else 172 { 173 defaultZone = modulePage.createZone("default"); 174 } 175 176 boolean hasService = defaultZone.getZoneItems().stream().anyMatch(zi -> NEWS_SERVICE_ID.equals(zi.getServiceId())); 177 178 if (!hasService) 179 { 180 ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem(); 181 defaultZoneItem.setType(ZoneType.SERVICE); 182 defaultZoneItem.setServiceId(NEWS_SERVICE_ID); 183 184 ModifiableModelAwareDataHolder serviceDataHolder = defaultZoneItem.getServiceParameters(); 185 serviceDataHolder.setValue("header", null); 186 serviceDataHolder.setValue("contentTypes", SEARCH_SERVICE_CONTENT_TYPES); 187 serviceDataHolder.setValue("returnables", SEARCH_SERVICE_RETURNABLES); 188 serviceDataHolder.setValue("initialSorts", SEARCH_SERVICE_SORTS); 189 serviceDataHolder.setValue("contexts", SEARCH_SERVICE_CONTEXTS); 190 serviceDataHolder.setValue("resultsPerPage", 12); 191 serviceDataHolder.setValue("rightCheckingMode", "none"); 192 serviceDataHolder.setValue("resultPlace", "ABOVE_CRITERIA"); 193 serviceDataHolder.setValue("launchSearchAtStartup", true); 194 serviceDataHolder.setValue("rss", false); 195 serviceDataHolder.setValue("contentView", SEARCH_SEARCH_CONTENT_VIEW); 196 serviceDataHolder.setValue("xslt", SEARCH_SERVICE_XSLT); 197 } 198 } 199 200 @Override 201 protected String getModulePageTemplate() 202 { 203 return "page"; 204 } 205 206 @Override 207 public Map<String, Object> _getInternalStatistics(Project project, boolean isActive) 208 { 209 if (isActive) 210 { 211 Map<String, Object> statistics = new HashMap<>(); 212 try 213 { 214 AmetysObjectIterable<Content> results = _contentSearcherFactory.create(WorkspacesConstants.PROJECT_NEWS_CONTENT_TYPE_ID) 215 .search(new SiteQuery(project.getName())); 216 statistics.put(__NEWS_NUMBER_HEADER_ID, results.getSize()); 217 } 218 catch (Exception e) 219 { 220 getLogger().error("Error searching news content images in project " + project.getId(), e); 221 } 222 return statistics; 223 } 224 else 225 { 226 return Map.of(__NEWS_NUMBER_HEADER_ID, __SIZE_INACTIVE); 227 } 228 } 229 230 @Override 231 public List<StatisticColumn> _getInternalStatisticModel() 232 { 233 return List.of(new StatisticColumn(__NEWS_NUMBER_HEADER_ID, new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_NEWS_NUMBER")) 234 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderElements") 235 .withType(StatisticsColumnType.LONG) 236 .withGroup(GROUP_HEADER_ELEMENTS_ID)); 237 } 238 239 @Override 240 protected long _getModuleSize(Project project) 241 { 242 try 243 { 244 AmetysObjectIterable<Content> results = _contentSearcherFactory.create(WorkspacesConstants.PROJECT_NEWS_CONTENT_TYPE_ID) 245 .search(new SiteQuery(project.getName())); 246 247 return results.stream() 248 .map(content -> content.getValue("illustration/image")) 249 .filter(Objects::nonNull) 250 .filter(Binary.class::isInstance) 251 .map(Binary.class::cast) 252 .mapToLong(Binary::getLength) 253 .sum(); 254 } 255 catch (Exception e) 256 { 257 getLogger().error("Error searching news images in project " + project.getId(), e); 258 return -1; 259 } 260 } 261 262 @Override 263 protected boolean _showModuleSize() 264 { 265 return true; 266 } 267 268 @Override 269 protected boolean _showActivatedStatus() 270 { 271 return false; 272 } 273}