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