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