001/* 002 * Copyright 2017 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.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import javax.jcr.Node; 028import javax.jcr.RepositoryException; 029import javax.jcr.Value; 030 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.commons.lang3.StringUtils; 034import org.apache.jackrabbit.value.StringValue; 035 036import org.ametys.cms.contenttype.ContentType; 037import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 038import org.ametys.core.observation.Event; 039import org.ametys.core.observation.ObservationManager; 040import org.ametys.core.ui.Callable; 041import org.ametys.core.util.LambdaUtils; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.jcr.JCRAmetysObject; 044import org.ametys.plugins.userdirectory.OrganisationChartPageHandler; 045import org.ametys.plugins.userdirectory.UserDirectoryHelper; 046import org.ametys.plugins.userdirectory.observation.ObservationConstants; 047import org.ametys.plugins.userdirectory.page.VirtualOrganisationChartPageFactory; 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.runtime.i18n.I18nizableTextParameter; 050import org.ametys.web.clientsideelement.AbstractPageClientSideElement; 051import org.ametys.web.repository.page.LockablePage; 052import org.ametys.web.repository.page.ModifiablePage; 053import org.ametys.web.repository.page.Page; 054import org.ametys.web.rights.PageRightAssignmentContext; 055 056/** 057 * Client side element for a controller wich set/remove the organisation chart root page 058 */ 059public class SetOrganisationChartRootClientSideElement extends AbstractPageClientSideElement 060{ 061 /** Observer manager. */ 062 protected ObservationManager _observationManager; 063 064 /** The organisation chart page handler */ 065 protected OrganisationChartPageHandler _pageHandler; 066 067 /** The extension point for content types */ 068 protected ContentTypeExtensionPoint _contentTypeEP; 069 070 /** The organization chart page handler */ 071 protected OrganisationChartPageHandler _orgUnitPageHandler; 072 073 @Override 074 public void service(ServiceManager smanager) throws ServiceException 075 { 076 super.service(smanager); 077 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 078 _pageHandler = (OrganisationChartPageHandler) smanager.lookup(OrganisationChartPageHandler.ROLE); 079 _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 080 _orgUnitPageHandler = (OrganisationChartPageHandler) smanager.lookup(OrganisationChartPageHandler.ROLE); 081 } 082 /** 083 * Gets the status of the given page 084 * @param pageId The page id 085 * @return the status of the given page 086 */ 087 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 088 public Map<String, Object> getStatus(String pageId) 089 { 090 // Assume that no read access is checked (required to update client side element status) 091 092 Map<String, Object> result = new HashMap<>(); 093 094 Map<String, Object> parameters = this._script.getParameters(); 095 096 Page page = _resolver.resolveById(pageId); 097 098 if (page instanceof JCRAmetysObject) 099 { 100 if (_pageHandler.isOrganisationChartRootPage((JCRAmetysObject) page)) 101 { 102 List<String> i18nParameters = new ArrayList<>(); 103 i18nParameters.add(page.getTitle()); 104 105 I18nizableText ed = (I18nizableText) parameters.get("organisation-chart-page-description"); 106 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 107 result.put("organisation-chart-page-title", msg); 108 109 ed = (I18nizableText) parameters.get("remove-organisation-chart-page-description"); 110 msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 111 result.put("remove-organisation-chart-page-title", msg); 112 113 String contentTypeId = page.getValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY); 114 115 if (StringUtils.isNotEmpty(contentTypeId)) 116 { 117 I18nizableText contentTypeText = _contentTypeEP.hasExtension(contentTypeId) ? _contentTypeEP.getExtension(contentTypeId).getLabel() : new I18nizableText(contentTypeId); 118 119 Map<String, I18nizableTextParameter> contentTypeI18nParameters = new HashMap<>(); 120 contentTypeI18nParameters.put("0", contentTypeText); 121 122 ed = (I18nizableText) parameters.get("contenttype-organisation-chart-page-description"); 123 msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), contentTypeI18nParameters); 124 result.put("contenttype-organisation-chart-page-description", msg); 125 } 126 127 result.put("organisation-chart-page-id", new I18nizableText(page.getId())); 128 } 129 else 130 { 131 List<String> i18nParameters = new ArrayList<>(); 132 i18nParameters.add(page.getTitle()); 133 134 I18nizableText ed = (I18nizableText) parameters.get("add-organisation-chart-page-description"); 135 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters); 136 137 result.put("add-organisation-chart-page-id", new I18nizableText(page.getId())); 138 result.put("add-organisation-chart-page-title", msg); 139 } 140 } 141 else 142 { 143 List<String> noJcrI18nParameters = new ArrayList<>(); 144 noJcrI18nParameters.add(page.getTitle()); 145 146 I18nizableText ed = (I18nizableText) parameters.get("no-jcr-page-description"); 147 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), noJcrI18nParameters); 148 149 result.put("no-jcr-page-id", new I18nizableText(page.getId())); 150 result.put("no-jcr-page-title", msg); 151 } 152 153 return result; 154 } 155 156 /** 157 * Sets the given page as the root of a organization chart 158 * @param pageId The id of the page 159 * @param contentType The id of the content type 160 * @return A result map 161 * @throws RepositoryException if a repository error occurred 162 */ 163 @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 164 public Map<String, Object> setOrganisationChartRoot(String pageId, String contentType) throws RepositoryException 165 { 166 Map<String, Object> result = new HashMap<>(); 167 if (!_contentTypeEP.isSameOrDescendant(contentType, UserDirectoryHelper.ORGUNIT_CONTENT_TYPE)) 168 { 169 result.put("error", "invalid-content-type"); 170 return result; 171 } 172 173 Page page = _resolver.resolveById(pageId); 174 175 if (page instanceof LockablePage lockablePage && lockablePage.isLocked()) 176 { 177 throw new IllegalStateException("Cannot set the locked page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "' as the root of a organization chart"); 178 } 179 180 String oldContentType = page.getValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, StringUtils.EMPTY); 181 182 // Do nothing if page attribute are the same 183 if (!oldContentType.equals(contentType)) 184 { 185 Set<Page> currentOrgUnitPages = _orgUnitPageHandler.getOrganisationChartRootPages(page.getSiteName(), page.getSitemapName()); 186 187 Map<String, Object> eventParams = new HashMap<>(); 188 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 189 190 if (currentOrgUnitPages.contains(page)) 191 { 192 // Unindex pages for all workspaces before the properties changed 193 _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_UPDATING, _currentUserProvider.getUser(), eventParams)); 194 195 _updateOrgUnitRootProperty(page, contentType); 196 } 197 else 198 { 199 _addOrgUnitRootProperty(page, contentType); 200 } 201 202 203 // Live synchronization 204 _notifyPageUpdated(page); 205 206 // Indexation 207 _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_UPDATED, _currentUserProvider.getUser(), eventParams)); 208 } 209 210 return result; 211 } 212 private void _addOrgUnitRootProperty(Page page, String contentType) throws RepositoryException 213 { 214 if (page instanceof JCRAmetysObject) 215 { 216 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 217 Node node = jcrPage.getNode(); 218 219 List<Value> values = new ArrayList<>(); 220 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 221 { 222 values.addAll(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues())); 223 } 224 225 StringValue virtualOrgUnitPageFactoryClassName = new StringValue(VirtualOrganisationChartPageFactory.class.getName()); 226 if (!values.contains(virtualOrgUnitPageFactoryClassName)) 227 { 228 values.add(virtualOrgUnitPageFactoryClassName); 229 } 230 231 node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()])); 232 233 // Set the organisation chart root property 234 if (page instanceof ModifiablePage) 235 { 236 ModifiablePage modifiablePage = (ModifiablePage) page; 237 modifiablePage.setValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, contentType); 238 } 239 240 jcrPage.saveChanges(); 241 } 242 } 243 244 private void _updateOrgUnitRootProperty(Page page, String contentType) 245 { 246 if (page instanceof ModifiablePage) 247 { 248 ModifiablePage modifiablePage = (ModifiablePage) page; 249 250 // Set the organisation chart root property 251 modifiablePage.setValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME, contentType); 252 253 modifiablePage.saveChanges(); 254 } 255 } 256 /** 257 * Remove the organization chart root status to the given page 258 * @param pageId The id of the page 259 * @return A result map 260 * @throws RepositoryException if a repository error occurred 261 */ 262 @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 263 public Map<String, Object> removeOrganisationChartRoot(String pageId) throws RepositoryException 264 { 265 Map<String, Object> result = new HashMap<>(); 266 267 Page page = _resolver.resolveById(pageId); 268 269 if (page instanceof JCRAmetysObject) 270 { 271 if (!_pageHandler.isOrganisationChartRootPage((JCRAmetysObject) page)) 272 { 273 result.put("error", "no-root"); 274 return result; 275 } 276 277 if (page instanceof LockablePage lockablePage && lockablePage.isLocked()) 278 { 279 throw new IllegalStateException("Cannot unset root status from a locked page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "'"); 280 } 281 282 Map<String, Object> eventParams = new HashMap<>(); 283 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 284 285 // Unindex pages for all workspaces before the properties were removed 286 _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_DELETING, _currentUserProvider.getUser(), eventParams)); 287 288 _removeOrgUnitRootProperty(page); 289 290 _notifyPageUpdated(page); 291 292 // After live synchronization 293 _observationManager.notify(new Event(ObservationConstants.EVENT_ORGANISATION_CHART_ROOT_DELETED, _currentUserProvider.getUser(), eventParams)); 294 } 295 else 296 { 297 result.put("error", "no-root"); 298 } 299 return result; 300 } 301 302 303 /** 304 * Gets the content types which can build an organisation chart 305 * @param pageId The id of the page being edited 306 * @return the content types which can build an organisation chart 307 */ 308 @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 309 public List<Map<String, Object>> getSupportedContentTypes(String pageId) 310 { 311 List<Map<String, Object>> result = new ArrayList<>(); 312 Page page = _resolver.resolveById(pageId); 313 314 Set<String> orgUnitContentTypes = new HashSet<>(); 315 316 orgUnitContentTypes.add(UserDirectoryHelper.ORGUNIT_CONTENT_TYPE); 317 orgUnitContentTypes.addAll(_contentTypeEP.getSubTypes(UserDirectoryHelper.ORGUNIT_CONTENT_TYPE)); 318 319 for (String contentTypeId : orgUnitContentTypes) 320 { 321 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 322 Page orgUnitRootPage = _orgUnitPageHandler.getOrganisationChartRootPage(page.getSiteName(), page.getSitemapName(), contentTypeId); 323 if (!contentType.isAbstract() && (orgUnitRootPage == null || orgUnitRootPage.equals(page))) 324 { 325 // The content type is not already a root of an organisation chart or is the root of the currently edited page 326 Map<String, Object> entry = new HashMap<>(); 327 entry.put("value", contentType.getId()); 328 entry.put("text", contentType.getLabel()); 329 result.add(entry); 330 } 331 } 332 333 return result; 334 } 335 336 private void _removeOrgUnitRootProperty(Page page) throws RepositoryException 337 { 338 if (page instanceof JCRAmetysObject) 339 { 340 JCRAmetysObject jcrPage = (JCRAmetysObject) page; 341 Node node = jcrPage.getNode(); 342 343 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 344 { 345 List<Value> values = new ArrayList<>(Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues())); 346 int index = values.stream() 347 .map(LambdaUtils.wrap(Value::getString)) 348 .collect(Collectors.toList()) 349 .indexOf(VirtualOrganisationChartPageFactory.class.getName()); 350 351 if (index != -1) 352 { 353 values.remove(index); 354 node.setProperty(AmetysObjectResolver.VIRTUAL_PROPERTY, values.toArray(new Value[values.size()])); 355 } 356 357 // Remove the organization chart root property 358 if (page instanceof ModifiablePage) 359 { 360 ModifiablePage modifiablePage = (ModifiablePage) page; 361 modifiablePage.removeValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME); 362 } 363 jcrPage.saveChanges(); 364 } 365 } 366 } 367 368 private void _notifyPageUpdated(Page page) 369 { 370 Map<String, Object> eventParams = new HashMap<>(); 371 eventParams.put(org.ametys.web.ObservationConstants.ARGS_PAGE, page); 372 _observationManager.notify(new Event(org.ametys.web.ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 373 } 374 375 376 /** 377 * Gets information about organisation chart root status on the given. 378 * @param pageId The id of the page 379 * @return information about organisation chart root status on the given. 380 */ 381 @Callable (rights = "User_Directory_Right_Organisation_Chart_SetRoot", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 382 public Map<String, Object> getRootPageInfo(String pageId) 383 { 384 Map<String, Object> result = new HashMap<>(); 385 386 Page page = _resolver.resolveById(pageId); 387 388 if (page.hasValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME)) 389 { 390 result.put("isRoot", true); 391 result.put("contentType", page.getValue(OrganisationChartPageHandler.CONTENT_TYPE_DATA_NAME)); 392 } 393 else 394 { 395 result.put("isRoot", false); 396 } 397 398 return result; 399 } 400}