001/* 002 * Copyright 2018 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.ugc.clientsideelement; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.stream.Collectors; 025 026import javax.jcr.Node; 027import javax.jcr.RepositoryException; 028import javax.jcr.Value; 029 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.jackrabbit.value.StringValue; 034 035import org.ametys.cms.contenttype.ContentType; 036import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 037import org.ametys.core.observation.Event; 038import org.ametys.core.observation.ObservationManager; 039import org.ametys.core.ui.Callable; 040import org.ametys.core.util.LambdaUtils; 041import org.ametys.plugins.repository.AmetysObjectResolver; 042import org.ametys.plugins.repository.jcr.JCRAmetysObject; 043import org.ametys.plugins.ugc.observation.ObservationConstants; 044import org.ametys.plugins.ugc.page.UGCPageHandler; 045import org.ametys.plugins.ugc.page.VirtualUGCPageFactory; 046import org.ametys.runtime.i18n.I18nizableText; 047import org.ametys.runtime.i18n.I18nizableTextParameter; 048import org.ametys.web.clientsideelement.AbstractPageClientSideElement; 049import org.ametys.web.repository.page.ModifiablePage; 050import org.ametys.web.repository.page.Page; 051 052/** 053 * Client side element for a controller which set/remove the root page of a ugc. 054 */ 055public class SetUGCRootClientSideElement extends AbstractPageClientSideElement 056{ 057 /** Observer manager. */ 058 protected ObservationManager _observationManager; 059 /** The extension point for content types */ 060 protected ContentTypeExtensionPoint _contentTypeEP; 061 /** The UGC page handler */ 062 protected UGCPageHandler _ugcPageHandler; 063 064 @Override 065 public void service(ServiceManager smanager) throws ServiceException 066 { 067 super.service(smanager); 068 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 069 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 070 _ugcPageHandler = (UGCPageHandler) smanager.lookup(UGCPageHandler.ROLE); 071 072 } 073 074 /** 075 * Gets the status of the given page 076 * @param pageId The page id 077 * @return the status of the given page 078 */ 079 @Callable 080 public Map<String, Object> getStatus(String pageId) 081 { 082 Map<String, Object> result = new HashMap<>(); 083 084 Map<String, Object> parameters = this._script.getParameters(); 085 086 Page page = _resolver.resolveById(pageId); 087 088 if (page instanceof JCRAmetysObject) 089 { 090 if (_isUGCRootPage((JCRAmetysObject) page)) 091 { 092 List<String> i18nParameters = new ArrayList<>(); 093 i18nParameters.add(page.getTitle()); 094 095 I18nizableText ed = (I18nizableText) parameters.get("ugc-page-description"); 096 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 097 result.put("ugc-page-title", msg); 098 099 ed = (I18nizableText) parameters.get("remove-ugc-page-description"); 100 msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 101 result.put("remove-ugc-page-title", msg); 102 103 String contentTypeId = page.getValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY); 104 if (StringUtils.isNotEmpty(contentTypeId)) 105 { 106 I18nizableText contentTypeText = _contentTypeEP.hasExtension(contentTypeId) ? _contentTypeEP.getExtension(contentTypeId).getLabel() : new I18nizableText(contentTypeId); 107 108 Map<String, I18nizableTextParameter> contentTypeI18nParameters = new HashMap<>(); 109 contentTypeI18nParameters.put("0", contentTypeText); 110 111 ed = (I18nizableText) parameters.get("contenttype-ugc-page-description"); 112 msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), contentTypeI18nParameters); 113 result.put("contenttype-ugc-page-description", msg); 114 } 115 116 result.put("ugc-page-id", new I18nizableText(page.getId())); 117 } 118 else if (!_ugcPageHandler.getUGCRootPages(page.getSiteName(), page.getSitemapName()).isEmpty()) 119 { 120 I18nizableText ed = (I18nizableText) parameters.get("ugc-page-already-exist"); 121 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey()); 122 result.put("ugc-page-already-exist", msg); 123 } 124 else 125 { 126 List<String> i18nParameters = new ArrayList<>(); 127 i18nParameters.add(page.getTitle()); 128 129 I18nizableText ed = (I18nizableText) parameters.get("add-ugc-page-description"); 130 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 131 132 result.put("add-ugc-page-id", new I18nizableText(page.getId())); 133 result.put("add-ugc-page-title", msg); 134 } 135 } 136 else 137 { 138 List<String> noJcrI18nParameters = new ArrayList<>(); 139 noJcrI18nParameters.add(page.getTitle()); 140 141 I18nizableText ed = (I18nizableText) parameters.get("no-jcr-page-description"); 142 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), noJcrI18nParameters); 143 144 result.put("no-jcr-page-id", new I18nizableText(page.getId())); 145 result.put("no-jcr-page-title", msg); 146 } 147 148 return result; 149 } 150 151 /** 152 * Sets the given page as the root of a ugc 153 * @param pageId The id of the page 154 * @param contentTypeId The id of the content type 155 * @param attributePath The classification attribute path 156 * @param classificationPageVisible the visibility of transitional pages 157 * @return A result map 158 * @throws RepositoryException if a repository error occurred 159 */ 160 @Callable 161 public Map<String, Object> setUGCRoot(String pageId, String contentTypeId, String attributePath, boolean classificationPageVisible) throws RepositoryException 162 { 163 Map<String, Object> result = new HashMap<>(); 164 165 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 166 if (contentType == null) 167 { 168 result.put("error", "wrong-content-type"); 169 return result; 170 } 171 else if (StringUtils.isNotBlank(attributePath)) 172 { 173 if (!contentType.hasModelItem(attributePath)) 174 { 175 result.put("error", "wrong-metadata"); 176 return result; 177 } 178 } 179 180 Page page = _resolver.resolveById(pageId); 181 Page currentUGCPage = _ugcPageHandler.getUGCRootPage(page.getSiteName(), page.getSitemapName(), contentTypeId); 182 183 Map<String, Object> eventParams = new HashMap<>(); 184 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 185 186 if (currentUGCPage != null && currentUGCPage.getId().equals(pageId)) 187 { 188 // Unindex pages for all workspaces before the properties changed 189 _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_UPDATING, _currentUserProvider.getUser(), eventParams)); 190 191 _updateUGCRootProperty(page, contentTypeId, attributePath, classificationPageVisible); 192 } 193 else 194 { 195 _addUGCRootProperty(page, contentTypeId, attributePath, classificationPageVisible); 196 } 197 198 // Live synchronization 199 _notifyPageUpdated(page); 200 201 // Indexation 202 _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams)); 203 return result; 204 } 205 206 /** 207 * Remove the ugc root status to the given page 208 * @param pageId The id of the page 209 * @return A result map 210 * @throws RepositoryException if a repository error occured 211 */ 212 @Callable 213 public Map<String, Object> removeUGCRoot(String pageId) throws RepositoryException 214 { 215 Map<String, Object> result = new HashMap<>(); 216 217 Page page = _resolver.resolveById(pageId); 218 219 if (page instanceof JCRAmetysObject) 220 { 221 if (!_isUGCRootPage((JCRAmetysObject) page)) 222 { 223 result.put("error", "no-root"); 224 return result; 225 } 226 227 Map<String, Object> eventParams = new HashMap<>(); 228 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 229 230 // Unindex pages for all workspaces before the properties were removed 231 _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_DELETING, _currentUserProvider.getUser(), eventParams)); 232 233 _removeUGCRootProperty(page); 234 235 _notifyPageUpdated(page); 236 237 // After live synchronization 238 _observationManager.notify(new Event(ObservationConstants.EVENT_UGC_ROOT_DELETED, _currentUserProvider.getUser(), eventParams)); 239 } 240 else 241 { 242 result.put("error", "no-root"); 243 } 244 return result; 245 } 246 247 /** 248 * Gets information about ugc root status on the given. 249 * @param pageId The id of the page 250 * @return information about ugc root status on the given. 251 */ 252 @Callable 253 public Map<String, Object> getRootPageInfo(String pageId) 254 { 255 Map<String, Object> result = new HashMap<>(); 256 257 Page page = _resolver.resolveById(pageId); 258 Set<Page> currentUGCPages = _ugcPageHandler.getUGCRootPages(page.getSiteName(), page.getSitemapName()); 259 260 if (currentUGCPages.contains(page)) 261 { 262 result.put("isRoot", true); 263 result.put("contentType", page.getValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME)); 264 result.put("metadata", page.getValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME)); 265 result.put("is-visible", page.getValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME, false)); 266 } 267 else 268 { 269 result.put("isRoot", false); 270 } 271 272 return result; 273 } 274 275 private void _addUGCRootProperty(Page page, String contentType, String metadata, boolean classificationPageVisible) throws RepositoryException 276 { 277 if (page instanceof JCRAmetysObject) 278 { 279 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 280 Node node = jcrPage.getNode(); 281 282 List<Value> values = new ArrayList<>(); 283 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 284 { 285 values.addAll(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues())); 286 } 287 288 StringValue virtualUGCPageFactoryClassName = new StringValue(VirtualUGCPageFactory.class.getName()); 289 if (!values.contains(virtualUGCPageFactoryClassName)) 290 { 291 values.add(virtualUGCPageFactoryClassName); 292 } 293 294 node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()])); 295 296 // Set the ugc root property 297 if (page instanceof ModifiablePage) 298 { 299 ((ModifiablePage) page).setValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME, contentType); 300 ((ModifiablePage) page).setValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, metadata); 301 ((ModifiablePage) page).setValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME, classificationPageVisible); 302 } 303 304 jcrPage.saveChanges(); 305 } 306 } 307 308 private void _updateUGCRootProperty(Page page, String contentType, String metadata, boolean classificationPageVisible) 309 { 310 if (page instanceof ModifiablePage) 311 { 312 ModifiablePage modifiablePage = (ModifiablePage) page; 313 314 // Set the ugc root property 315 modifiablePage.setValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME, contentType); 316 modifiablePage.setValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, metadata); 317 modifiablePage.setValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME, classificationPageVisible); 318 319 modifiablePage.saveChanges(); 320 } 321 } 322 323 private void _removeUGCRootProperty(Page page) throws RepositoryException 324 { 325 if (page instanceof JCRAmetysObject) 326 { 327 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 328 Node node = jcrPage.getNode(); 329 330 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 331 { 332 List<Value> values = new ArrayList<>(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues())); 333 int index = values.stream() 334 .map(LambdaUtils.wrap(Value::getString)) 335 .collect(Collectors.toList()) 336 .indexOf(VirtualUGCPageFactory.class.getName()); 337 if (index != -1) 338 { 339 values.remove(index); 340 node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()])); 341 } 342 343 // Remove the ugc root property 344 if (page instanceof ModifiablePage) 345 { 346 ModifiablePage modifiablePage = (ModifiablePage) page; 347 modifiablePage.removeValue(UGCPageHandler.CONTENT_TYPE_DATA_NAME); 348 modifiablePage.removeValue(UGCPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME); 349 modifiablePage.removeValue(UGCPageHandler.CLASSIFICATION_PAGE_VISIBLE_DATA_NAME); 350 } 351 352 jcrPage.saveChanges(); 353 } 354 } 355 } 356 357 private boolean _isUGCRootPage (JCRAmetysObject jcrPage) 358 { 359 try 360 { 361 Node node = jcrPage.getNode(); 362 363 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 364 { 365 List<Value> values = Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()); 366 367 return values.stream() 368 .map(LambdaUtils.wrap(Value::getString)) 369 .anyMatch(v -> VirtualUGCPageFactory.class.getName().equals(v)); 370 } 371 else 372 { 373 return false; 374 } 375 } 376 catch (RepositoryException e) 377 { 378 return false; 379 } 380 } 381 382 private void _notifyPageUpdated(Page page) 383 { 384 Map<String, Object> eventParams = new HashMap<>(); 385 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 386 _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 387 } 388}