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