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