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