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