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