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