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