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