001/* 002 * Copyright 2015 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.linkdirectory.link; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024import java.util.stream.Collectors; 025 026import javax.jcr.RepositoryException; 027 028import org.apache.avalon.framework.component.Component; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.logger.AbstractLogEnabled; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.Constants; 036import org.apache.cocoon.components.ContextHelper; 037import org.apache.cocoon.environment.Context; 038import org.apache.commons.lang3.StringUtils; 039 040import org.ametys.cms.data.Binary; 041import org.ametys.core.observation.Event; 042import org.ametys.core.observation.ObservationManager; 043import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint; 044import org.ametys.core.right.RightManager; 045import org.ametys.core.ui.Callable; 046import org.ametys.core.upload.Upload; 047import org.ametys.core.upload.UploadManager; 048import org.ametys.core.user.CurrentUserProvider; 049import org.ametys.core.user.UserIdentity; 050import org.ametys.core.util.JSONUtils; 051import org.ametys.plugins.explorer.ObservationConstants; 052import org.ametys.plugins.explorer.resources.Resource; 053import org.ametys.plugins.linkdirectory.DirectoryEvents; 054import org.ametys.plugins.linkdirectory.DirectoryHelper; 055import org.ametys.plugins.linkdirectory.Link; 056import org.ametys.plugins.linkdirectory.Link.LinkStatus; 057import org.ametys.plugins.linkdirectory.Link.LinkType; 058import org.ametys.plugins.linkdirectory.LinkDirectoryColorsComponent; 059import org.ametys.plugins.linkdirectory.dynamic.DynamicInformationProvider; 060import org.ametys.plugins.linkdirectory.dynamic.DynamicInformationProviderExtensionPoint; 061import org.ametys.plugins.linkdirectory.repository.DefaultLink; 062import org.ametys.plugins.linkdirectory.repository.DefaultLinkFactory; 063import org.ametys.plugins.repository.AmetysObject; 064import org.ametys.plugins.repository.AmetysObjectIterable; 065import org.ametys.plugins.repository.AmetysObjectResolver; 066import org.ametys.plugins.repository.AmetysRepositoryException; 067import org.ametys.plugins.repository.ModifiableAmetysObject; 068import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 069import org.ametys.plugins.repository.TraversableAmetysObject; 070import org.ametys.plugins.repository.UnknownAmetysObjectException; 071import org.ametys.plugins.repository.jcr.NameHelper; 072import org.ametys.runtime.i18n.I18nizableText; 073import org.ametys.web.repository.page.Page; 074import org.ametys.web.repository.site.Site; 075import org.ametys.web.repository.site.SiteManager; 076 077/** 078 * DAO for manipulating {@link Link} 079 */ 080public class LinkDAO extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 081{ 082 /** Avalon Role */ 083 public static final String ROLE = LinkDAO.class.getName(); 084 085 private AmetysObjectResolver _resolver; 086 087 private ObservationManager _observationManager; 088 private SiteManager _siteManager; 089 private CurrentUserProvider _currentUserProvider; 090 private UploadManager _uploadManager; 091 private RightManager _rightManager; 092 private ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP; 093 094 private JSONUtils _jsonUtils; 095 private DirectoryHelper _directoryHelper; 096 097 private org.apache.avalon.framework.context.Context _context; 098 private Context _cocoonContext; 099 100 private DynamicInformationProviderExtensionPoint _dynamicInfoExtensionPoint; 101 102 private LinkDirectoryColorsComponent _colorComponent; 103 104 @Override 105 public void service(ServiceManager manager) throws ServiceException 106 { 107 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 108 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 109 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 110 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 111 _uploadManager = (UploadManager) manager.lookup(UploadManager.ROLE); 112 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 113 _directoryHelper = (DirectoryHelper) manager.lookup(DirectoryHelper.ROLE); 114 _dynamicInfoExtensionPoint = (DynamicInformationProviderExtensionPoint) manager.lookup(DynamicInformationProviderExtensionPoint.ROLE); 115 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 116 _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE); 117 _colorComponent = (LinkDirectoryColorsComponent) manager.lookup(LinkDirectoryColorsComponent.ROLE); 118 } 119 120 @Override 121 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 122 { 123 _context = context; 124 _cocoonContext = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 125 } 126 127 /** 128 * Create a new link 129 * @param parameters a map of the following parameters for the link : siteName, language, url, title, content, url-alternative, picture, picture#type, picture-alternative, themes, grant-any-user, fousers, fogroups 130 * @return The new link id 131 */ 132 @Callable 133 public Map<String, Object> createLink(Map<String, Object> parameters) 134 { 135 Map<String, Object> result = new HashMap<>(); 136 137 String siteName = (String) parameters.get("siteName"); 138 String language = (String) parameters.get("lang"); 139 Site site = _siteManager.getSite(siteName); 140 141 String url = StringUtils.defaultString((String) parameters.get("url")); 142 String internalUrl = StringUtils.defaultString((String) parameters.get("internal-url")); 143 144 // Check that the word doesn't already exist. 145 if (_urlExists(url, internalUrl, siteName, language)) 146 { 147 result.put("already-exists", true); 148 return result; 149 } 150 151 ModifiableTraversableAmetysObject rootNode = _directoryHelper.getLinksNode(site, language); 152 153 String name = url; 154 if (StringUtils.isBlank(name)) 155 { 156 name = internalUrl; 157 } 158 DefaultLink link = _createLink(name, rootNode); 159 _setValues(link, parameters); 160 161 rootNode.saveChanges(); 162 163 // Notify listeners 164 Map<String, Object> eventParams = new HashMap<>(); 165 eventParams.put(ObservationConstants.ARGS_ID, link.getId()); 166 eventParams.put(ObservationConstants.ARGS_PARENT_ID, rootNode.getId()); 167 eventParams.put(ObservationConstants.ARGS_NAME, link.getName()); 168 eventParams.put(ObservationConstants.ARGS_PATH, link.getPath()); 169 170 _observationManager.notify(new Event(DirectoryEvents.LINK_CREATED, _currentUserProvider.getUser(), eventParams)); 171 172 // Set public access 173 _setAccess(link, null); 174 175 return convertLink2JsonObject(link); 176 } 177 178 /** 179 * Create a new user link 180 * @param parameters a map of the following parameters for the link : siteName, language, url, title, content, url-alternative, picture, picture#type, picture-alternative 181 * @return The new link id 182 */ 183 public Map<String, Object> createUserLink(Map<String, Object> parameters) 184 { 185 Map<String, Object> result = new HashMap<>(); 186 187 UserIdentity currentUser = _currentUserProvider.getUser(); 188 if (currentUser == null) 189 { 190 result.put("unauthenticated-user", true); 191 return result; 192 } 193 194 String siteName = (String) parameters.get("siteName"); 195 String language = (String) parameters.get("lang"); 196 Site site = _siteManager.getSite(siteName); 197 198 String url = StringUtils.defaultString((String) parameters.get("url")); 199 String internalUrl = StringUtils.defaultString((String) parameters.get("internal-url")); 200 201 // Check that the word doesn't already exist for the given user. 202 if (_urlExistsForUser(url, internalUrl, siteName, language, currentUser)) 203 { 204 result.put("already-exists", true); 205 return result; 206 } 207 208 ModifiableTraversableAmetysObject rootNodeForUser = _directoryHelper.getLinksForUserNode(site, language, currentUser); 209 210 String name = url; 211 if (StringUtils.isBlank(name)) 212 { 213 name = internalUrl; 214 } 215 DefaultLink link = _createLink(name, rootNodeForUser); 216 _setValues(link, parameters); 217 218 rootNodeForUser.saveChanges(); 219 220 // Notify listeners 221 Map<String, Object> eventParams = new HashMap<>(); 222 eventParams.put(ObservationConstants.ARGS_ID, link.getId()); 223 eventParams.put(ObservationConstants.ARGS_PARENT_ID, rootNodeForUser.getId()); 224 eventParams.put(ObservationConstants.ARGS_NAME, link.getName()); 225 eventParams.put(ObservationConstants.ARGS_PATH, link.getPath()); 226 227 _observationManager.notify(new Event(DirectoryEvents.LINK_CREATED, _currentUserProvider.getUser(), eventParams)); 228 229 // Set public access 230 _setAccess(link, currentUser); 231 232 return convertLink2JsonObject(link); 233 } 234 235 /** 236 * Updates a link 237 * @param parameters a map of the following parameters for the link : siteName, language, id, url, title, content, url-alternative, picture, picture#type, picture-alternative, themes, grant-any-user, fousers, fogroups 238 * @return the update link 239 */ 240 @Callable 241 public Map<String, Object> updateLink(Map<String, Object> parameters) 242 { 243 Map<String, Object> result = new HashMap<>(); 244 245 String siteName = (String) parameters.get("siteName"); 246 String language = (String) parameters.get("language"); 247 String id = StringUtils.defaultString((String) parameters.get("id")); 248 String url = StringUtils.defaultString((String) parameters.get("url")); 249 String internalUrl = StringUtils.defaultString((String) parameters.get("internal-url")); 250 251 try 252 { 253 DefaultLink link = _resolver.resolveById(id); 254 255 // If the url was changed, check that the new url doesn't already exist. 256 if (!link.getUrl().equals(url) && _urlExists(url, internalUrl, siteName, language)) 257 { 258 result.put("already-exists", true); 259 return result; 260 } 261 262 _setValues(link, parameters); 263 link.saveChanges(); 264 265 // Notify listeners 266 Map<String, Object> eventParams = new HashMap<>(); 267 eventParams.put(ObservationConstants.ARGS_ID, link.getId()); 268 eventParams.put(ObservationConstants.ARGS_NAME, link.getName()); 269 eventParams.put(ObservationConstants.ARGS_PATH, link.getPath()); 270 271 _observationManager.notify(new Event(DirectoryEvents.LINK_MODIFIED, _currentUserProvider.getUser(), eventParams)); 272 273 return convertLink2JsonObject(link); 274 } 275 catch (UnknownAmetysObjectException e) 276 { 277 result.put("unknown-link", true); 278 return result; 279 } 280 catch (AmetysRepositoryException e) 281 { 282 throw new IllegalStateException(e); 283 } 284 } 285 286 @SuppressWarnings("unchecked") 287 private void _setValues(Link link, Map<String, Object> values) 288 { 289 // Set values (url, type, title, etc.) 290 _setLinkValues(link, values); 291 292 // Set themes 293 List<String> themes = Collections.EMPTY_LIST; 294 Object themesFromValues = values.get("themes"); 295 if (themesFromValues instanceof List) 296 { 297 themes = (List<String>) themesFromValues; 298 } 299 else if (themesFromValues instanceof String) 300 { 301 themes = _jsonUtils.convertJsonToList((String) themesFromValues).stream() 302 .map(Object::toString) 303 .collect(Collectors.toList()); 304 } 305 306 _setThemes(link, themes); 307 } 308 309 private void _setLinkValues(Link link, Map<String, Object> values) 310 { 311 String url = StringUtils.defaultString((String) values.get("url")); 312 String dynInfoProviderId = StringUtils.defaultString((String) values.get("dynamic-info-provider")); 313 String internalUrl = StringUtils.defaultString((String) values.get("internal-url")); 314 String urlType = StringUtils.defaultString((String) values.get("url-type")); 315 String title = StringUtils.defaultString((String) values.get("title")); 316 String content = StringUtils.defaultString((String) values.get("content")); 317 String alternative = StringUtils.defaultString((String) values.get("url-alternative")); 318 String color = StringUtils.defaultString((String) values.get("color")); 319 String pageId = StringUtils.defaultString((String) values.get("page")); 320 String status = StringUtils.defaultString((String) values.get("status")); 321 322 String pictureAsStr = StringUtils.defaultString((String) values.get("picture")); 323 String pictureAlternative = StringUtils.defaultString((String) values.get("picture-alternative")); 324 325 // Check the dynamic provider still exists 326 if (!_dynamicInfoExtensionPoint.hasExtension(dynInfoProviderId)) 327 { 328 dynInfoProviderId = ""; 329 } 330 331 link.setUrl(LinkType.valueOf(urlType), url); 332 link.setDynamicInformationProvider(dynInfoProviderId); 333 link.setInternalUrl(internalUrl); 334 link.setTitle(title); 335 link.setContent(content); 336 link.setAlternative(alternative); 337 link.setPictureAlternative(pictureAlternative); 338 link.setColor(color); 339 link.setPage(pageId); 340 link.setStatus(StringUtils.isNotBlank(status) ? LinkStatus.valueOf(status) : LinkStatus.NORMAL); 341 342 _setPicture(link, pictureAsStr); 343 } 344 345 private void _setThemes(Link link, List<String> themes) 346 { 347 link.setThemes(themes.toArray(new String[themes.size()])); 348 } 349 350 private void _setPicture(Link link, String valueAsStr) 351 { 352 if (StringUtils.isNotEmpty(valueAsStr)) 353 { 354 Map<String, Object> picture = _jsonUtils.convertJsonToMap(valueAsStr); 355 356 if (!picture.isEmpty()) 357 { 358 String pictureType = (String) picture.get("type"); 359 String value = (String) picture.get("id"); 360 361 if (pictureType.equals("explorer") && !"untouched".equals(value)) 362 { 363 link.setResourcePicture(value); 364 } 365 else if (pictureType.equals("glyph")) 366 { 367 link.setPictureGlyph(value); 368 } 369 else if (!"untouched".equals(value)) 370 { 371 UserIdentity user = _currentUserProvider.getUser(); 372 Upload upload = _uploadManager.getUpload(user, value); 373 374 String filename = upload.getFilename(); 375 String mimeType = upload.getMimeType() != null ? upload.getMimeType() : _cocoonContext.getMimeType(filename); 376 String finalMimeType = mimeType != null ? mimeType : "application/unknown"; 377 378 link.setExternalPicture(finalMimeType, filename, upload.getInputStream()); 379 } 380 } 381 else 382 { 383 // Remove picture 384 link.setNoPicture(); 385 } 386 387 } 388 else 389 { 390 // Remove picture 391 link.setNoPicture(); 392 } 393 } 394 395 /** 396 * Delete one or multiples links 397 * @param ids a list of links' ids 398 * @return true if all the links were deleted, false if at least one link could not be delete. 399 */ 400 @Callable 401 public List<String> deleteLinks(List<String> ids) 402 { 403 List<String> result = new ArrayList<>(); 404 405 for (String id : ids) 406 { 407 try 408 { 409 DefaultLink link = _resolver.resolveById(id); 410 411 String siteName = link.getSiteName(); 412 String language = link.getLanguage(); 413 String linkId = link.getId(); 414 String name = link.getName(); 415 String path = link.getPath(); 416 417 ModifiableAmetysObject parent = link.getParent(); 418 link.remove(); 419 420 parent.saveChanges(); 421 422 // Notify listeners 423 Map<String, Object> eventParams = new HashMap<>(); 424 eventParams.put(ObservationConstants.ARGS_ID, linkId); 425 eventParams.put(ObservationConstants.ARGS_NAME, name); 426 eventParams.put(ObservationConstants.ARGS_PATH, path); 427 eventParams.put("siteName", siteName); 428 eventParams.put("language", language); 429 _observationManager.notify(new Event(DirectoryEvents.LINK_DELETED, _currentUserProvider.getUser(), eventParams)); 430 431 result.add(linkId); 432 } 433 catch (UnknownAmetysObjectException e) 434 { 435 // ignore the id and continue the deletion. 436 getLogger().error("Unable to delete the link of id '" + id + ", because it does not exist.", e); 437 } 438 } 439 440 return result; 441 } 442 443 /** 444 * Move a link in the list 445 * @param id the link id 446 * @param role the move action 447 * @throws RepositoryException if a repository error occurs. 448 * @return the moved link in JSON 449 */ 450 @Callable 451 public Map<String, Object> moveLink(String id, String role) throws RepositoryException 452 { 453 DefaultLink link = _resolver.resolveById(id); 454 455 switch (role) 456 { 457 case "move-first": 458 _moveFirst(link); 459 break; 460 case "move-up": 461 _moveUp(link); 462 break; 463 case "move-down": 464 _moveDown(link); 465 break; 466 case "move-last": 467 _moveLast(link); 468 break; 469 default: 470 break; 471 } 472 473 return convertLink2JsonObject(link); 474 } 475 476 477 478 /** 479 * Get the JSON object representing a link 480 * @param id the id of link 481 * @return the link as JSON object 482 */ 483 @Callable 484 public Map<String, Object> getLink (String id) 485 { 486 DefaultLink link = _resolver.resolveById(id); 487 return convertLink2JsonObject(link); 488 } 489 490 /** 491 * Determines if the restriction IP parameter is not empty 492 * @param siteName the site name 493 * @return true if the restriction IP parameter is not empty 494 */ 495 @Callable 496 public boolean isInternalURLAllowed (String siteName) 497 { 498 Site site = _siteManager.getSite(siteName); 499 String allowedIpParameter = site.getValue("allowed-ip"); 500 return StringUtils.isNotBlank(allowedIpParameter); 501 } 502 503 /** 504 * Returns the list of providers of dynamic information as json object 505 * @return the providers of dynamic information 506 */ 507 @Callable 508 public List<Map<String, Object>> getDynamicInformationProviders() 509 { 510 List<Map<String, Object>> result = new ArrayList<>(); 511 512 for (String id : _dynamicInfoExtensionPoint.getExtensionsIds()) 513 { 514 DynamicInformationProvider provider = _dynamicInfoExtensionPoint.getExtension(id); 515 Map<String, Object> info = new HashMap<>(); 516 info.put("id", provider.getId()); 517 info.put("label", provider.getLabel()); 518 result.add(info); 519 } 520 521 return result; 522 } 523 524 /** 525 * Convert a link to JSON object 526 * @param link the link 527 * @return the link as JSON object 528 */ 529 public Map<String, Object> convertLink2JsonObject (DefaultLink link) 530 { 531 Map<String, Object> infos = new HashMap<>(); 532 533 infos.put("id", link.getId()); 534 infos.put("lang", link.getLanguage()); 535 infos.put("url", link.getUrl()); 536 infos.put("dynamicInfoProvider", link.getDynamicInformationProvider()); 537 infos.put("internalUrl", link.getInternalUrl()); 538 infos.put("urlType", link.getUrlType().toString()); 539 540 if (link.getUrlType() == LinkType.PAGE) 541 { 542 String pageId = link.getUrl(); 543 try 544 { 545 Page page = _resolver.resolveById(pageId); 546 infos.put("pageTitle", page.getTitle()); 547 } 548 catch (UnknownAmetysObjectException e) 549 { 550 infos.put("unknownPage", true); 551 } 552 } 553 554 infos.put("title", link.getTitle()); 555 infos.put("alternative", link.getAlternative()); 556 557 infos.put("content", StringUtils.defaultString(link.getContent())); 558 infos.put("pictureAlternative", StringUtils.defaultString(link.getPictureAlternative())); 559 560 Map<String, Object> pictureInfos = new HashMap<>(); 561 String pictureType = link.getPictureType(); 562 563 TraversableAmetysObject parent = link.getParent(); 564 infos.put("position", parent.getChildPosition(link)); 565 infos.put("count", parent.getChildren().getSize()); 566 567 infos.put("color", StringUtils.defaultString(link.getColor())); 568 infos.put("page", StringUtils.defaultString(link.getPage())); 569 if (link.getStatus() != null) 570 { 571 infos.put("status", link.getStatus().name()); 572 } 573 574 if (pictureType.equals("resource")) 575 { 576 String resourceId = link.getResourcePictureId(); 577 pictureInfos.put("id", resourceId); 578 try 579 { 580 Resource resource = _resolver.resolveById(resourceId); 581 582 pictureInfos.put("filename", resource.getName()); 583 pictureInfos.put("size", resource.getLength()); 584 pictureInfos.put("type", "explorer"); 585 pictureInfos.put("lastModified", resource.getLastModified()); 586 587 String contextPath = ContextHelper.getRequest(_context).getContextPath(); 588 String viewUrl = contextPath + "/plugins/explorer/resource?id=" + resource.getId(); 589 String downloadUrl = viewUrl + "&download=true"; 590 pictureInfos.put("viewUrl", viewUrl); 591 pictureInfos.put("downloadUrl", downloadUrl); 592 } 593 catch (UnknownAmetysObjectException e) 594 { 595 getLogger().error("The resource of id'" + resourceId + "' does not exist anymore. The picture for link of id '" + link.getId() + "' will be ignored.", e); 596 infos.put("pictureNotFound", true); 597 } 598 } 599 else if (pictureType.equals("external")) 600 { 601 Binary picMeta = link.getExternalPicture(); 602 603 pictureInfos.put("path", DefaultLink.PROPERTY_PICTURE); 604 pictureInfos.put("filename", picMeta.getFilename()); 605 pictureInfos.put("size", picMeta.getLength()); 606 pictureInfos.put("lastModified", picMeta.getLastModificationDate()); 607 pictureInfos.put("type", "metadata"); 608 609 String contextPath = ContextHelper.getRequest(_context).getContextPath(); 610 String viewUrl = contextPath + "/plugins/cms/binary/" + DefaultLink.PROPERTY_PICTURE + "?objectId=" + link.getId(); 611 String downloadUrl = viewUrl + "&download=true"; 612 pictureInfos.put("viewUrl", viewUrl); 613 pictureInfos.put("downloadUrl", downloadUrl); 614 615 } 616 else if (pictureType.equals("glyph")) 617 { 618 pictureInfos.put("id", link.getPictureGlyph()); 619 pictureInfos.put("type", "glyph"); 620 } 621 infos.put("picture", pictureInfos); 622 623 infos.put("isRestricted", !_rightManager.hasAnonymousReadAccess(link)); 624 625 // Themes 626 List<Map<String, Object>> themesList = new ArrayList<>(); 627 for (String themeId : link.getThemes()) 628 { 629 try 630 { 631 I18nizableText themeTitle = _directoryHelper.getThemeTitle(themeId, link.getSiteName(), link.getLanguage()); 632 Map<String, Object> themeData = new HashMap<>(); 633 themeData.put("id", themeId); 634 themeData.put("label", themeTitle); 635 themesList.add(themeData); 636 } 637 catch (UnknownAmetysObjectException e) 638 { 639 // Theme does not exist anymore 640 } 641 } 642 643 infos.put("themes", themesList); 644 645 return infos; 646 } 647 648 /** 649 * Get links infos 650 * @param linkIds the link id 651 * @return the link infos 652 */ 653 @Callable 654 public List<Map<String, Object>> getLinks(List<String> linkIds) 655 { 656 List<Map<String, Object>> result = new ArrayList<>(); 657 658 for (String linkId: linkIds) 659 { 660 try 661 { 662 result.add(getLink(linkId)); 663 } 664 catch (UnknownAmetysObjectException e) 665 { 666 // does not exists 667 } 668 } 669 return result; 670 } 671 672 /** 673 * Test if a link with the specified url or internal url exists in the directory. 674 * @param url the url to test. 675 * @param internalUrl the internal url to test 676 * @param siteName the site name. 677 * @param language the language. 678 * @return true if the link exists. 679 * @throws AmetysRepositoryException if a repository error occurs. 680 */ 681 protected boolean _urlExists(String url, String internalUrl, String siteName, String language) throws AmetysRepositoryException 682 { 683 boolean externalLinkExists = false; 684 if (StringUtils.isNotBlank(url)) 685 { 686 String externalLinkXpathQuery = _directoryHelper.getUrlExistsQuery(siteName, language, url); 687 try (AmetysObjectIterable<DefaultLink> externalLinks = _resolver.query(externalLinkXpathQuery);) 688 { 689 externalLinkExists = externalLinks.iterator().hasNext(); 690 } 691 } 692 693 boolean internalLinkExists = false; 694 if (StringUtils.isNotBlank(internalUrl)) 695 { 696 String internalLinkXpathQuery = _directoryHelper.getUrlExistsQuery(siteName, language, internalUrl); 697 try (AmetysObjectIterable<DefaultLink> internalLinks = _resolver.query(internalLinkXpathQuery);) 698 { 699 internalLinkExists = internalLinks.iterator().hasNext(); 700 } 701 } 702 703 return externalLinkExists || internalLinkExists; 704 } 705 706 /** 707 * Test if a link with the specified url or internal url exists in the directory for the given user. 708 * @param url the url to test. 709 * @param internalUrl the internal url to test 710 * @param siteName the site name. 711 * @param language the language. 712 * @param user The user identity 713 * @return true if the link exists for the given user. 714 * @throws AmetysRepositoryException if a repository error occurs. 715 */ 716 protected boolean _urlExistsForUser(String url, String internalUrl, String siteName, String language, UserIdentity user) throws AmetysRepositoryException 717 { 718 boolean externalLinkExists = false; 719 if (StringUtils.isNotBlank(url)) 720 { 721 String externalLinkXpathQuery = _directoryHelper.getUrlExistsForUserQuery(siteName, language, url, user); 722 try (AmetysObjectIterable<DefaultLink> externalLinks = _resolver.query(externalLinkXpathQuery);) 723 { 724 externalLinkExists = externalLinks.iterator().hasNext(); 725 } 726 } 727 728 boolean internalLinkExists = false; 729 if (StringUtils.isNotBlank(internalUrl)) 730 { 731 String internalLinkXpathQuery = _directoryHelper.getUrlExistsForUserQuery(siteName, language, internalUrl, user); 732 try (AmetysObjectIterable<DefaultLink> internalLinks = _resolver.query(internalLinkXpathQuery);) 733 { 734 internalLinkExists = internalLinks.iterator().hasNext(); 735 } 736 } 737 738 return externalLinkExists || internalLinkExists; 739 } 740 741 /** 742 * Create the link object. 743 * @param name the desired link name. 744 * @param rootNode the links root node. 745 * @return the created Link. 746 */ 747 protected DefaultLink _createLink(String name, ModifiableTraversableAmetysObject rootNode) 748 { 749 String originalName = NameHelper.filterName(name); 750 751 // Find unique name 752 String uniqueName = originalName; 753 int index = 2; 754 while (rootNode.hasChild(uniqueName)) 755 { 756 uniqueName = originalName + "-" + (index++); 757 } 758 759 return rootNode.createChild(uniqueName, DefaultLinkFactory.LINK_NODE_TYPE); 760 } 761 762 /** 763 * Move link the first position 764 * @param link the link to move 765 * @throws RepositoryException if an error occurs while exploring the repository 766 */ 767 private void _moveFirst(DefaultLink link) throws RepositoryException 768 { 769 try (AmetysObjectIterable<AmetysObject> children = ((TraversableAmetysObject) link.getParent()).getChildren();) 770 { 771 // Resolve the link in the same session or the linkRoot.saveChanges() call below won't see the order changes. 772 link.orderBefore(((TraversableAmetysObject) link.getParent()).getChildren().iterator().next()); 773 ((ModifiableAmetysObject) link.getParent()).saveChanges(); 774 } 775 } 776 777 /** 778 * Move link after its following 779 * @param link the link to move down 780 * @throws RepositoryException if an error occurs while exploring the repository 781 */ 782 private void _moveDown(DefaultLink link) throws RepositoryException 783 { 784 TraversableAmetysObject parentNode = link.getParent(); 785 AmetysObjectIterable<AmetysObject> siblings = parentNode.getChildren(); 786 Iterator<AmetysObject> it = siblings.iterator(); 787 788 boolean iterate = true; 789 while (it.hasNext() && iterate) 790 { 791 DefaultLink sibling = (DefaultLink) it.next(); 792 iterate = !sibling.getName().equals(link.getName()); 793 } 794 795 if (it.hasNext()) 796 { 797 // Move the link after his next sibling: move the next sibling before the link to move. 798 DefaultLink nextLink = (DefaultLink) it.next(); 799 nextLink.orderBefore(link); 800 801 link.saveChanges(); 802 } 803 } 804 805 /** 806 * Move link before its preceding 807 * @param link the link to move up 808 * @throws RepositoryException if an error occurs while exploring the repository 809 */ 810 private void _moveUp(DefaultLink link) throws RepositoryException 811 { 812 TraversableAmetysObject parentNode = link.getParent(); 813 AmetysObjectIterable<AmetysObject> siblings = parentNode.getChildren(); 814 Iterator<AmetysObject> it = siblings.iterator(); 815 DefaultLink previousLink = null; 816 817 while (it.hasNext()) 818 { 819 DefaultLink sibling = (DefaultLink) it.next(); 820 if (sibling.getName().equals(link.getName())) 821 { 822 break; 823 } 824 825 previousLink = sibling; 826 } 827 828 if (previousLink != null) 829 { 830 // Move the link after his next sibling: move the next sibling before the link to move. 831 link.orderBefore(previousLink); 832 link.saveChanges(); 833 } 834 } 835 836 /** 837 * Move link to the last position. 838 * @param link the link to move up 839 * @throws RepositoryException if an error occurs while exploring the repository 840 */ 841 private void _moveLast(DefaultLink link) throws RepositoryException 842 { 843 link.moveTo(link.getParent(), false); 844 ((ModifiableAmetysObject) link.getParent()).saveChanges(); 845 } 846 847 /** 848 * Set access to the link 849 * @param link the link 850 * @param user the user to set access. Can be null, in this case, we set anonymous right 851 */ 852 private void _setAccess (Link link, UserIdentity user) 853 { 854 if (user != null) 855 { 856 _profileAssignmentStorageEP.allowProfileToUser(user, RightManager.READER_PROFILE_ID, link); 857 } 858 else 859 { 860 _profileAssignmentStorageEP.allowProfileToAnonymous(RightManager.READER_PROFILE_ID, link); 861 } 862 863 Map<String, Object> eventParams = new HashMap<>(); 864 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT, link); 865 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_PROFILES, Collections.singleton(RightManager.READER_PROFILE_ID)); 866 867 _observationManager.notify(new Event(org.ametys.core.ObservationConstants.EVENT_ACL_UPDATED, _currentUserProvider.getUser(), eventParams)); 868 } 869 870 /** 871 * Retrieves the color of the link. 872 * If there is no color configured on the link, the default site color 873 * @param link the link 874 * @return the color of the link 875 */ 876 public String getLinkColor(DefaultLink link) 877 { 878 Map<String, Map<String, String>> colors = _colorComponent.getColors(link.getSiteName()); 879 if (colors.containsKey(link.getColor())) 880 { 881 return colors.get(link.getColor()).get("main"); 882 } 883 else 884 { 885 return colors.get(_colorComponent.getDefaultKey(link.getSiteName())).get("main"); 886 } 887 } 888}