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 * Indicate if a link is a user link 237 * @param link the link 238 * @return true if the link is a user link 239 */ 240 public boolean isUserLink(DefaultLink link) 241 { 242 AmetysObject parent = link.getParent(); 243 AmetysObject linksNode = _directoryHelper.getLinksNode(link.getSite(), link.getLanguage()); 244 return !parent.equals(linksNode); 245 } 246 247 248 /** 249 * Updates a link 250 * @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 251 * @return the update link 252 */ 253 @Callable 254 public Map<String, Object> updateLink(Map<String, Object> parameters) 255 { 256 Map<String, Object> result = new HashMap<>(); 257 258 String siteName = (String) parameters.get("siteName"); 259 String language = (String) parameters.get("language"); 260 String id = StringUtils.defaultString((String) parameters.get("id")); 261 String url = StringUtils.defaultString((String) parameters.get("url")); 262 String internalUrl = StringUtils.defaultString((String) parameters.get("internal-url")); 263 264 try 265 { 266 DefaultLink link = _resolver.resolveById(id); 267 268 // If the url was changed, check that the new url doesn't already exist. 269 if (!link.getUrl().equals(url) && _urlExists(url, internalUrl, siteName, language)) 270 { 271 result.put("already-exists", true); 272 return result; 273 } 274 275 _setValues(link, parameters); 276 link.saveChanges(); 277 278 // Notify listeners 279 Map<String, Object> eventParams = new HashMap<>(); 280 eventParams.put(ObservationConstants.ARGS_ID, link.getId()); 281 eventParams.put(ObservationConstants.ARGS_NAME, link.getName()); 282 eventParams.put(ObservationConstants.ARGS_PATH, link.getPath()); 283 284 _observationManager.notify(new Event(DirectoryEvents.LINK_MODIFIED, _currentUserProvider.getUser(), eventParams)); 285 286 return convertLink2JsonObject(link); 287 } 288 catch (UnknownAmetysObjectException e) 289 { 290 result.put("unknown-link", true); 291 return result; 292 } 293 catch (AmetysRepositoryException e) 294 { 295 throw new IllegalStateException(e); 296 } 297 } 298 299 @SuppressWarnings("unchecked") 300 private void _setValues(Link link, Map<String, Object> values) 301 { 302 // Set values (url, type, title, etc.) 303 _setLinkValues(link, values); 304 305 // Set themes 306 List<String> themes = Collections.EMPTY_LIST; 307 Object themesFromValues = values.get("themes"); 308 if (themesFromValues instanceof List) 309 { 310 themes = (List<String>) themesFromValues; 311 } 312 else if (themesFromValues instanceof String) 313 { 314 themes = _jsonUtils.convertJsonToList((String) themesFromValues).stream() 315 .map(Object::toString) 316 .collect(Collectors.toList()); 317 } 318 319 _setThemes(link, themes); 320 } 321 322 private void _setLinkValues(Link link, Map<String, Object> values) 323 { 324 String url = StringUtils.defaultString((String) values.get("url")); 325 String dynInfoProviderId = StringUtils.defaultString((String) values.get("dynamic-info-provider")); 326 String internalUrl = StringUtils.defaultString((String) values.get("internal-url")); 327 String urlType = StringUtils.defaultString((String) values.get("url-type")); 328 String title = StringUtils.defaultString((String) values.get("title")); 329 String content = StringUtils.defaultString((String) values.get("content")); 330 String alternative = StringUtils.defaultString((String) values.get("url-alternative")); 331 String color = StringUtils.defaultString((String) values.get("color")); 332 String pageId = StringUtils.defaultString((String) values.get("page")); 333 String status = StringUtils.defaultString((String) values.get("status")); 334 335 String pictureAsStr = StringUtils.defaultString((String) values.get("picture")); 336 String pictureAlternative = StringUtils.defaultString((String) values.get("picture-alternative")); 337 338 // Check the dynamic provider still exists 339 if (!_dynamicInfoExtensionPoint.hasExtension(dynInfoProviderId)) 340 { 341 dynInfoProviderId = ""; 342 } 343 344 link.setUrl(LinkType.valueOf(urlType), url); 345 link.setDynamicInformationProvider(dynInfoProviderId); 346 link.setInternalUrl(internalUrl); 347 link.setTitle(title); 348 link.setContent(content); 349 link.setAlternative(alternative); 350 link.setPictureAlternative(pictureAlternative); 351 link.setColor(color); 352 link.setPage(pageId); 353 link.setStatus(StringUtils.isNotBlank(status) ? LinkStatus.valueOf(status) : LinkStatus.NORMAL); 354 355 _setPicture(link, pictureAsStr); 356 } 357 358 private void _setThemes(Link link, List<String> themes) 359 { 360 link.setThemes(themes.toArray(new String[themes.size()])); 361 } 362 363 private void _setPicture(Link link, String valueAsStr) 364 { 365 if (StringUtils.isNotEmpty(valueAsStr)) 366 { 367 Map<String, Object> picture = _jsonUtils.convertJsonToMap(valueAsStr); 368 369 if (!picture.isEmpty()) 370 { 371 String pictureType = (String) picture.get("type"); 372 String value = (String) picture.get("id"); 373 374 if (pictureType.equals("explorer") && !"untouched".equals(value)) 375 { 376 link.setResourcePicture(value); 377 } 378 else if (pictureType.equals("glyph")) 379 { 380 link.setPictureGlyph(value); 381 } 382 else if (!"untouched".equals(value)) 383 { 384 UserIdentity user = _currentUserProvider.getUser(); 385 Upload upload = _uploadManager.getUpload(user, value); 386 387 String filename = upload.getFilename(); 388 String mimeType = upload.getMimeType() != null ? upload.getMimeType() : _cocoonContext.getMimeType(filename); 389 String finalMimeType = mimeType != null ? mimeType : "application/unknown"; 390 391 link.setExternalPicture(finalMimeType, filename, upload.getInputStream()); 392 } 393 } 394 else 395 { 396 // Remove picture 397 link.setNoPicture(); 398 } 399 400 } 401 else 402 { 403 // Remove picture 404 link.setNoPicture(); 405 } 406 } 407 408 /** 409 * Delete one or multiples links 410 * @param ids a list of links' ids 411 * @return true if all the links were deleted, false if at least one link could not be delete. 412 */ 413 @Callable 414 public List<String> deleteLinks(List<String> ids) 415 { 416 List<String> result = new ArrayList<>(); 417 418 for (String id : ids) 419 { 420 try 421 { 422 DefaultLink link = _resolver.resolveById(id); 423 424 String siteName = link.getSiteName(); 425 String language = link.getLanguage(); 426 String linkId = link.getId(); 427 String name = link.getName(); 428 String path = link.getPath(); 429 430 ModifiableAmetysObject parent = link.getParent(); 431 link.remove(); 432 433 parent.saveChanges(); 434 435 // Notify listeners 436 Map<String, Object> eventParams = new HashMap<>(); 437 eventParams.put(ObservationConstants.ARGS_ID, linkId); 438 eventParams.put(ObservationConstants.ARGS_NAME, name); 439 eventParams.put(ObservationConstants.ARGS_PATH, path); 440 eventParams.put("siteName", siteName); 441 eventParams.put("language", language); 442 _observationManager.notify(new Event(DirectoryEvents.LINK_DELETED, _currentUserProvider.getUser(), eventParams)); 443 444 result.add(linkId); 445 } 446 catch (UnknownAmetysObjectException e) 447 { 448 // ignore the id and continue the deletion. 449 getLogger().error("Unable to delete the link of id '" + id + ", because it does not exist.", e); 450 } 451 } 452 453 return result; 454 } 455 456 /** 457 * Move a link in the list 458 * @param id the link id 459 * @param role the move action 460 * @throws RepositoryException if a repository error occurs. 461 * @return the moved link in JSON 462 */ 463 @Callable 464 public Map<String, Object> moveLink(String id, String role) throws RepositoryException 465 { 466 DefaultLink link = _resolver.resolveById(id); 467 468 switch (role) 469 { 470 case "move-first": 471 _moveFirst(link); 472 break; 473 case "move-up": 474 _moveUp(link); 475 break; 476 case "move-down": 477 _moveDown(link); 478 break; 479 case "move-last": 480 _moveLast(link); 481 break; 482 default: 483 break; 484 } 485 486 return convertLink2JsonObject(link); 487 } 488 489 490 491 /** 492 * Get the JSON object representing a link 493 * @param id the id of link 494 * @return the link as JSON object 495 */ 496 @Callable 497 public Map<String, Object> getLink (String id) 498 { 499 DefaultLink link = _resolver.resolveById(id); 500 return convertLink2JsonObject(link); 501 } 502 503 /** 504 * Determines if the restriction IP parameter is not empty 505 * @param siteName the site name 506 * @return true if the restriction IP parameter is not empty 507 */ 508 @Callable 509 public boolean isInternalURLAllowed (String siteName) 510 { 511 Site site = _siteManager.getSite(siteName); 512 String allowedIpParameter = site.getValue("allowed-ip"); 513 return StringUtils.isNotBlank(allowedIpParameter); 514 } 515 516 /** 517 * Returns the list of providers of dynamic information as json object 518 * @return the providers of dynamic information 519 */ 520 @Callable 521 public List<Map<String, Object>> getDynamicInformationProviders() 522 { 523 List<Map<String, Object>> result = new ArrayList<>(); 524 525 for (String id : _dynamicInfoExtensionPoint.getExtensionsIds()) 526 { 527 DynamicInformationProvider provider = _dynamicInfoExtensionPoint.getExtension(id); 528 Map<String, Object> info = new HashMap<>(); 529 info.put("id", provider.getId()); 530 info.put("label", provider.getLabel()); 531 result.add(info); 532 } 533 534 return result; 535 } 536 537 /** 538 * Convert a link to JSON object 539 * @param link the link 540 * @return the link as JSON object 541 */ 542 public Map<String, Object> convertLink2JsonObject (DefaultLink link) 543 { 544 Map<String, Object> infos = new HashMap<>(); 545 546 infos.put("id", link.getId()); 547 infos.put("lang", link.getLanguage()); 548 infos.put("url", link.getUrl()); 549 infos.put("dynamicInfoProvider", link.getDynamicInformationProvider()); 550 infos.put("internalUrl", link.getInternalUrl()); 551 infos.put("urlType", link.getUrlType().toString()); 552 553 if (link.getUrlType() == LinkType.PAGE) 554 { 555 String pageId = link.getUrl(); 556 try 557 { 558 Page page = _resolver.resolveById(pageId); 559 infos.put("pageTitle", page.getTitle()); 560 } 561 catch (UnknownAmetysObjectException e) 562 { 563 infos.put("unknownPage", true); 564 } 565 } 566 567 infos.put("title", link.getTitle()); 568 infos.put("alternative", link.getAlternative()); 569 570 infos.put("content", StringUtils.defaultString(link.getContent())); 571 infos.put("pictureAlternative", StringUtils.defaultString(link.getPictureAlternative())); 572 573 Map<String, Object> pictureInfos = new HashMap<>(); 574 String pictureType = link.getPictureType(); 575 576 TraversableAmetysObject parent = link.getParent(); 577 infos.put("position", parent.getChildPosition(link)); 578 infos.put("count", parent.getChildren().getSize()); 579 580 infos.put("color", StringUtils.defaultString(link.getColor())); 581 infos.put("page", StringUtils.defaultString(link.getPage())); 582 if (link.getStatus() != null) 583 { 584 infos.put("status", link.getStatus().name()); 585 } 586 587 if (pictureType.equals("resource")) 588 { 589 String resourceId = link.getResourcePictureId(); 590 pictureInfos.put("id", resourceId); 591 try 592 { 593 Resource resource = _resolver.resolveById(resourceId); 594 595 pictureInfos.put("filename", resource.getName()); 596 pictureInfos.put("size", resource.getLength()); 597 pictureInfos.put("type", "explorer"); 598 pictureInfos.put("lastModified", resource.getLastModified()); 599 600 String contextPath = ContextHelper.getRequest(_context).getContextPath(); 601 String viewUrl = contextPath + "/plugins/explorer/resource?id=" + resource.getId(); 602 String downloadUrl = viewUrl + "&download=true"; 603 pictureInfos.put("viewUrl", viewUrl); 604 pictureInfos.put("downloadUrl", downloadUrl); 605 } 606 catch (UnknownAmetysObjectException e) 607 { 608 getLogger().error("The resource of id'" + resourceId + "' does not exist anymore. The picture for link of id '" + link.getId() + "' will be ignored.", e); 609 infos.put("pictureNotFound", true); 610 } 611 } 612 else if (pictureType.equals("external")) 613 { 614 Binary picMeta = link.getExternalPicture(); 615 616 pictureInfos.put("path", DefaultLink.PROPERTY_PICTURE); 617 pictureInfos.put("filename", picMeta.getFilename()); 618 pictureInfos.put("size", picMeta.getLength()); 619 pictureInfos.put("lastModified", picMeta.getLastModificationDate()); 620 pictureInfos.put("type", "metadata"); 621 622 String contextPath = ContextHelper.getRequest(_context).getContextPath(); 623 String viewUrl = contextPath + "/plugins/cms/binary/" + DefaultLink.PROPERTY_PICTURE + "?objectId=" + link.getId(); 624 String downloadUrl = viewUrl + "&download=true"; 625 pictureInfos.put("viewUrl", viewUrl); 626 pictureInfos.put("downloadUrl", downloadUrl); 627 628 } 629 else if (pictureType.equals("glyph")) 630 { 631 pictureInfos.put("id", link.getPictureGlyph()); 632 pictureInfos.put("type", "glyph"); 633 } 634 infos.put("picture", pictureInfos); 635 636 infos.put("isRestricted", !_rightManager.hasAnonymousReadAccess(link)); 637 638 // Themes 639 List<Map<String, Object>> themesList = new ArrayList<>(); 640 for (String themeId : link.getThemes()) 641 { 642 try 643 { 644 I18nizableText themeTitle = _directoryHelper.getThemeTitle(themeId, link.getSiteName(), link.getLanguage()); 645 Map<String, Object> themeData = new HashMap<>(); 646 themeData.put("id", themeId); 647 themeData.put("label", themeTitle); 648 themesList.add(themeData); 649 } 650 catch (UnknownAmetysObjectException e) 651 { 652 // Theme does not exist anymore 653 } 654 } 655 656 infos.put("themes", themesList); 657 658 return infos; 659 } 660 661 /** 662 * Get links infos 663 * @param linkIds the link id 664 * @return the link infos 665 */ 666 @Callable 667 public List<Map<String, Object>> getLinks(List<String> linkIds) 668 { 669 List<Map<String, Object>> result = new ArrayList<>(); 670 671 for (String linkId: linkIds) 672 { 673 try 674 { 675 result.add(getLink(linkId)); 676 } 677 catch (UnknownAmetysObjectException e) 678 { 679 // does not exists 680 } 681 } 682 return result; 683 } 684 685 /** 686 * Test if a link with the specified url or internal url exists in the directory. 687 * @param url the url to test. 688 * @param internalUrl the internal url to test 689 * @param siteName the site name. 690 * @param language the language. 691 * @return true if the link exists. 692 * @throws AmetysRepositoryException if a repository error occurs. 693 */ 694 protected boolean _urlExists(String url, String internalUrl, String siteName, String language) throws AmetysRepositoryException 695 { 696 boolean externalLinkExists = false; 697 if (StringUtils.isNotBlank(url)) 698 { 699 String externalLinkXpathQuery = _directoryHelper.getUrlExistsQuery(siteName, language, url); 700 try (AmetysObjectIterable<DefaultLink> externalLinks = _resolver.query(externalLinkXpathQuery);) 701 { 702 externalLinkExists = externalLinks.iterator().hasNext(); 703 } 704 } 705 706 boolean internalLinkExists = false; 707 if (StringUtils.isNotBlank(internalUrl)) 708 { 709 String internalLinkXpathQuery = _directoryHelper.getUrlExistsQuery(siteName, language, internalUrl); 710 try (AmetysObjectIterable<DefaultLink> internalLinks = _resolver.query(internalLinkXpathQuery);) 711 { 712 internalLinkExists = internalLinks.iterator().hasNext(); 713 } 714 } 715 716 return externalLinkExists || internalLinkExists; 717 } 718 719 /** 720 * Test if a link with the specified url or internal url exists in the directory for the given user. 721 * @param url the url to test. 722 * @param internalUrl the internal url to test 723 * @param siteName the site name. 724 * @param language the language. 725 * @param user The user identity 726 * @return true if the link exists for the given user. 727 * @throws AmetysRepositoryException if a repository error occurs. 728 */ 729 protected boolean _urlExistsForUser(String url, String internalUrl, String siteName, String language, UserIdentity user) throws AmetysRepositoryException 730 { 731 boolean externalLinkExists = false; 732 if (StringUtils.isNotBlank(url)) 733 { 734 String externalLinkXpathQuery = _directoryHelper.getUrlExistsForUserQuery(siteName, language, url, user); 735 try (AmetysObjectIterable<DefaultLink> externalLinks = _resolver.query(externalLinkXpathQuery);) 736 { 737 externalLinkExists = externalLinks.iterator().hasNext(); 738 } 739 } 740 741 boolean internalLinkExists = false; 742 if (StringUtils.isNotBlank(internalUrl)) 743 { 744 String internalLinkXpathQuery = _directoryHelper.getUrlExistsForUserQuery(siteName, language, internalUrl, user); 745 try (AmetysObjectIterable<DefaultLink> internalLinks = _resolver.query(internalLinkXpathQuery);) 746 { 747 internalLinkExists = internalLinks.iterator().hasNext(); 748 } 749 } 750 751 return externalLinkExists || internalLinkExists; 752 } 753 754 /** 755 * Create the link object. 756 * @param name the desired link name. 757 * @param rootNode the links root node. 758 * @return the created Link. 759 */ 760 protected DefaultLink _createLink(String name, ModifiableTraversableAmetysObject rootNode) 761 { 762 String originalName = NameHelper.filterName(name); 763 764 // Find unique name 765 String uniqueName = originalName; 766 int index = 2; 767 while (rootNode.hasChild(uniqueName)) 768 { 769 uniqueName = originalName + "-" + (index++); 770 } 771 772 return rootNode.createChild(uniqueName, DefaultLinkFactory.LINK_NODE_TYPE); 773 } 774 775 /** 776 * Move link the first position 777 * @param link the link to move 778 * @throws RepositoryException if an error occurs while exploring the repository 779 */ 780 private void _moveFirst(DefaultLink link) throws RepositoryException 781 { 782 try (AmetysObjectIterable<AmetysObject> children = ((TraversableAmetysObject) link.getParent()).getChildren();) 783 { 784 // Resolve the link in the same session or the linkRoot.saveChanges() call below won't see the order changes. 785 link.orderBefore(((TraversableAmetysObject) link.getParent()).getChildren().iterator().next()); 786 ((ModifiableAmetysObject) link.getParent()).saveChanges(); 787 } 788 } 789 790 /** 791 * Move link after its following 792 * @param link the link to move down 793 * @throws RepositoryException if an error occurs while exploring the repository 794 */ 795 private void _moveDown(DefaultLink link) throws RepositoryException 796 { 797 TraversableAmetysObject parentNode = link.getParent(); 798 AmetysObjectIterable<AmetysObject> siblings = parentNode.getChildren(); 799 Iterator<AmetysObject> it = siblings.iterator(); 800 801 boolean iterate = true; 802 while (it.hasNext() && iterate) 803 { 804 DefaultLink sibling = (DefaultLink) it.next(); 805 iterate = !sibling.getName().equals(link.getName()); 806 } 807 808 if (it.hasNext()) 809 { 810 // Move the link after his next sibling: move the next sibling before the link to move. 811 DefaultLink nextLink = (DefaultLink) it.next(); 812 nextLink.orderBefore(link); 813 814 link.saveChanges(); 815 } 816 } 817 818 /** 819 * Move link before its preceding 820 * @param link the link to move up 821 * @throws RepositoryException if an error occurs while exploring the repository 822 */ 823 private void _moveUp(DefaultLink link) throws RepositoryException 824 { 825 TraversableAmetysObject parentNode = link.getParent(); 826 AmetysObjectIterable<AmetysObject> siblings = parentNode.getChildren(); 827 Iterator<AmetysObject> it = siblings.iterator(); 828 DefaultLink previousLink = null; 829 830 while (it.hasNext()) 831 { 832 DefaultLink sibling = (DefaultLink) it.next(); 833 if (sibling.getName().equals(link.getName())) 834 { 835 break; 836 } 837 838 previousLink = sibling; 839 } 840 841 if (previousLink != null) 842 { 843 // Move the link after his next sibling: move the next sibling before the link to move. 844 link.orderBefore(previousLink); 845 link.saveChanges(); 846 } 847 } 848 849 /** 850 * Move link to the last position. 851 * @param link the link to move up 852 * @throws RepositoryException if an error occurs while exploring the repository 853 */ 854 private void _moveLast(DefaultLink link) throws RepositoryException 855 { 856 link.moveTo(link.getParent(), false); 857 ((ModifiableAmetysObject) link.getParent()).saveChanges(); 858 } 859 860 /** 861 * Set access to the link 862 * @param link the link 863 * @param user the user to set access. Can be null, in this case, we set anonymous right 864 */ 865 private void _setAccess (Link link, UserIdentity user) 866 { 867 if (user != null) 868 { 869 _profileAssignmentStorageEP.allowProfileToUser(user, RightManager.READER_PROFILE_ID, link); 870 } 871 else 872 { 873 _profileAssignmentStorageEP.allowProfileToAnonymous(RightManager.READER_PROFILE_ID, link); 874 } 875 876 Map<String, Object> eventParams = new HashMap<>(); 877 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_CONTEXT, link); 878 eventParams.put(org.ametys.core.ObservationConstants.ARGS_ACL_PROFILES, Collections.singleton(RightManager.READER_PROFILE_ID)); 879 880 _observationManager.notify(new Event(org.ametys.core.ObservationConstants.EVENT_ACL_UPDATED, _currentUserProvider.getUser(), eventParams)); 881 } 882 883 /** 884 * Retrieves the color of the link. 885 * If there is no color configured on the link, the default site color 886 * @param link the link 887 * @return the color of the link 888 */ 889 public String getLinkColor(DefaultLink link) 890 { 891 Map<String, Map<String, String>> colors = _colorComponent.getColors(link.getSiteName()); 892 if (colors.containsKey(link.getColor())) 893 { 894 return colors.get(link.getColor()).get("main"); 895 } 896 else 897 { 898 return colors.get(_colorComponent.getDefaultKey(link.getSiteName())).get("main"); 899 } 900 } 901}