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