001/* 002 * Copyright 2016 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.userdirectory.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.userdirectory.UserDirectoryPageHandler; 044import org.ametys.plugins.userdirectory.observation.ObservationConstants; 045import org.ametys.plugins.userdirectory.page.VirtualUserDirectoryPageFactory; 046import org.ametys.runtime.i18n.I18nizableTextParameter; 047import org.ametys.runtime.i18n.I18nizableText; 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 wich set/remove the root page of a user directory. 054 */ 055public class SetUserDirectoryRootClientSideElement extends AbstractPageClientSideElement 056{ 057 /** Observer manager. */ 058 protected ObservationManager _observationManager; 059 /** The extension point for content types */ 060 protected ContentTypeExtensionPoint _contentTypeEP; 061 /** The User Directory page handler */ 062 protected UserDirectoryPageHandler _userDirectoryPageHandler; 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 _userDirectoryPageHandler = (UserDirectoryPageHandler) smanager.lookup(UserDirectoryPageHandler.ROLE); 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 (_isUserDirectoryRootPage((JCRAmetysObject) page)) 090 { 091 List<String> i18nParameters = new ArrayList<>(); 092 i18nParameters.add(page.getTitle()); 093 094 I18nizableText ed = (I18nizableText) parameters.get("user-directory-page-description"); 095 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 096 result.put("user-directory-page-title", msg); 097 098 ed = (I18nizableText) parameters.get("remove-user-directory-page-description"); 099 msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 100 result.put("remove-user-directory-page-title", msg); 101 102 String contentTypeId = page.getValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY); 103 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-user-directory-page-description"); 112 msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), contentTypeI18nParameters); 113 result.put("contenttype-user-directory-page-description", msg); 114 } 115 116 result.put("user-directory-page-id", new I18nizableText(page.getId())); 117 118 I18nizableText defaultDirectoryDeep = (I18nizableText) parameters.get("default-depth"); 119 long depthData = page.getValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, -1L); 120 I18nizableText depth = depthData == -1 ? defaultDirectoryDeep : new I18nizableText(String.valueOf(depthData)); 121 result.put("depth", depth); 122 } 123 else 124 { 125 List<String> i18nParameters = new ArrayList<>(); 126 i18nParameters.add(page.getTitle()); 127 128 I18nizableText ed = (I18nizableText) parameters.get("add-user-directory-page-description"); 129 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 130 131 result.put("add-user-directory-page-id", new I18nizableText(page.getId())); 132 result.put("add-user-directory-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 private boolean _isUserDirectoryRootPage (JCRAmetysObject jcrPage) 151 { 152 try 153 { 154 Node node = jcrPage.getNode(); 155 156 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 157 { 158 List<Value> values = Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()); 159 160 return values.stream() 161 .map(LambdaUtils.wrap(Value::getString)) 162 .anyMatch(v -> VirtualUserDirectoryPageFactory.class.getName().equals(v)); 163 } 164 else 165 { 166 return false; 167 } 168 } 169 catch (RepositoryException e) 170 { 171 return false; 172 } 173 } 174 175 /** 176 * Gets the content types which can build a user directory 177 * @param pageId The id of the page being edited 178 * @return the content types which can build a user directory 179 */ 180 @Callable 181 public List<Map<String, Object>> getSupportedContentTypes(String pageId) 182 { 183 List<Map<String, Object>> result = new ArrayList<>(); 184 Page page = _resolver.resolveById(pageId); 185 186 for (String contentTypeId : _contentTypeEP.getSubTypes(UserDirectoryPageHandler.ABSTRACT_USER_CONTENT_TYPE)) 187 { 188 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 189 Page userDirectoryRootPage = _userDirectoryPageHandler.getUserDirectoryRootPage(page.getSiteName(), page.getSitemapName(), contentTypeId); 190 if (!contentType.isAbstract() && (userDirectoryRootPage == null || userDirectoryRootPage.equals(page))) 191 { 192 // The content type is not already a root of a user directory or is the root of the currently edited page 193 Map<String, Object> entry = new HashMap<>(); 194 entry.put("value", contentType.getId()); 195 entry.put("text", contentType.getLabel()); 196 result.add(entry); 197 } 198 } 199 200 return result; 201 } 202 203 /** 204 * Sets the given page as the root of a user directory 205 * @param pageId The id of the page 206 * @param contentType The id of the content type 207 * @param viewName The view name for users rendering 208 * @param attribute The classification attribute 209 * @param depth The depth of the tree structure 210 * @return A result map 211 * @throws RepositoryException if a repository error occurred 212 */ 213 @Callable 214 public Map<String, Object> setUserDirectoryRoot(String pageId, String contentType, String viewName, String attribute, int depth) throws RepositoryException 215 { 216 Map<String, Object> result = new HashMap<>(); 217 if (!_contentTypeEP.isDescendant(contentType, UserDirectoryPageHandler.ABSTRACT_USER_CONTENT_TYPE)) 218 { 219 result.put("error", "invalid-content-type"); 220 return result; 221 } 222 223 Page page = _resolver.resolveById(pageId); 224 String oldContentType = page.getValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY); 225 String oldAttribute = page.getValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, StringUtils.EMPTY); 226 String oldViewName = page.getValue(UserDirectoryPageHandler.USER_VIEW_NAME, StringUtils.EMPTY); 227 long oldDepth = page.getValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, -1L); 228 229 // Do nothing if page attribute are the same 230 if (!oldContentType.equals(contentType) || !oldAttribute.equals(attribute) || oldDepth != depth || !oldViewName.equals(viewName)) 231 { 232 Set<Page> currentUserDirectoryPages = _userDirectoryPageHandler.getUserDirectoryRootPages(page.getSiteName(), page.getSitemapName()); 233 234 Map<String, Object> eventParams = new HashMap<>(); 235 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 236 237 if (currentUserDirectoryPages.contains(page)) 238 { 239 eventParams.put(ObservationConstants.ARGS_USER_CONTENT_VIEW_UPDATED, !oldViewName.equals(viewName)); 240 241 // Unindex pages for all workspaces before the properties changed 242 _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_UPDATING, _currentUserProvider.getUser(), eventParams)); 243 244 _updateUserDirectoryRootProperty(page, contentType, viewName, attribute, depth); 245 } 246 else 247 { 248 _addUserDirectoryRootProperty(page, contentType, viewName, attribute, depth); 249 } 250 251 _userDirectoryPageHandler.clearCache(page); 252 253 // Live synchronization 254 _notifyPageUpdated(page); 255 256 // Indexation 257 _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams)); 258 } 259 260 return result; 261 } 262 263 /** 264 * Remove the user directory root status to the given page 265 * @param pageId The id of the page 266 * @return A result map 267 * @throws RepositoryException if a repository error occured 268 */ 269 @Callable 270 public Map<String, Object> removeUserDirectoryRoot(String pageId) throws RepositoryException 271 { 272 Map<String, Object> result = new HashMap<>(); 273 274 Page page = _resolver.resolveById(pageId); 275 276 if (page instanceof JCRAmetysObject) 277 { 278 if (!_isUserDirectoryRootPage((JCRAmetysObject) page)) 279 { 280 result.put("error", "no-root"); 281 return result; 282 } 283 284 Map<String, Object> eventParams = new HashMap<>(); 285 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 286 287 // Unindex pages for all workspaces before the properties were removed 288 _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_DELETING, _currentUserProvider.getUser(), eventParams)); 289 290 _userDirectoryPageHandler.clearCache(page); 291 _removeUserDirectoryRootProperty(page); 292 293 _notifyPageUpdated(page); 294 295 // After live synchronization 296 _observationManager.notify(new Event(ObservationConstants.EVENT_USER_DIRECTORY_ROOT_DELETED, _currentUserProvider.getUser(), eventParams)); 297 } 298 else 299 { 300 result.put("error", "no-root"); 301 } 302 return result; 303 } 304 305 /** 306 * Gets information about user directory root status on the given. 307 * @param pageId The id of the page 308 * @return information about user directory root status on the given. 309 */ 310 @Callable 311 public Map<String, Object> getRootPageInfo(String pageId) 312 { 313 Map<String, Object> result = new HashMap<>(); 314 315 Page page = _resolver.resolveById(pageId); 316 Set<Page> currentUserDirectoryPages = _userDirectoryPageHandler.getUserDirectoryRootPages(page.getSiteName(), page.getSitemapName()); 317 318 if (currentUserDirectoryPages.contains(page)) 319 { 320 result.put("isRoot", true); 321 result.put("contentType", page.getValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME)); 322 result.put("viewName", page.getValue(UserDirectoryPageHandler.USER_VIEW_NAME, StringUtils.EMPTY)); 323 result.put("metadata", page.getValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME)); 324 result.put("depth", page.getValue(UserDirectoryPageHandler.DEPTH_DATA_NAME)); 325 } 326 else 327 { 328 result.put("isRoot", false); 329 } 330 331 return result; 332 } 333 334 private void _addUserDirectoryRootProperty(Page page, String contentType, String viewName, String attribute, int depth) throws RepositoryException 335 { 336 if (page instanceof JCRAmetysObject) 337 { 338 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 339 Node node = jcrPage.getNode(); 340 341 List<Value> values = new ArrayList<>(); 342 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 343 { 344 values.addAll(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues())); 345 } 346 347 StringValue virtualUserDirectoryPageFactoryClassName = new StringValue(VirtualUserDirectoryPageFactory.class.getName()); 348 if (!values.contains(virtualUserDirectoryPageFactoryClassName)) 349 { 350 values.add(virtualUserDirectoryPageFactoryClassName); 351 } 352 353 node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()])); 354 355 // Set the user directory root property 356 if (page instanceof ModifiablePage) 357 { 358 ModifiablePage modifiablePage = (ModifiablePage) page; 359 modifiablePage.setValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, contentType); 360 modifiablePage.setValue(UserDirectoryPageHandler.USER_VIEW_NAME, viewName); 361 modifiablePage.setValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, attribute); 362 modifiablePage.setValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, depth); 363 } 364 365 jcrPage.saveChanges(); 366 } 367 } 368 369 private void _updateUserDirectoryRootProperty(Page page, String contentType, String viewName, String attribute, int depth) 370 { 371 if (page instanceof ModifiablePage) 372 { 373 ModifiablePage modifiablePage = (ModifiablePage) page; 374 375 // Set the user directory root property 376 modifiablePage.setValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME, contentType); 377 modifiablePage.setValue(UserDirectoryPageHandler.USER_VIEW_NAME, viewName); 378 modifiablePage.setValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME, attribute); 379 modifiablePage.setValue(UserDirectoryPageHandler.DEPTH_DATA_NAME, depth); 380 381 modifiablePage.saveChanges(); 382 } 383 } 384 385 private void _removeUserDirectoryRootProperty(Page page) throws RepositoryException 386 { 387 if (page instanceof JCRAmetysObject) 388 { 389 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 390 Node node = jcrPage.getNode(); 391 392 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 393 { 394 List<Value> values = new ArrayList<>(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues())); 395 int index = values.stream() 396 .map(LambdaUtils.wrap(Value::getString)) 397 .collect(Collectors.toList()) 398 .indexOf(VirtualUserDirectoryPageFactory.class.getName()); 399 if (index != -1) 400 { 401 values.remove(index); 402 node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()])); 403 } 404 405 // Remove the user directory root property 406 if (page instanceof ModifiablePage) 407 { 408 ModifiablePage modifiablePage = (ModifiablePage) page; 409 modifiablePage.removeValue(UserDirectoryPageHandler.CONTENT_TYPE_DATA_NAME); 410 modifiablePage.removeValue(UserDirectoryPageHandler.USER_VIEW_NAME); 411 modifiablePage.removeValue(UserDirectoryPageHandler.CLASSIFICATION_ATTRIBUTE_DATA_NAME); 412 modifiablePage.removeValue(UserDirectoryPageHandler.DEPTH_DATA_NAME); 413 } 414 415 jcrPage.saveChanges(); 416 } 417 } 418 } 419 420 private void _notifyPageUpdated(Page page) 421 { 422 Map<String, Object> eventParams = new HashMap<>(); 423 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 424 _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 425 } 426}