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