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.web.repository.page; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Locale; 027import java.util.Map; 028import java.util.Objects; 029import java.util.Set; 030import java.util.function.Predicate; 031import java.util.stream.Collectors; 032 033import javax.jcr.Node; 034import javax.jcr.Repository; 035import javax.jcr.RepositoryException; 036import javax.jcr.Session; 037 038import org.apache.avalon.framework.activity.Initializable; 039import org.apache.avalon.framework.component.Component; 040import org.apache.avalon.framework.service.ServiceException; 041import org.apache.avalon.framework.service.ServiceManager; 042import org.apache.commons.collections4.CollectionUtils; 043import org.apache.commons.lang3.StringUtils; 044 045import org.ametys.cms.contenttype.ContentType; 046import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 047import org.ametys.cms.languages.Language; 048import org.ametys.cms.repository.Content; 049import org.ametys.cms.repository.ContentDAO.TagMode; 050import org.ametys.cms.repository.ModifiableContent; 051import org.ametys.cms.repository.WorkflowAwareContent; 052import org.ametys.cms.tag.CMSTag; 053import org.ametys.cms.tag.CMSTag.TagVisibility; 054import org.ametys.cms.tag.Tag; 055import org.ametys.cms.tag.TagHelper; 056import org.ametys.cms.tag.TagProviderExtensionPoint; 057import org.ametys.core.cache.AbstractCacheManager; 058import org.ametys.core.cache.Cache; 059import org.ametys.core.observation.Event; 060import org.ametys.core.observation.ObservationManager; 061import org.ametys.core.observation.Observer; 062import org.ametys.core.right.RightManager; 063import org.ametys.core.right.RightManager.RightResult; 064import org.ametys.core.ui.Callable; 065import org.ametys.core.user.CurrentUserProvider; 066import org.ametys.core.user.UserIdentity; 067import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 068import org.ametys.plugins.explorer.ExplorerNode; 069import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 070import org.ametys.plugins.explorer.resources.Resource; 071import org.ametys.plugins.repository.AmetysObject; 072import org.ametys.plugins.repository.AmetysObjectIterable; 073import org.ametys.plugins.repository.AmetysObjectIterator; 074import org.ametys.plugins.repository.AmetysObjectResolver; 075import org.ametys.plugins.repository.AmetysRepositoryException; 076import org.ametys.plugins.repository.CopiableAmetysObject; 077import org.ametys.plugins.repository.ModifiableAmetysObject; 078import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 079import org.ametys.plugins.repository.RemovableAmetysObject; 080import org.ametys.plugins.repository.RepositoryConstants; 081import org.ametys.plugins.repository.TraversableAmetysObject; 082import org.ametys.plugins.repository.UnknownAmetysObjectException; 083import org.ametys.plugins.repository.jcr.JCRAmetysObject; 084import org.ametys.plugins.repository.jcr.NameHelper; 085import org.ametys.plugins.repository.jcr.SimpleAmetysObject; 086import org.ametys.plugins.repository.lock.LockHelper; 087import org.ametys.plugins.repository.lock.LockableAmetysObject; 088import org.ametys.plugins.repository.query.expression.Expression.Operator; 089import org.ametys.plugins.repository.tag.TaggableAmetysObject; 090import org.ametys.plugins.repository.version.VersionableAmetysObject; 091import org.ametys.plugins.workflow.support.WorkflowProvider; 092import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 093import org.ametys.runtime.i18n.I18nizableText; 094import org.ametys.runtime.model.type.ModelItemTypeConstants; 095import org.ametys.web.ObservationConstants; 096import org.ametys.web.WebConstants; 097import org.ametys.web.alias.Alias.TargetType; 098import org.ametys.web.alias.AliasHelper; 099import org.ametys.web.alias.DefaultAlias; 100import org.ametys.web.languages.WebLanguagesManager; 101import org.ametys.web.repository.content.SharedContent; 102import org.ametys.web.repository.content.WebContent; 103import org.ametys.web.repository.content.shared.SharedContentManager; 104import org.ametys.web.repository.page.Page.LinkType; 105import org.ametys.web.repository.page.Page.PageType; 106import org.ametys.web.repository.page.ZoneItem.ZoneType; 107import org.ametys.web.repository.page.jcr.DefaultPage; 108import org.ametys.web.repository.site.Site; 109import org.ametys.web.repository.sitemap.Sitemap; 110import org.ametys.web.rights.PageRightAssignmentContext; 111import org.ametys.web.service.Service; 112import org.ametys.web.service.ServiceExtensionPoint; 113import org.ametys.web.site.CopyUpdaterExtensionPoint; 114import org.ametys.web.skin.Skin; 115import org.ametys.web.skin.SkinTemplate; 116import org.ametys.web.skin.SkinTemplateZone; 117import org.ametys.web.skin.SkinsManager; 118import org.ametys.web.skin.TemplatesAssignmentHandler; 119import org.ametys.web.synchronization.SynchronizeComponent; 120import org.ametys.web.tags.TagExpression; 121 122/** 123 * DAO for manipulating pages 124 */ 125public class PageDAO extends AbstractSitemapElementsDAO implements Component, Initializable, Observer 126{ 127 /** Constant for untouched binary metadata. */ 128 public static final String __SERVICE_PARAM_UNTOUCHED_BINARY = "untouched"; 129 130 /** Avalon Role */ 131 public static final String ROLE = PageDAO.class.getName(); 132 133 /** Constant for the {@link Cache} id for the pages in cache by sitename, lang, tag */ 134 private static final String MEMORY_PAGESTAGCACHE = PageDAO.class.getName() + "$PagesUUIDByTag"; 135 136 private AmetysObjectResolver _resolver; 137 private ObservationManager _observationManager; 138 private CurrentUserProvider _currentUserProvider; 139 private SkinsManager _skinsManager; 140 private TemplatesAssignmentHandler _templatesHandler; 141 private ServicesAssignmentHandler _serviceHandler; 142 private ContentTypesAssignmentHandler _cTypeHandler; 143 private ContentTypeExtensionPoint _contentTypeExtensionPoint; 144 private ServiceExtensionPoint _serviceExtensionPoint; 145 private WorkflowProvider _workflowProvider; 146 private SharedContentManager _sharedContentManager; 147 private TagProviderExtensionPoint _tagProvider; 148 private WebLanguagesManager _webLanguagesManager; 149 private CopySiteComponent _copySiteComponent; 150 private RightManager _rightManager; 151 private SynchronizeComponent _synchronizeComponent; 152 private Repository _repository; 153 private AmetysObjectResolver _ametysObjectResolver; 154 private AbstractCacheManager _cacheManager; 155 156 private CopyUpdaterExtensionPoint _copyUpdaterEP; 157 158 @Override 159 public void service(ServiceManager smanager) throws ServiceException 160 { 161 super.service(smanager); 162 163 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 164 _ametysObjectResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 165 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 166 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 167 _skinsManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE); 168 _templatesHandler = (TemplatesAssignmentHandler) smanager.lookup(TemplatesAssignmentHandler.ROLE); 169 _cTypeHandler = (ContentTypesAssignmentHandler) smanager.lookup(ContentTypesAssignmentHandler.ROLE); 170 _serviceHandler = (ServicesAssignmentHandler) smanager.lookup(ServicesAssignmentHandler.ROLE); 171 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE); 172 _serviceExtensionPoint = (ServiceExtensionPoint) smanager.lookup(ServiceExtensionPoint.ROLE); 173 _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE); 174 _sharedContentManager = (SharedContentManager) smanager.lookup(SharedContentManager.ROLE); 175 _tagProvider = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE); 176 _webLanguagesManager = (WebLanguagesManager) smanager.lookup(WebLanguagesManager.ROLE); 177 _copySiteComponent = (CopySiteComponent) smanager.lookup(CopySiteComponent.ROLE); 178 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 179 _synchronizeComponent = (SynchronizeComponent) smanager.lookup(SynchronizeComponent.ROLE); 180 _repository = (Repository) smanager.lookup(Repository.class.getName()); 181 _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE); 182 _copyUpdaterEP = (CopyUpdaterExtensionPoint) smanager.lookup(CopyUpdaterExtensionPoint.ROLE); 183 } 184 185 public void initialize() throws Exception 186 { 187 _createCaches(); 188 _observationManager.registerObserver(this); 189 } 190 191 private void _createCaches() 192 { 193 _cacheManager.createMemoryCache(MEMORY_PAGESTAGCACHE, 194 new I18nizableText("plugin.web", "PLUGINS_WEB_PAGEDAO_CACHE_PAGES_BY_TAG_LABEL"), 195 new I18nizableText("plugin.web", "PLUGINS_WEB_PAGEDAO_CACHE_PAGES_BY_TAG_DESCRIPTION"), 196 true, 197 null); 198 } 199 200 public int getPriority(Event event) 201 { 202 return 0; 203 } 204 205 public boolean supports(Event event) 206 { 207 return event.getId().equals(ObservationConstants.EVENT_PAGE_UPDATED) && event.getArguments().containsKey(ObservationConstants.ARGS_PAGE_TAGS); 208 } 209 210 public void observe(Event event, Map<String, Object> transientVars) throws Exception 211 { 212 Page page = (Page) event.getArguments().get(ObservationConstants.ARGS_PAGE); 213 if (page != null) 214 { 215 _getMemoryPageTagCache().invalidate(PageTagCacheKey.of(page.getSiteName(), page.getSitemapName())); 216 } 217 } 218 219 /** 220 * Get the properties of given pages 221 * @param pageIds the id of pages 222 * @param zoneName The optional zone name to limit informations of zones to that zone. Can be null or empty to avoid limitation. 223 * @param zoneItemId The optional zone item identifier to limit informations of zones to that zone item. Can be null or empty to avoid limitation. 224 * @return the properties of pages in a result map 225 */ 226 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 227 public Map<String, Object> getPagesInfos (List<String> pageIds, String zoneName, String zoneItemId) 228 { 229 // Assume that no read access is checked (required for bus message target) 230 Map<String, Object> result = new HashMap<>(); 231 232 List<Map<String, Object>> pages = new ArrayList<>(); 233 List<String> pagesNotFound = new ArrayList<>(); 234 235 for (String pageId : pageIds) 236 { 237 try 238 { 239 Page page = _resolver.resolveById(pageId); 240 pages.add(getPageInfos(page, zoneName, zoneItemId)); 241 } 242 catch (UnknownAmetysObjectException e) 243 { 244 pagesNotFound.add(pageId); 245 } 246 } 247 248 result.put("pages", pages); 249 result.put("pagesNotFound", pagesNotFound); 250 251 return result; 252 } 253 254 /** 255 * Get the properties of given pages 256 * @param pageIds the id of pages 257 * @return the properties of pages in a result map 258 */ 259 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 260 public Map<String, Object> getPagesInfos (List<String> pageIds) 261 { 262 // Assume that no read access is checked (required for bus message target) 263 return getPagesInfos(pageIds, null, null); 264 } 265 266 /** 267 * Get the page's properties 268 * @param pageId the page ID 269 * @return the properties 270 */ 271 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 272 public Map<String, Object> getPageInfos (String pageId) 273 { 274 // Assume that no read access is checked (required for bus message target) 275 return getPageInfos(pageId, null, null); 276 } 277 278 /** 279 * Get the page's properties 280 * @param pageId the page ID 281 * @param zoneName The optional zone name to limit informations of zones to that zone. Can be null or empty to avoid limitation. 282 * @param zoneItemId The optional zone item identifier to limit informations of zones to that zone item. Can be null or empty to avoid limitation. 283 * @return the properties 284 */ 285 public Map<String, Object> getPageInfos (String pageId, String zoneName, String zoneItemId) 286 { 287 // Assume that no read access is checked (required for bus message target) 288 Page page = _resolver.resolveById(pageId); 289 return getPageInfos(page, zoneName, zoneItemId); 290 } 291 292 /** 293 * Get the page's properties 294 * @param page the page 295 * @param zoneName The optional zone name to limit informations of zones to that zone. Can be null or empty to avoid limitation. 296 * @param zoneItemId The optional zone item identifier to limit informations of zones to that zone item. Can be null or empty to avoid limitation. 297 * @return the properties 298 */ 299 public Map<String, Object> getPageInfos (Page page, String zoneName, String zoneItemId) 300 { 301 Map<String, Object> infos = new HashMap<>(); 302 303 infos.put("id", page.getId()); 304 infos.put("name", page.getName()); 305 infos.put("parentId", page.getParent().getId()); 306 infos.put("title", page.getTitle()); 307 infos.put("longTitle", page.getLongTitle()); 308 infos.put("path", page.getPathInSitemap()); 309 infos.put("siteName", page.getSiteName()); 310 infos.put("type", page.getType()); 311 infos.put("lang", page.getSitemapName()); 312 infos.put("isModifiable", page instanceof ModifiablePage); 313 infos.put("isMoveable", page instanceof MoveablePage); 314 infos.put("isTaggable", page instanceof TaggableAmetysObject); 315 infos.put("isVisible", page.isVisible()); 316 infos.put("isParentInvisible", _isParentInvisible(page)); 317 // Publication information 318 infos.put("publication", _publication2Json(page)); 319 infos.put("isPreviewable", _isPreviewable(page)); 320 321 // limitation information 322 if (StringUtils.isNotBlank(zoneName)) 323 { 324 infos.put("zoneName", zoneName); 325 } 326 if (StringUtils.isNotBlank(zoneItemId)) 327 { 328 infos.put("zoneItemId", zoneItemId); 329 } 330 331 String skinId = page.getSite().getSkinId(); 332 Skin skin = _skinsManager.getSkin(skinId); 333 infos.put("isPageValid", _synchronizeComponent.isPageValid(page, skin)); 334 335 Session liveSession = null; 336 try 337 { 338 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 339 infos.put("isLiveHierarchyValid", _synchronizeComponent.isHierarchyValid(page, liveSession)); 340 } 341 catch (RepositoryException e) 342 { 343 throw new RuntimeException("Unable to check live workspace", e); 344 } 345 finally 346 { 347 if (liveSession != null) 348 { 349 liveSession.logout(); 350 } 351 } 352 353 PageType type = page.getType(); 354 switch (type) 355 { 356 case CONTAINER: 357 infos.putAll(_sitemapElement2json(page, zoneName, zoneItemId)); 358 break; 359 360 case LINK: 361 infos.put("url", page.getURL()); 362 363 LinkType urlType = page.getURLType(); 364 infos.put("urlType", urlType.toString()); 365 366 switch (urlType) 367 { 368 case PAGE: 369 try 370 { 371 Page targetPage = _resolver.resolveById(page.getURL()); 372 infos.put("urlTitle", targetPage.getTitle()); 373 } 374 catch (UnknownAmetysObjectException e) 375 { 376 getLogger().error("Page '" + page.getId() + "' redirects to an unexisting page '" + page.getURL() + "'"); 377 } 378 break; 379 380 default: 381 break; 382 } 383 break; 384 default: 385 AmetysObjectIterator< ? extends Page> iterator = page.getChildrenPages().iterator(); 386 if (iterator.hasNext()) 387 { 388 Page firstSubPage = iterator.next(); 389 infos.put("url", firstSubPage.getId()); 390 infos.put("urlTitle", firstSubPage.getTitle()); 391 infos.put("urlType", LinkType.PAGE.toString()); 392 break; 393 394 } 395 break; 396 } 397 398 399 infos.put("rights", getUserRights(page)); 400 401 return infos; 402 } 403 404 /** 405 * Get the page's properties 406 * @param page the page 407 * @return the properties 408 */ 409 public Map<String, Object> getPageInfos (Page page) 410 { 411 return getPageInfos(page, null, null); 412 } 413 414 /** 415 * Get the page's properties 416 * @param pageId the id of page 417 * @return the properties 418 */ 419 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 420 public Map<String, Object> getPageProperties (String pageId) 421 { 422 Page page = _resolver.resolveById(pageId); 423 424 if (!_rightManager.currentUserHasReadAccess(page)) 425 { 426 return Map.of(); 427 } 428 429 Map<String, Object> infos = new HashMap<>(); 430 431 infos.put("id", page.getId()); 432 if (page instanceof JCRAmetysObject) 433 { 434 try 435 { 436 String jcrId = ((JCRAmetysObject) page).getNode().getIdentifier(); 437 infos.put("jcrId", jcrId); 438 439 String jcrPath = ((JCRAmetysObject) page).getNode().getPath(); 440 infos.put("jcrPath", jcrPath); 441 } 442 catch (RepositoryException e) 443 { 444 // Ignore 445 } 446 } 447 448 infos.put("repository-access", Boolean.toString(_rightManager.currentUserHasRight("REPOSITORY_Rights_Access", "/repository") == RightResult.RIGHT_ALLOW)); 449 450 infos.put("name", page.getName()); 451 infos.put("parentId", page.getParent().getId()); 452 infos.put("title", page.getTitle()); 453 infos.put("longTitle", page.getLongTitle()); 454 infos.put("path", page.getPathInSitemap()); 455 infos.put("siteName", page.getSiteName()); 456 infos.put("siteTitle", page.getSite().getTitle()); 457 infos.put("type", page.getType()); 458 459 String lang = page.getSitemapName(); 460 infos.put("lang", lang); 461 Language language = _webLanguagesManager.getAvailableLanguages().get(lang); 462 if (language != null) 463 { 464 infos.put("langIcon", language.getSmallIcon()); 465 infos.put("langLabel", language.getLabel()); 466 } 467 468 PageType type = page.getType(); 469 switch (type) 470 { 471 case CONTAINER: 472 infos.put("template", page.getTemplate()); 473 break; 474 475 case LINK: 476 infos.put("url", page.getURL()); 477 LinkType urlType = page.getURLType(); 478 infos.put("urlType", urlType.toString()); 479 break; 480 default: 481 break; 482 } 483 484 Map<String, Object> contextParameters = new HashMap<>(); 485 contextParameters.put("siteName", page.getSiteName()); 486 487 // Tags 488 List<I18nizableText> tags = new ArrayList<>(); 489 for (String tagName : page.getTags()) 490 { 491 Tag tag = _tagProvider.getTag(tagName, contextParameters); 492 if (tag != null) 493 { 494 tags.add(tag.getTitle()); 495 } 496 } 497 infos.put("tags", tags); 498 499 // Incoming references 500 List<Map<String, Object>> incomingContents = new ArrayList<>(); 501 AmetysObjectIterable<Content> contents = _getIncomingContentReferences (page.getId()); 502 for (Content content : contents) 503 { 504 incomingContents.add(_content2Json(content, new Locale(page.getSitemapName()))); 505 } 506 infos.put("inComingContents", incomingContents); 507 508 List<Map<String, Object>> incomingPages = new ArrayList<>(); 509 AmetysObjectIterable<Page> pages = _getIncomingPageReferences (page.getId()); 510 for (Page pageRef : pages) 511 { 512 incomingPages.add(_page2Json(pageRef)); 513 } 514 infos.put("inComingPages", incomingPages); 515 516 // Publication information 517 infos.put("publication", _publication2Json(page)); 518 519 return infos; 520 } 521 522 /** 523 * Check current user right on given page or sitemap 524 * @param id The id of the page or sitemap 525 * @param rightId The if of right to check 526 * @return true if user has right 527 */ 528 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 529 public boolean hasRight(String id, String rightId) 530 { 531 UserIdentity user = _currentUserProvider.getUser(); 532 SitemapElement page = _resolver.resolveById(id); 533 534 return _rightManager.hasRight(user, rightId, page) == RightResult.RIGHT_ALLOW; 535 } 536 537 /** 538 * Create a new page 539 * @param parentId The parent id. Can not be null. 540 * @param title The page's title. Can not be null. 541 * @param longTitle The page's long title. Can be null or blank. 542 * @return The result map with id of created page 543 */ 544 @Callable (rights = "Web_Rights_Page_Create", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 545 public Map<String, Object> createPage (String parentId, String title, String longTitle) 546 { 547 Map<String, Object> result = new HashMap<>(); 548 549 SitemapElement parent = _resolver.resolveById(parentId); 550 551 assert parent instanceof ModifiableSitemapElement; 552 553 try 554 { 555 ModifiablePage page = createPage((ModifiableSitemapElement) parent, null, title, longTitle); 556 result.put("id", page.getId()); 557 result.put("parentId", parent.getId()); 558 result.put("lang", page.getSitemapName()); 559 result.put("title", page.getTitle()); 560 } 561 catch (IllegalArgumentException e) 562 { 563 result.put("invalid-name", title); 564 } 565 return result; 566 } 567 568 /** 569 * Create a new page blank page. 570 * 571 * In the case where {@code name} is {@code null}, the value of title will be used. 572 * In all case, the name (or title) will be used to derive the page name (and therefore 573 * path by filtering it with {@link NameHelper#filterName(String)} and appending an integer 574 * if a page with the same name already exist. 575 * 576 * @param parent the sitemap element where the new page is created 577 * @param name the name to use as path for the page. (if null, the title will be used) 578 * @param title the title of the page 579 * @param longTitle the long title of the page 580 * @return The result map with id of the created page 581 * @throws IllegalArgumentException If the provided name (or title) can not be used in URI 582 */ 583 public ModifiablePage createPage(ModifiableSitemapElement parent, String name, String title, String longTitle) throws IllegalArgumentException 584 { 585 Site site = parent.getSite(); 586 587 String pageName = PageDAO.findFreePageNameByTitle(parent, name != null ? name : title); 588 589 ModifiablePage page = parent.createChild(pageName, "ametys:defaultPage"); 590 591 page.setTitle(title); 592 page.setType(PageType.NODE); 593 page.setSiteName(site.getName()); 594 page.setSitemapName(page.getSitemap().getName()); 595 596 if (!StringUtils.isBlank(longTitle)) 597 { 598 page.setLongTitle(longTitle); 599 } 600 601 site.saveChanges(); 602 603 Map<String, Object> eventParams = new HashMap<>(); 604 eventParams.put(ObservationConstants.ARGS_PAGE, page); 605 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams)); 606 607 return page; 608 } 609 610 /** 611 * Copy a page 612 * @param id The id of page to copy 613 * @param target The id of parent target page 614 * @param keepReferences true to keep references 615 * @return the result map 616 * @throws RepositoryException if an error occurred during copy 617 */ 618 @Callable (rights = "Web_Rights_Page_Create", rightContext = PageRightAssignmentContext.ID, paramIndex = 1) 619 public Map<String, String> copyPage (String id, String target, boolean keepReferences) throws RepositoryException 620 { 621 Map<String, String> result = new HashMap<>(); 622 623 Page page = _resolver.resolveById(id); 624 625 if (!(page instanceof CopiableAmetysObject)) 626 { 627 throw new IllegalArgumentException("The page '" + page.getId() + "' is not a copiable ametys object, it can not be copied"); 628 } 629 630 if (!(page instanceof JCRAmetysObject)) 631 { 632 throw new IllegalArgumentException("The page '" + page.getId() + "' is not a JCR ametys object, it can not be copied"); 633 } 634 635 SitemapElement parent = _resolver.resolveById(target); 636 637 if (parent instanceof ModifiableSitemapElement modifiableParent) 638 { 639 ModifiablePage cPage = null; 640 641 String initialTitle; 642 if (page instanceof DefaultPage defaultPage) 643 { 644 initialTitle = defaultPage.getInitialTitle(); 645 } 646 else 647 { 648 initialTitle = page.getTitle(); 649 } 650 651 String newPageName = PageDAO.findFreePageNameByTitle(parent, initialTitle); 652 653 if (!keepReferences) 654 { 655 // Restrict the copy to the page and its current children to avoid infinitive loop 656 List<String> pagesToCopy = new ArrayList<>(); 657 pagesToCopy.add(page.getId()); 658 pagesToCopy.addAll(_getChildrenPageIds(page)); 659 660 // Copy and duplicate contents 661 cPage = (ModifiablePage) ((CopiableAmetysObject) page).copyTo(modifiableParent, newPageName, pagesToCopy); 662 _copySiteComponent.updateReferencesAfterCopy(page, cPage); 663 664 // Creates the first version on all copied contents 665 _updateContentsAfterCopy(page, cPage); 666 } 667 else 668 { 669 // Copy without duplicating contents (keep references) 670 671 if (!page.getSitemapName().equals(modifiableParent.getSitemapName())) 672 { 673 throw new IllegalArgumentException("The page '" + page.getId() + "' from sitemap '" + page.getSitemapName() + "' cannot be copied to a different sitemap '" + parent.getSitemapName() + "' while keeping references to contents"); 674 } 675 676 677 String pagePath = ((SimpleAmetysObject) parent).getNode().getPath() + "/" + newPageName; 678 Node node = ((JCRAmetysObject) page).getNode(); 679 node.getSession().getWorkspace().copy(node.getPath(), pagePath); 680 681 cPage = modifiableParent.getChild(newPageName); 682 } 683 684 cPage.setTitle(PageDAO.findFreeTitle(modifiableParent, initialTitle)); 685 if (cPage instanceof DefaultPage defaultPage) 686 { 687 defaultPage.setInitialTitle(initialTitle); 688 } 689 690 ((ModifiableTraversableAmetysObject) parent).saveChanges(); 691 result.put("id", cPage.getId()); 692 693 Map<String, Object> eventParams = new HashMap<>(); 694 eventParams.put(ObservationConstants.ARGS_PAGE, cPage); 695 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams)); 696 } 697 698 return result; 699 } 700 701 /** 702 * find a free name under a given parent node 703 * @param parent The parent 704 * @param initialTitle The title of the page 705 * @return The free name 706 */ 707 public static String findFreePageNameByTitle(TraversableAmetysObject parent, String initialTitle) 708 { 709 return findFreePageNameByTitle(parent, initialTitle, null); 710 } 711 712 /** 713 * find a free name under a given parent node 714 * @param parent The parent 715 * @param initialTitle The title of the page 716 * @param currentName The current name of the node, that can thus be authorized 717 * @return The free name 718 */ 719 public static String findFreePageNameByTitle(TraversableAmetysObject parent, String initialTitle, String currentName) 720 { 721 String originalPageName = NameHelper.filterName(initialTitle); 722 723 return findFreePageName(parent, originalPageName, currentName); 724 } 725 726 /** 727 * find a free name under a given parent node 728 * @param parent The parent 729 * @param originalPageName The name of the page 730 * @return The free name 731 */ 732 public static String findFreePageName(TraversableAmetysObject parent, String originalPageName) 733 { 734 return findFreePageName(parent, originalPageName, null); 735 } 736 737 /** 738 * find a free name under a given parent node 739 * @param parent The parent 740 * @param originalPageName The name of the page 741 * @param currentName The current name of the node, that can thus be authorized 742 * @return The free name 743 */ 744 public static String findFreePageName(TraversableAmetysObject parent, String originalPageName, String currentName) 745 { 746 int index = 2; 747 748 String pageName = originalPageName; 749 while (parent.hasChild(pageName) && !pageName.equals(currentName)) 750 { 751 // Find unused name on new parent node 752 pageName = originalPageName + "-" + index++; 753 } 754 return pageName; 755 } 756 757 /** 758 * find a free title under a given parent node 759 * @param parent The parent 760 * @param initialTitle The title of the page 761 * @return The free title 762 */ 763 public static String findFreeTitle(SitemapElement parent, String initialTitle) 764 { 765 List<String> existingTitles = parent.getChildrenPages().stream().map(Page::getTitle).toList(); 766 767 int index = 2; 768 769 String title = initialTitle; 770 while (existingTitles.contains(title)) 771 { 772 title = initialTitle + " (" + index++ + ")"; 773 } 774 775 return title; 776 } 777 778 /** 779 * Copy a page under another page 780 * @param targetId the page to copy in 781 * @param sourceId the page to copy 782 * @param keepReferences true to keep references for contents, or false to duplicate contents 783 * @return The id of created page is a result map. 784 * @throws RepositoryException if an error occurs 785 */ 786 @Callable (rights = "Web_Rights_Page_Create", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 787 @Deprecated 788 public Map<String, String> pastePage(String targetId, String sourceId, boolean keepReferences) throws RepositoryException 789 { 790 return copyPage(sourceId, targetId, keepReferences); 791 } 792 793 /** 794 * Move a page 795 * @param id The page id 796 * @param parentId The id of parent destination 797 * @param index The position in parent child nodes where page will be inserted. -1 means as the last child. 798 * @return the result map 799 */ 800 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 801 public Map<String, Object> movePage (String id, String parentId, int index) 802 { 803 Map<String, Object> result = new HashMap<>(); 804 805 String oldPathInSitemap = null; 806 String newPathInSitemap = null; 807 808 Page page = _resolver.resolveById(id); 809 SitemapElement srcParent = page.getParent(); 810 oldPathInSitemap = page.getPathInSitemap(); 811 Sitemap sitemap = page.getSitemap(); 812 813 // check rights of deletion on old parent page 814 if (!srcParent.getId().equals(parentId) && !_canDelete(page)) 815 { 816 throw new IllegalStateException("You do not have the rights to delete the page '/" + page.getSitemapName() + "/" + oldPathInSitemap + "'"); 817 } 818 819 if (!(page instanceof MoveablePage)) 820 { 821 throw new IllegalArgumentException("The page '/" + page.getSitemapName() + "/" + oldPathInSitemap + "' is not a moveable page"); 822 } 823 824 if (srcParent.getId().equals(parentId) && index != -1) 825 { 826 try 827 { 828 // in case of reorder, check right of creation on current parent page 829 if (!_canCreate(srcParent)) 830 { 831 throw new IllegalStateException("You do not have to reorder a page under '/" + srcParent.getSitemapName() + "/" + srcParent.getPathInSitemap() + "'"); 832 } 833 834 Page brother = srcParent.getChildPageAt(index); 835 ((MoveablePage) page).orderBefore(brother); 836 } 837 catch (UnknownAmetysObjectException e) 838 { 839 // Move the last child position 840 ((MoveablePage) page).orderBefore(null); 841 } 842 843 // Path is not modified 844 newPathInSitemap = oldPathInSitemap; 845 } 846 else 847 { 848 SitemapElement newParentPage = _resolver.resolveById(parentId); 849 850 // check right of creation on new parent page 851 if (!_canCreate(newParentPage)) 852 { 853 throw new IllegalStateException("You do not have the rights to create a page under '/" + newParentPage.getSitemapName() + "/" + newParentPage.getPathInSitemap() + "'"); 854 } 855 856 ((MoveablePage) page).moveTo(newParentPage, true); 857 if (index != -1) 858 { 859 Page brother = newParentPage.getChildPageAt(index); 860 861 ((MoveablePage) page).orderBefore(brother); 862 } 863 864 // Path is modified 865 newPathInSitemap = page.getPathInSitemap(); 866 } 867 868 if (sitemap.needsSave()) 869 { 870 sitemap.saveChanges(); 871 } 872 873 // Notify observers that the page has been moved 874 Map<String, Object> eventParams = new HashMap<>(); 875 eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap); 876 eventParams.put(ObservationConstants.ARGS_PAGE, page); 877 eventParams.put("page.old.path", oldPathInSitemap); 878 eventParams.put("page.old.parent", srcParent); 879 eventParams.put(ObservationConstants.ARGS_PAGE_PATH, newPathInSitemap); 880 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_MOVED, _currentUserProvider.getUser(), eventParams)); 881 882 result.put("id", page.getId()); 883 result.put("parentId", page.getParent().getId()); 884 885 return result; 886 } 887 888 private boolean _canCreate(SitemapElement parentPage) 889 { 890 UserIdentity user = _currentUserProvider.getUser(); 891 if (_rightManager.hasRight(user, "Web_Rights_Page_Create", parentPage) == RightResult.RIGHT_ALLOW) 892 { 893 return true; 894 } 895 896 if (getLogger().isInfoEnabled()) 897 { 898 getLogger().info("The user '" + user + "' tried to create page under '/" + parentPage.getSitemapName() + "/" + parentPage.getPathInSitemap() + "' without sufficient rights"); 899 } 900 901 return false; 902 } 903 904 private boolean _canDelete(Page page) 905 { 906 UserIdentity user = _currentUserProvider.getUser(); 907 SitemapElement parent = page.getParent(); 908 if (_rightManager.hasRight(user, "Web_Rights_Page_Delete", parent) == RightResult.RIGHT_ALLOW) 909 { 910 return true; 911 } 912 913 if (getLogger().isInfoEnabled()) 914 { 915 getLogger().info("The user '" + user + "' tried to move page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "' without sufficient rights"); 916 } 917 918 return false; 919 } 920 921 /** 922 * Set pages as redirection 923 * @param pageIds the id of pages to modify 924 * @param url the url of redirection 925 * @param urlType the type of redirection 926 * @return the id of pages which succeeded or failed. 927 */ 928 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 929 public Map<String, Object> setLink (List<String> pageIds, String url, String urlType) 930 { 931 List<String> allRightIds = new ArrayList<>(); 932 List<Map<String, Object>> noRightPages = new ArrayList<>(); 933 List<Map<String, Object>> errorPages = new ArrayList<>(); 934 List<Map<String, Object>> noModifiablePages = new ArrayList<>(); 935 936 if (StringUtils.isEmpty(url)) 937 { 938 throw new IllegalArgumentException("Can not set page as a redirection with an empty url"); 939 } 940 941 for (String pageId : pageIds) 942 { 943 Page page = _resolver.resolveById(pageId); 944 945 try 946 { 947 if (_rightManager.currentUserHasRight("Web_Rights_Page_LinkPage", page) != RightResult.RIGHT_ALLOW) 948 { 949 noRightPages.add(Map.of("id", pageId, "title", page.getTitle())); 950 } 951 else if (!(page instanceof ModifiablePage)) 952 { 953 noModifiablePages.add(Map.of("id", pageId, "title", page.getTitle())); 954 } 955 else 956 { 957 ModifiablePage mPage = (ModifiablePage) page; 958 959 if (page.getType().equals(PageType.CONTAINER)) 960 { 961 // Remove zones 962 for (ModifiableZone zone : mPage.getZones()) 963 { 964 zone.remove(); 965 } 966 } 967 968 if (pageId.equals(url)) 969 { 970 throw new IllegalArgumentException("A page can not redirect to itself"); 971 } 972 973 mPage.setType(PageType.LINK); 974 mPage.setURL(LinkType.valueOf(urlType), url); 975 mPage.getSitemap().saveChanges(); 976 977 allRightIds.add(pageId); 978 979 Map<String, Object> eventParams = new HashMap<>(); 980 eventParams.put(ObservationConstants.ARGS_PAGE, page); 981 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 982 } 983 } 984 catch (Exception e) 985 { 986 getLogger().error("Cannot set the page '" + pageId + "' as link [" + url + ", " + urlType.toString() + "]", e); 987 errorPages.add(Map.of("id", pageId, "title", page.getTitle())); 988 } 989 } 990 991 return Map.of("allright-pages", allRightIds, "noright-pages", noRightPages, "error-pages", errorPages, "nomodifiable-pages", noModifiablePages); 992 } 993 994 /** 995 * Get available template for specified page 996 * @param pageId The page's id 997 * @return the list of available template as JSON 998 */ 999 private List<Map<String, Object>> _getAvailableTemplates (String pageId) 1000 { 1001 List<Map<String, Object>> templates = new ArrayList<>(); 1002 1003 Page page = _resolver.resolveById(pageId); 1004 1005 Set<String> availableTemplateIds = _templatesHandler.getAvailablesTemplates(page); 1006 for (String templateName : availableTemplateIds) 1007 { 1008 String skinId = page.getSite().getSkinId(); 1009 Skin skin = _skinsManager.getSkin(skinId); 1010 1011 SkinTemplate template = skin.getTemplate(templateName); 1012 1013 Map<String, Object> template2json = new HashMap<>(); 1014 template2json.put("id", template.getId()); 1015 template2json.put("label", template.getLabel()); 1016 template2json.put("description", template.getDescription()); 1017 template2json.put("iconSmall", template.getSmallImage()); 1018 template2json.put("iconMedium", template.getMediumImage()); 1019 template2json.put("iconLarge", template.getLargeImage()); 1020 template2json.put("zone", template.getDefaultZoneId()); 1021 1022 templates.add(template2json); 1023 } 1024 1025 return templates; 1026 } 1027 1028 /** 1029 * Get available content types for specified page 1030 * @param pageId The page's id 1031 * @param zoneName the name of the zone 1032 * @return the list of available content types 1033 */ 1034 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1035 public List<Map<String, Object>> getAvailableContentTypes (String pageId, String zoneName) 1036 { 1037 List<Map<String, Object>> contenttypes = new ArrayList<>(); 1038 1039 Page page = _resolver.resolveById(pageId); 1040 1041 Set<String> contentTypeIds = _cTypeHandler.getAvailableContentTypes(page, zoneName); 1042 for (String contentTypeId : contentTypeIds) 1043 { 1044 ContentType cType = _contentTypeExtensionPoint.getExtension(contentTypeId); 1045 1046 if (cType != null && _hasRight(cType, page)) 1047 { 1048 Map<String, Object> ctype2json = new HashMap<>(); 1049 ctype2json.put("id", cType.getId()); 1050 ctype2json.put("label", cType.getLabel()); 1051 ctype2json.put("description", cType.getDescription()); 1052 ctype2json.put("iconGlyph", cType.getIconGlyph()); 1053 ctype2json.put("iconDecorator", cType.getIconDecorator()); 1054 ctype2json.put("iconSmall", cType.getSmallIcon()); 1055 ctype2json.put("iconMedium", cType.getMediumIcon()); 1056 ctype2json.put("iconLarge", cType.getLargeIcon()); 1057 ctype2json.put("defaultTitle", cType.getDefaultTitle()); 1058 ctype2json.put("viewNames", cType.getViewNames(true)); 1059 ctype2json.put("category", cType.getCategory().isI18n() ? cType.getCategory().getKey() : cType.getCategory().getLabel()); 1060 ctype2json.put("categoryLabel", cType.getCategory()); 1061 1062 contenttypes.add(ctype2json); 1063 } 1064 } 1065 1066 return contenttypes; 1067 } 1068 1069 /** 1070 * Get available content types for a page being created 1071 * @param pageId The page's id. Can be null of the page is not yet created 1072 * @param zoneName the name of the zone 1073 * @param parentId The id of parent page 1074 * @param pageTitle The title of page to create 1075 * @param template The template of page to create 1076 * @return the list of available services 1077 */ 1078 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1079 public List<Map<String, Object>> getAvailableContentTypesForCreation(String pageId, String zoneName, String parentId, String pageTitle, String template) 1080 { 1081 if (StringUtils.isNotEmpty(pageId)) 1082 { 1083 // Get available services for a page 1084 return getAvailableContentTypes(pageId, zoneName); 1085 } 1086 else if (StringUtils.isNotEmpty(parentId)) 1087 { 1088 // Get available services for a not yet existing page 1089 SitemapElement parent = _resolver.resolveById(parentId); 1090 1091 // Create page temporarily 1092 Page page = _createPage(parent, pageTitle, template); 1093 1094 List<Map<String, Object>> availableContentTypes = getAvailableContentTypes(page.getId(), zoneName); 1095 1096 // Cancel page creation 1097 page.getSitemap().revertChanges(); 1098 1099 return availableContentTypes; 1100 } 1101 1102 return Collections.EMPTY_LIST; 1103 } 1104 1105 private boolean _hasRight(ContentType contentType, Page page) 1106 { 1107 String right = contentType.getRight(); 1108 1109 if (right == null) 1110 { 1111 return true; 1112 } 1113 else 1114 { 1115 UserIdentity user = _currentUserProvider.getUser(); 1116 return _rightManager.hasRight(user, right, page) == RightResult.RIGHT_ALLOW; 1117 } 1118 } 1119 1120 /** 1121 * Get available services for specified page 1122 * @param pageId The page's id 1123 * @param zoneName the name of the zone 1124 * @return the list of available services 1125 */ 1126 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1127 public List<Map<String, Object>> getAvailableServices (String pageId, String zoneName) 1128 { 1129 List<Map<String, Object>> services = new ArrayList<>(); 1130 1131 SitemapElement sitemapElement = _resolver.resolveById(pageId); 1132 1133 Set<String> serviceIds = _serviceHandler.getAvailableServices(sitemapElement, zoneName); 1134 for (String serviceId : serviceIds) 1135 { 1136 Service service = _serviceExtensionPoint.getExtension(serviceId); 1137 if (service != null && _hasRight(service, sitemapElement)) 1138 { 1139 Map<String, Object> serviceMap = new HashMap<>(); 1140 serviceMap.put("id", service.getId()); 1141 serviceMap.put("label", service.getLabel()); 1142 serviceMap.put("description", service.getDescription()); 1143 serviceMap.put("iconGlyph", service.getIconGlyph()); 1144 serviceMap.put("iconDecorator", service.getIconDecorator()); 1145 serviceMap.put("iconSmall", service.getSmallIcon()); 1146 serviceMap.put("iconMedium", service.getMediumIcon()); 1147 serviceMap.put("iconLarge", service.getLargeIcon()); 1148 serviceMap.put("parametersAction", service.getParametersScript().getScriptClassname()); 1149 serviceMap.put("category", service.getCategory().isI18n() ? service.getCategory().getKey() : service.getCategory().getLabel()); 1150 serviceMap.put("categoryLabel", service.getCategory()); 1151 1152 services.add(serviceMap); 1153 } 1154 } 1155 1156 return services; 1157 } 1158 1159 /** 1160 * Get available services for a page being created 1161 * @param pageId The page's id. Can be null of the page is not yet created 1162 * @param zoneName the name of the zone 1163 * @param parentId The id of parent page 1164 * @param pageTitle The title of page to create 1165 * @param template The template of page to create 1166 * @return the list of available services 1167 */ 1168 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1169 public List<Map<String, Object>> getAvailableServicesForCreation(String pageId, String zoneName, String parentId, String pageTitle, String template) 1170 { 1171 if (StringUtils.isNotEmpty(pageId)) 1172 { 1173 // Get available services for a page 1174 return getAvailableServices(pageId, zoneName); 1175 } 1176 else if (StringUtils.isNotEmpty(parentId)) 1177 { 1178 // Get available services for a not yet existing page 1179 SitemapElement parent = _resolver.resolveById(parentId); 1180 1181 // Create page temporarily 1182 Page page = _createPage(parent, pageTitle, template); 1183 1184 List<Map<String, Object>> availableServices = getAvailableServices(page.getId(), zoneName); 1185 1186 // Cancel page creation 1187 page.getSitemap().revertChanges(); 1188 1189 return availableServices; 1190 } 1191 1192 return Collections.EMPTY_LIST; 1193 } 1194 1195 private Page _createPage (SitemapElement parent, String pageTitle, String template) 1196 { 1197 Site site = parent.getSite(); 1198 1199 String pageName = PageDAO.findFreePageNameByTitle(parent, pageTitle); 1200 1201 ModifiablePage page = ((ModifiableTraversableAmetysObject) parent).createChild(pageName, "ametys:defaultPage"); 1202 1203 page.setTitle(pageTitle); 1204 page.setType(PageType.NODE); 1205 page.setSiteName(site.getName()); 1206 page.setSitemapName(page.getSitemap().getName()); 1207 1208 if (template != null) 1209 { 1210 String skinId = page.getSite().getSkinId(); 1211 SkinTemplate tpl = _skinsManager.getSkin(skinId).getTemplate(template); 1212 if (tpl == null) 1213 { 1214 throw new IllegalStateException("Template '" + template + "' does not exist on skin '" + skinId + "'"); 1215 } 1216 1217 // Set temporary the template to get available services 1218 page.setType(PageType.CONTAINER); 1219 page.setTemplate(template); 1220 } 1221 1222 return page; 1223 } 1224 1225 private boolean _hasRight(Service service, SitemapElement sitemapElement) 1226 { 1227 String right = service.getRight(); 1228 1229 if (right == null) 1230 { 1231 return true; 1232 } 1233 else 1234 { 1235 UserIdentity user = _currentUserProvider.getUser(); 1236 return _rightManager.hasRight(user, right, sitemapElement) == RightResult.RIGHT_ALLOW; 1237 } 1238 } 1239 1240 /** 1241 * Get available content types for a page being created 1242 * @param pageId The page's id. Can be null of the page is not yet created 1243 * @param parentId The id of parent page 1244 * @param pageTitle The title of page to create 1245 * @return the list of available services 1246 */ 1247 @Callable (rights = "Web_Rights_Page_Create", rightContext = PageRightAssignmentContext.ID, paramIndex = 1) 1248 public List<Map<String, Object>> getAvailableTemplatesForCreation (String pageId, String parentId, String pageTitle) 1249 { 1250 if (StringUtils.isNotEmpty(pageId)) 1251 { 1252 // Get available template for a page 1253 return _getAvailableTemplates(pageId); 1254 } 1255 else if (StringUtils.isNotEmpty(parentId)) 1256 { 1257 // Get available template for a not yet existing page 1258 SitemapElement parent = _resolver.resolveById(parentId); 1259 1260 // Create page temporarily 1261 Page page = _createPage(parent, pageTitle, null); 1262 1263 List<Map<String, Object>> availableTemplates = _getAvailableTemplates(page.getId()); 1264 1265 // Cancel page creation 1266 page.getSitemap().revertChanges(); 1267 1268 return availableTemplates; 1269 } 1270 1271 return Collections.EMPTY_LIST; 1272 } 1273 1274 /** 1275 * Get service info 1276 * @param pageId Optional, the page id of the service. To get some basic info about the page. 1277 * @param serviceId The id of the service 1278 * @return a Map containing some info about the service (label, url..) 1279 */ 1280 @Callable (rights = Callable.READ_ACCESS, rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 1281 public Map<String, Object> getServiceInfo(String pageId, String serviceId) 1282 { 1283 Map<String, Object> info = new HashMap<>(); 1284 1285 if (StringUtils.isNotEmpty(pageId)) 1286 { 1287 SitemapElement sitemapElement = _resolver.resolveById(pageId); 1288 info.put("page-id", sitemapElement.getId()); 1289 info.put("page-title", sitemapElement.getTitle()); 1290 } 1291 1292 Service service = _serviceExtensionPoint.getExtension(serviceId); 1293 info.put("id", service.getId()); 1294 info.put("label", service.getLabel()); 1295 info.put("url", service.getURL()); 1296 info.put("smallIcon", service.getSmallIcon()); 1297 info.put("iconGlyph", service.getIconGlyph()); 1298 info.put("iconDecorator", service.getIconDecorator()); 1299 return info; 1300 } 1301 1302 /** 1303 * Rename a page 1304 * @param pageId The id of page to rename 1305 * @param title The page's title 1306 * @param longTitle The page's long title. 1307 * @param updatePath true to update page's path 1308 * @param createAlias true to create a alias 1309 * @return the result map 1310 */ 1311 @Callable (rights = "Web_Rights_Page_Rename", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 1312 public Map<String, Object> renamePage (String pageId, String title, String longTitle, boolean updatePath, boolean createAlias) 1313 { 1314 Map<String, Object> result = new HashMap<>(); 1315 1316 Page page = _resolver.resolveById(pageId); 1317 1318 if (!(page instanceof ModifiablePage)) 1319 { 1320 throw new IllegalArgumentException("Can not rename a non-modifiable page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "'"); 1321 } 1322 1323 ModifiablePage mPage = (ModifiablePage) page; 1324 mPage.setTitle(title); 1325 mPage.setLongTitle(longTitle); 1326 1327 if (updatePath) 1328 { 1329 String oldPathInSitemap = page.getPathInSitemap(); 1330 String oldPath = "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + ".html"; 1331 String oldPathForChild = "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "/**.html"; 1332 1333 String pageName = ""; 1334 try 1335 { 1336 pageName = PageDAO.findFreePageNameByTitle(mPage.getParent(), title, page.getName()); 1337 } 1338 catch (IllegalArgumentException e) 1339 { 1340 result.put("invalid-name", title); 1341 return result; 1342 } 1343 1344 if (!page.getName().equals(pageName)) 1345 { 1346 mPage.rename(pageName); 1347 1348 if (createAlias) 1349 { 1350 ModifiableTraversableAmetysObject rootNode = AliasHelper.getRootNode(page.getSite()); 1351 1352 DefaultAlias alias = rootNode.createChild(AliasHelper.getAliasNextUniqueName(rootNode), "ametys:alias"); 1353 alias.setUrl(oldPath); 1354 alias.setTarget(page.getId()); 1355 alias.setType(TargetType.PAGE); 1356 alias.setCreationDate(new Date()); 1357 1358 // Alias for child pages 1359 alias = rootNode.createChild(AliasHelper.getAliasNextUniqueName(rootNode), "ametys:alias"); 1360 alias.setUrl(oldPathForChild); 1361 alias.setTarget("/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "/{1}.html"); 1362 alias.setType(TargetType.URL); 1363 alias.setCreationDate(new Date()); 1364 1365 rootNode.saveChanges(); 1366 } 1367 1368 // Notify observers that the page has been renamed 1369 Map<String, Object> eventParams = new HashMap<>(); 1370 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1371 eventParams.put("path.old.path", oldPathInSitemap); 1372 eventParams.put(ObservationConstants.ARGS_PAGE_PATH, page.getPathInSitemap()); 1373 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_RENAMED, _currentUserProvider.getUser(), eventParams)); 1374 1375 } 1376 else 1377 { 1378 // Notify observers that the page's title has been modified 1379 Map<String, Object> eventParams = new HashMap<>(); 1380 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1381 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1382 } 1383 1384 } 1385 else 1386 { 1387 // Notify observers that the page's title has been modified 1388 Map<String, Object> eventParams = new HashMap<>(); 1389 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1390 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1391 } 1392 1393 Sitemap sitemap = page.getSitemap(); 1394 if (sitemap.needsSave()) 1395 { 1396 sitemap.saveChanges(); 1397 } 1398 1399 result.put("id", page.getId()); 1400 result.put("path", page.getPath()); 1401 result.put("title", page.getTitle()); 1402 1403 return result; 1404 } 1405 1406 /** 1407 * Delete a page and its sub-pages. 1408 * The contents that belong only to the deleted page will be deleted if 'deleteBelongingContents' is set to true. 1409 * The newly created contents are deleted whatever the value if 'deleteBelongingContents'. 1410 * @param pageId the id of page to delete 1411 * @param deleteBelongingContents true to delete the contents that belong to the page and its sub-pages only 1412 * @return The id of deleted pages 1413 */ 1414 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1415 public Map<String, Object> deletePage(String pageId, boolean deleteBelongingContents) 1416 { 1417 ModifiablePage page = _resolver.resolveById(pageId); 1418 1419 // Check rights on parent 1420 if (!_canDelete(page)) 1421 { 1422 throw new IllegalStateException("You do not have the rights to delete the page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "'"); 1423 } 1424 1425 return deletePage(page, deleteBelongingContents); 1426 } 1427 1428 /** 1429 * Delete a page and its sub-pages. 1430 * The contents that belong only to the deleted page will be deleted if 'deleteBelongingContents' is set to true. 1431 * The newly created contents are deleted whatever the value if 'deleteBelongingContents'. 1432 * @param page the page to delete 1433 * @param deleteBelongingContents true to delete the contents that belong to the page and its sub-pages only 1434 * @return The id of deleted pages 1435 */ 1436 public Map<String, Object> deletePage(ModifiablePage page, boolean deleteBelongingContents) 1437 { 1438 Map<String, Object> result = new HashMap<>(); 1439 1440 PageHierarchy pageHierarchy = _getPageContents(page, true); 1441 List<String> contentToDelete = _getDeleteablePageContentIds(pageHierarchy, !deleteBelongingContents); 1442 1443 Sitemap sitemap = page.getSitemap(); 1444 SitemapElement parent = page.getParent(); 1445 String pagePathInSitemap = page.getPathInSitemap(); 1446 1447 List<String> childPagesIds = _getChildrenPageIds(page); 1448 1449 Map<String, Object> eventParams = new HashMap<>(); 1450 eventParams.put(ObservationConstants.ARGS_PAGE_ID, page.getId()); 1451 eventParams.put(ObservationConstants.ARGS_PAGE_PARENT, parent); 1452 eventParams.put(ObservationConstants.ARGS_PAGE_PATH, pagePathInSitemap); 1453 eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap); 1454 eventParams.put(ObservationConstants.ARGS_PAGE_CONTENTS, getPageContents(page)); 1455 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_DELETING, _currentUserProvider.getUser(), eventParams)); 1456 1457 // FIXME API test if this is not modifiable 1458 page.getParent(); 1459 page.remove(); 1460 ((ModifiableAmetysObject) parent).saveChanges(); 1461 1462 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_DELETED, _currentUserProvider.getUser(), eventParams)); 1463 1464 result.put("id", page.getId()); 1465 result.put("childPages", childPagesIds); 1466 1467 result.putAll(_contentDAO.deleteContents(contentToDelete, true)); 1468 return result; 1469 } 1470 1471 /** 1472 * Set pages as blank page 1473 * @param pageIds the id of pages to modify 1474 * @return the id of pages which succeeded or failed. 1475 */ 1476 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1477 public Map<String, Object> setBlank (List<String> pageIds) 1478 { 1479 List<String> allRightIds = new ArrayList<>(); 1480 List<Map<String, Object>> noRightPages = new ArrayList<>(); 1481 List<Map<String, Object>> errorPages = new ArrayList<>(); 1482 List<Map<String, Object>> noModifiablePages = new ArrayList<>(); 1483 1484 for (String pageId : pageIds) 1485 { 1486 Page page = _resolver.resolveById(pageId); 1487 1488 try 1489 { 1490 if (_rightManager.currentUserHasRight("Web_Rights_Page_BlankPage", page) != RightResult.RIGHT_ALLOW) 1491 { 1492 noRightPages.add(Map.of("id", pageId, "title", page.getTitle())); 1493 } 1494 else if (!(page instanceof ModifiablePage)) 1495 { 1496 noModifiablePages.add(Map.of("id", pageId, "title", page.getTitle())); 1497 } 1498 else 1499 { 1500 ModifiablePage mPage = (ModifiablePage) page; 1501 1502 if (page.getType().equals(PageType.CONTAINER)) 1503 { 1504 // Remove zones 1505 for (ModifiableZone zone : mPage.getZones()) 1506 { 1507 zone.remove(); 1508 } 1509 } 1510 1511 mPage.setType(PageType.NODE); 1512 mPage.getSitemap().saveChanges(); 1513 1514 allRightIds.add(pageId); 1515 1516 Map<String, Object> eventParams = new HashMap<>(); 1517 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1518 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 1519 } 1520 } 1521 catch (Exception e) 1522 { 1523 getLogger().error("Cannot set the page '" + pageId + "' as blank page", e); 1524 errorPages.add(Map.of("id", pageId, "title", page.getTitle())); 1525 } 1526 } 1527 1528 return Map.of("allright-pages", allRightIds, "noright-pages", noRightPages, "error-pages", errorPages, "nomodifiable-pages", noModifiablePages); 1529 } 1530 1531 /** 1532 * Set a template to pages 1533 * @param pageIds the id of pages to update 1534 * @param templateName The template name 1535 * @return the id of pages which succeeded 1536 */ 1537 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1538 public Map<String, Object> setTemplate (List<String> pageIds, String templateName) 1539 { 1540 return setTemplate(pageIds, templateName, true); 1541 } 1542 1543 /** 1544 * Set a template to pages 1545 * @param pageIds the id of pages to update 1546 * @param templateName The template name 1547 * @param checkAvailableTemplate true if you want to check available template 1548 * @return the id of pages which succeeded 1549 */ 1550 public Map<String, Object> setTemplate (List<String> pageIds, String templateName, boolean checkAvailableTemplate) 1551 { 1552 List<String> allRightIds = new ArrayList<>(); 1553 List<Map<String, Object>> noRightPages = new ArrayList<>(); 1554 List<Map<String, Object>> errorPages = new ArrayList<>(); 1555 List<Map<String, Object>> noModifiablePages = new ArrayList<>(); 1556 1557 String defaultZoneName = null; 1558 1559 for (String pageId : pageIds) 1560 { 1561 Page page = _resolver.resolveById(pageId); 1562 1563 if (_rightManager.currentUserHasRight("Web_Rights_Page_Templates", page) != RightResult.RIGHT_ALLOW) 1564 { 1565 noRightPages.add(Map.of("id", pageId, "title", page.getTitle())); 1566 } 1567 else if (!(page instanceof ModifiablePage)) 1568 { 1569 noModifiablePages.add(Map.of("id", pageId, "title", page.getTitle())); 1570 } 1571 else 1572 { 1573 ModifiablePage mPage = (ModifiablePage) page; 1574 1575 if (defaultZoneName == null) 1576 { 1577 String skinId = page.getSite().getSkinId(); 1578 SkinTemplate tpl = _skinsManager.getSkin(skinId).getTemplate(templateName); 1579 if (tpl == null) 1580 { 1581 throw new IllegalStateException("Template '" + templateName + "' does not exist on skin '" + skinId + "'"); 1582 } 1583 1584 defaultZoneName = tpl.getDefaultZoneId(); 1585 } 1586 1587 if (checkAvailableTemplate && !_templatesHandler.getAvailablesTemplates(mPage).contains(templateName)) 1588 { 1589 throw new IllegalStateException("Template '" + templateName + "' is not available for page '" + pageId + "'"); 1590 } 1591 1592 if (page.getType().equals(PageType.CONTAINER)) 1593 { 1594 _removeOldZones (mPage, templateName); 1595 } 1596 1597 mPage.setTemplate(templateName); 1598 mPage.setType(PageType.CONTAINER); 1599 mPage.getSitemap().saveChanges(); 1600 1601 allRightIds.add(pageId); 1602 1603 Map<String, Object> eventParams = new HashMap<>(); 1604 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1605 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 1606 } 1607 1608 } 1609 1610 Map<String, Object> results = new HashMap<>(); 1611 results.put("allright-pages", allRightIds); 1612 results.put("noright-pages", noRightPages); 1613 results.put("error-pages", errorPages); 1614 results.put("nomodifiable-pages", noModifiablePages); 1615 1616 if (defaultZoneName != null) 1617 { 1618 results.put("zonename", defaultZoneName); 1619 } 1620 return results; 1621 } 1622 1623 /** 1624 * Get the script class name to execute to add or update this service 1625 * @param serviceId the service id 1626 * @return the script class name. Can be empty 1627 */ 1628 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1629 public String getServiceParametersAction (String serviceId) 1630 { 1631 try 1632 { 1633 Service service = _serviceExtensionPoint.getExtension(serviceId); 1634 return service.getParametersScript().getScriptClassname(); 1635 } 1636 catch (IllegalArgumentException e) 1637 { 1638 throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e); 1639 } 1640 } 1641 1642 private void _removeOldZones (ModifiablePage page, String templateName) 1643 { 1644 String skinId = page.getSite().getSkinId(); 1645 1646 SkinTemplate oldTemplate = _skinsManager.getSkin(skinId).getTemplate(templateName); 1647 1648 Map<String, SkinTemplateZone> templateZones = oldTemplate.getZones(); 1649 1650 for (ModifiableZone zone : page.getZones()) 1651 { 1652 if (!templateZones.containsKey(zone.getName())) 1653 { 1654 zone.remove(); 1655 } 1656 } 1657 } 1658 1659 /** 1660 * Get the tags from the pages 1661 * @param pageIds The ids of the pages 1662 * @return the tags of the pages 1663 */ 1664 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 1665 public Set<String> getTags (List<String> pageIds) 1666 { 1667 Set<String> tags = new HashSet<>(); 1668 1669 for (String pageId : pageIds) 1670 { 1671 Page page = _resolver.resolveById(pageId); 1672 if (_rightManager.currentUserHasReadAccess(page)) 1673 { 1674 tags.addAll(page.getTags()); 1675 } 1676 } 1677 1678 return tags; 1679 } 1680 1681 /** 1682 * Tag a list of pages 1683 * @param pageIds The ids of pages to tag 1684 * @param tagNames The tags 1685 * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 1686 * @param contextualParameters Contextual parameters. Must contain the site name 1687 * @return the result 1688 */ 1689 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 1690 public Map<String, Object> tag (List<String> pageIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters) 1691 { 1692 return tag(pageIds, tagNames, TagMode.valueOf(mode), contextualParameters, false); 1693 } 1694 1695 /** 1696 * Tag a list of pages 1697 * @param pageIds The ids of pages to tag 1698 * @param tagNames The tags 1699 * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 1700 * @param contextualParameters Contextual parameters. Must contain the site name 1701 * @param ignoreRights <code>true</code> to ignore rights on tag 1702 * @return the result 1703 */ 1704 public Map<String, Object> tag (List<String> pageIds, List<String> tagNames, TagMode mode, Map<String, Object> contextualParameters, boolean ignoreRights) 1705 { 1706 Map<String, Object> result = new HashMap<>(); 1707 1708 result.put("nomodifiable-pages", new ArrayList<>()); 1709 result.put("invalid-tags", new ArrayList<>()); 1710 result.put("allright-pages", new ArrayList<>()); 1711 result.put("noright-pages", new ArrayList<>()); 1712 1713 for (String pageId : pageIds) 1714 { 1715 Page page = _resolver.resolveById(pageId); 1716 1717 Map<String, Object> page2json = new HashMap<>(); 1718 page2json.put("id", page.getId()); 1719 page2json.put("title", page.getTitle()); 1720 1721 if (!ignoreRights && !_hasTagRights(page, tagNames, mode, contextualParameters)) 1722 { 1723 @SuppressWarnings("unchecked") 1724 List<Map<String, Object>> noRightPages = (List<Map<String, Object>>) result.get("noright-pages"); 1725 noRightPages.add(page2json); 1726 } 1727 else 1728 { 1729 if (page instanceof ModifiablePage mPage) 1730 { 1731 @SuppressWarnings("unchecked") 1732 List<String> invalidTags = (List<String>) result.get("invalid-tags"); 1733 invalidTags.addAll(tag(mPage, tagNames, mode)); 1734 1735 page2json.put("tags", page.getTags()); 1736 @SuppressWarnings("unchecked") 1737 List<Map<String, Object>> allRightPages = (List<Map<String, Object>>) result.get("allright-pages"); 1738 allRightPages.add(page2json); 1739 } 1740 else 1741 { 1742 @SuppressWarnings("unchecked") 1743 List<Map<String, Object>> nomodifiablePages = (List<Map<String, Object>>) result.get("nomodifiable-pages"); 1744 nomodifiablePages.add(page2json); 1745 } 1746 } 1747 } 1748 1749 return result; 1750 } 1751 1752 private boolean _hasTagRights(Page page, List<String> tagNames, TagMode mode, Map<String, Object> contextualParameters) 1753 { 1754 List<CMSTag> tags = tagNames.stream() 1755 .map(t -> _tagProvider.getTag(t, contextualParameters)) 1756 .filter(Objects::nonNull) 1757 .toList(); 1758 1759 // In case of replace, only check the right on tag that are modified, ie added or removed tags 1760 if (TagMode.REPLACE.equals(mode)) 1761 { 1762 List<CMSTag> existingTags = page.getTags().stream() 1763 .map(t -> _tagProvider.getTag(t, contextualParameters)) 1764 .filter(Objects::nonNull) 1765 .toList(); 1766 1767 tags = new ArrayList<>(CollectionUtils.disjunction(tags, existingTags)); 1768 } 1769 1770 boolean hasPublicTagRight = _rightManager.currentUserHasRight("Web_Rights_Page_Tag", page) == RightResult.RIGHT_ALLOW; 1771 boolean hasPrivateTagRight = _rightManager.currentUserHasRight("Web_Rights_Page_Private_Tag", page) == RightResult.RIGHT_ALLOW; 1772 1773 // Test if the current user has the right to tag public tag on page only if there are at least one public tag 1774 boolean hasRight = TagHelper.filterTags(tags, TagVisibility.PUBLIC, "PAGE").isEmpty() || hasPublicTagRight || hasPrivateTagRight; 1775 1776 // Test if the current user has the right to tag private tag on page only if there are at least one private tag 1777 return hasRight && (TagHelper.filterTags(tags, TagVisibility.PRIVATE, "PAGE").isEmpty() || hasPrivateTagRight); 1778 } 1779 1780 /** 1781 * Tag a page 1782 * @param page The page to tag 1783 * @param tagNames The tags 1784 * @param tagMode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 1785 * @return the list of invalid tags that were not set 1786 */ 1787 public List<String> tag(ModifiablePage page, List<String> tagNames, TagMode tagMode) 1788 { 1789 List<String> invalidTags = new ArrayList<>(); 1790 1791 Set<String> oldTags = page.getTags(); 1792 if (TagMode.REPLACE.equals(tagMode)) 1793 { 1794 // First delete old tags 1795 for (String tagName : oldTags) 1796 { 1797 page.untag(tagName); 1798 } 1799 } 1800 1801 // Then set new tags 1802 for (String tagName : tagNames) 1803 { 1804 if (_isTagValid(page, tagName)) 1805 { 1806 if (TagMode.REMOVE.equals(tagMode)) 1807 { 1808 page.untag(tagName); 1809 } 1810 else if (TagMode.REPLACE.equals(tagMode) || !oldTags.contains(tagName)) 1811 { 1812 page.tag(tagName); 1813 } 1814 } 1815 else 1816 { 1817 invalidTags.add(tagName); 1818 } 1819 } 1820 1821 page.saveChanges(); 1822 1823 if (!oldTags.equals(page.getTags())) 1824 { 1825 // Notify observers that the page has been tagged 1826 Map<String, Object> eventParams = new HashMap<>(); 1827 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1828 eventParams.put(ObservationConstants.ARGS_PAGE_TAGS, page.getTags()); 1829 eventParams.put(ObservationConstants.ARGS_PAGE_OLD_TAGS, oldTags); 1830 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1831 } 1832 1833 return invalidTags; 1834 } 1835 1836 /** 1837 * Tag a list of contents with the given tags 1838 * @param pageIds The ids of pages to tag 1839 * @param contentIds The ids of contents to tag 1840 * @param tagNames The tags 1841 * @param contextualParameters The contextual parameters 1842 * @return the result map 1843 */ 1844 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 1845 public Map<String, Object> tag (List<String> pageIds, List<String> contentIds, List<String> tagNames, Map<String, Object> contextualParameters) 1846 { 1847 return tag(pageIds, contentIds, tagNames, TagMode.REPLACE.toString(), contextualParameters); 1848 } 1849 1850 /** 1851 * Tag a list of contents and/org pages 1852 * @param pageIds The ids of pages to tag 1853 * @param contentIds The ids of contents to tag 1854 * @param tagNames The tags 1855 * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 1856 * @param contextualParameters The contextual parameters 1857 * @return the result 1858 */ 1859 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 1860 public Map<String, Object> tag (List<String> pageIds, List<String> contentIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters) 1861 { 1862 TagMode tagMode = TagMode.valueOf(mode); 1863 1864 // Tag pages 1865 Map<String, Object> result = tag(pageIds, tagNames, tagMode, contextualParameters, false); 1866 1867 // Tag contents 1868 result.putAll(_contentDAO.tag(contentIds, tagNames, tagMode, contextualParameters, false)); 1869 1870 // Invalid tags are ignored 1871 result.remove("invalid-tags"); 1872 return result; 1873 } 1874 1875 /** 1876 * Test if a tag is valid for a specific page 1877 * @param page The page 1878 * @param tagName The tag name 1879 * @return True if the tag is valid 1880 */ 1881 public boolean _isTagValid (Page page, String tagName) 1882 { 1883 Map<String, Object> params = new HashMap<>(); 1884 params.put("siteName", page.getSiteName()); 1885 CMSTag tag = _tagProvider.getTag(tagName, params); 1886 1887 return tag != null && tag.getTarget().getName().equals("PAGE"); 1888 } 1889 1890 /** 1891 * Returns the ids of the pages matching the tag 1892 * @param sitename The site id 1893 * @param lang The language code 1894 * @param tag The tag id 1895 * @return Array of pages ids 1896 */ 1897 public List<String> findPagedIdsByTag(String sitename, String lang, String tag) 1898 { 1899 return _getMemoryPageTagCache().get(PageTagCacheKey.of(sitename, lang, tag), __ -> _computePagesIds(sitename, lang, tag)) 1900 .stream() 1901 .filter(_resolver::hasAmetysObjectForId) // Cache remember for tag in default, we have to check if page exists in current workspace 1902 .collect(Collectors.toList()); 1903 } 1904 1905 private List<String> _computePagesIds(String sitename, String lang, String tag) 1906 { 1907 1908 Session defaultSession = null; 1909 try 1910 { 1911 List<String> pagesIds = new ArrayList<>(); 1912 1913 // Force default workspace to execute query 1914 defaultSession = _repository.login(RepositoryConstants.DEFAULT_WORKSPACE); 1915 1916 String xpath = PageQueryHelper.getPageXPathQuery(sitename, lang, null, new TagExpression(Operator.EQ, tag), null); 1917 AmetysObjectIterable<Page> pages = _ametysObjectResolver.query(xpath, defaultSession); 1918 Iterator<Page> it = pages.iterator(); 1919 1920 while (it.hasNext()) 1921 { 1922 pagesIds.add(it.next().getId()); 1923 } 1924 1925 return pagesIds; 1926 } 1927 catch (RepositoryException e) 1928 { 1929 throw new AmetysRepositoryException(e); 1930 } 1931 finally 1932 { 1933 if (defaultSession != null) 1934 { 1935 defaultSession.logout(); 1936 } 1937 } 1938 } 1939 1940 private Cache<PageTagCacheKey, List<String>> _getMemoryPageTagCache() 1941 { 1942 return _cacheManager.get(MEMORY_PAGESTAGCACHE); 1943 } 1944 1945 /** 1946 * Set the visible of pages 1947 * @param pageIds The id of pages 1948 * @param visible <code>true</code> to set pages as visible, <code>false</code> otherwise 1949 * @return The result map 1950 */ 1951 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 1952 public Map<String, Object> setVisibility (List<String> pageIds, boolean visible) 1953 { 1954 Map<String, Object> result = new HashMap<>(); 1955 1956 result.put("nomodifiable-pages", new ArrayList<>()); 1957 result.put("noright-pages", new ArrayList<>()); 1958 result.put("allright-pages", new ArrayList<>()); 1959 1960 for (String id : pageIds) 1961 { 1962 Page page = _resolver.resolveById(id); 1963 1964 Map<String, Object> page2json = new HashMap<>(); 1965 page2json.put("id", page.getId()); 1966 page2json.put("title", page.getTitle()); 1967 1968 if (_rightManager.currentUserHasRight("Web_Right_Page_Visibility", page) != RightResult.RIGHT_ALLOW) 1969 { 1970 @SuppressWarnings("unchecked") 1971 List<Map<String, Object>> norightPages = (List<Map<String, Object>>) result.get("noright-pages"); 1972 norightPages.add(page2json); 1973 } 1974 else if (page instanceof ModifiablePage) 1975 { 1976 ModifiablePage mPage = (ModifiablePage) page; 1977 mPage.setVisible(visible); 1978 mPage.saveChanges(); 1979 1980 Map<String, Object> eventParams = new HashMap<>(); 1981 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1982 eventParams.put(ObservationConstants.ARGS_PAGE_ID, page.getId()); 1983 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1984 1985 @SuppressWarnings("unchecked") 1986 List<Map<String, Object>> allRightPages = (List<Map<String, Object>>) result.get("allright-pages"); 1987 allRightPages.add(page2json); 1988 } 1989 else 1990 { 1991 @SuppressWarnings("unchecked") 1992 List<Map<String, Object>> nomodifiablePages = (List<Map<String, Object>>) result.get("nomodifiable-pages"); 1993 nomodifiablePages.add(page2json); 1994 } 1995 } 1996 1997 return result; 1998 } 1999 2000 /** 2001 * Get the contents of a {@link Page} and its subpages which can be deleted. 2002 * A content is deleteable if user has right, the content is not locked and not referenced by other pages. 2003 * @param id The id of page 2004 * @return The list of deletable contents 2005 */ 2006 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 2007 public Map<String, Object> getDeleteablePageContents (String id) 2008 { 2009 Map<String, Object> results = new HashMap<>(); 2010 2011 results.put("deleteable-contents", new ArrayList<>()); 2012 results.put("referenced-contents", new ArrayList<>()); 2013 results.put("unauthorized-contents", new ArrayList<>()); 2014 results.put("locked-contents", new ArrayList<>()); 2015 2016 Page page = _resolver.resolveById(id); 2017 PageHierarchy pageHierarchy = _getPageContents(page, true); 2018 2019 for (Content content : pageHierarchy.contents()) 2020 { 2021 Map<String, Object> contentParams = new HashMap<>(); 2022 contentParams.put("id", content.getId()); 2023 contentParams.put("title", content.getTitle(new Locale(page.getSitemapName()))); 2024 contentParams.put("name", content.getName()); 2025 2026 if (_isReferenced(content, pageHierarchy.pages())) 2027 { 2028 // Content is referenced by at least another page 2029 @SuppressWarnings("unchecked") 2030 List<Map<String, Object>> referencedContents = (List<Map<String, Object>>) results.get("referenced-contents"); 2031 referencedContents.add(contentParams); 2032 } 2033 else if (!_contentDAO.canDelete(content)) 2034 { 2035 @SuppressWarnings("unchecked") 2036 List<Map<String, Object>> unauthorizedContents = (List<Map<String, Object>>) results.get("unauthorized-contents"); 2037 unauthorizedContents.add(contentParams); 2038 } 2039 else if (_isLocked(content)) 2040 { 2041 // If the content is locked by other 2042 @SuppressWarnings("unchecked") 2043 List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents"); 2044 lockedContents.add(contentParams); 2045 } 2046 else 2047 { 2048 Map<String, Object> content2json = new HashMap<>(); 2049 content2json.put("id", content.getId()); 2050 content2json.put("name", content.getName()); 2051 content2json.put("title", content.getTitle(new Locale(page.getSitemapName()))); 2052 content2json.put("isNew", _isNew(content)); 2053 content2json.put("isShared", content instanceof SharedContent); 2054 content2json.put("hasShared", _sharedContentManager.hasSharedContents(content)); 2055 2056 @SuppressWarnings("unchecked") 2057 List<Map<String, Object>> allrightContents = (List<Map<String, Object>>) results.get("deleteable-contents"); 2058 allrightContents.add(content2json); 2059 } 2060 } 2061 2062 return results; 2063 } 2064 2065 /** 2066 * Get the contents that belong to the {@link Page} and its sub-pages and that can be deleted. 2067 * A content is deleteable if user has right, the content is not locked and it's not referenced by other pages. 2068 * If 'onlyNewlyCreatedContents' is set to 'true', only newly created contents will be returned 2069 * @param pageHierarchy Object containing the page and its sub pages, and all referencing contents in these pages 2070 * @param onlyNewlyCreatedContents true to return only the newly created contents 2071 * @return The ids of deleteable contents 2072 */ 2073 private List<String> _getDeleteablePageContentIds (PageHierarchy pageHierarchy, boolean onlyNewlyCreatedContents) 2074 { 2075 return pageHierarchy.contents() 2076 .stream() 2077 .filter(_contentDAO::canDelete) 2078 .filter(Predicate.not(this::_isLocked)) 2079 .filter(c -> !_isReferenced(c, pageHierarchy.pages())) 2080 .filter(c -> !onlyNewlyCreatedContents || _isNew(c)) 2081 .map(Content::getId) 2082 .toList(); 2083 } 2084 2085 /** 2086 * Get the unreferenced contents of a {@link Page} or a {@link ZoneItem} 2087 * @param id The id of page or zone item 2088 * @return The list of unreferenced contents 2089 */ 2090 @Callable (rights = Callable.READ_ACCESS, rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 2091 public List<Map<String, Object>> getUnreferencedContents (String id) 2092 { 2093 List<Map<String, Object>> unreferencedContents = new ArrayList<>(); 2094 2095 Page page = _resolver.resolveById(id); 2096 PageHierarchy pageHierarchy = _getPageContents(page, true); 2097 2098 for (Content content : pageHierarchy.contents()) 2099 { 2100 if (!_isReferenced(content, pageHierarchy.pages())) 2101 { 2102 Map<String, Object> content2json = new HashMap<>(); 2103 content2json.put("id", content.getId()); 2104 content2json.put("name", content.getName()); 2105 content2json.put("title", content.getTitle(new Locale(page.getSitemapName()))); 2106 content2json.put("isNew", _isNew(content)); 2107 content2json.put("isShared", content instanceof SharedContent); 2108 content2json.put("hasShared", _sharedContentManager.hasSharedContents(content)); 2109 2110 unreferencedContents.add(content2json); 2111 } 2112 } 2113 2114 return unreferencedContents; 2115 } 2116 2117 /** 2118 * Returns the page's attachments root node 2119 * @param id the page's id 2120 * @return The attachments' root node informations 2121 */ 2122 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 2123 public Map<String, Object> getAttachmentsRootNode (String id) 2124 { 2125 Map<String, Object> result = new HashMap<>(); 2126 2127 Page page = _resolver.resolveById(id); 2128 2129 result.put("title", page.getTitle()); 2130 result.put("pageId", page.getId()); 2131 2132 if (_rightManager.currentUserHasRight("Web_Rights_Page_Attachments", page) != RightResult.RIGHT_ALLOW 2133 && _rightManager.currentUserHasRight("Web_Rights_Page_AttachmentHandle", page) != RightResult.RIGHT_ALLOW) 2134 { 2135 return result; 2136 } 2137 2138 TraversableAmetysObject attachments = page.getRootAttachments(); 2139 2140 if (attachments != null) 2141 { 2142 result.put("id", attachments.getId()); 2143 if (attachments instanceof ModifiableAmetysObject) 2144 { 2145 result.put("isModifiable", true); 2146 } 2147 if (attachments instanceof ModifiableResourceCollection) 2148 { 2149 result.put("canCreateChild", true); 2150 } 2151 2152 boolean hasChildNodes = false; 2153 boolean hasResources = false; 2154 2155 for (AmetysObject child : attachments.getChildren()) 2156 { 2157 if (child instanceof Resource) 2158 { 2159 hasResources = true; 2160 } 2161 else if (child instanceof ExplorerNode) 2162 { 2163 hasChildNodes = true; 2164 } 2165 } 2166 2167 if (hasChildNodes) 2168 { 2169 result.put("hasChildNodes", true); 2170 } 2171 2172 if (hasResources) 2173 { 2174 result.put("hasResources", true); 2175 } 2176 2177 return result; 2178 } 2179 2180 throw new IllegalArgumentException("Page with id '" + id + "' does not support attachments."); 2181 } 2182 /** 2183 * Returns the page's parents ids 2184 * @param id the page's id 2185 * @return The attachments' root node informations 2186 */ 2187 @Callable (rights = Callable.SKIP_BUILTIN_CHECK) 2188 public Map<String, Object> getPageParents (String id) 2189 { 2190 Map<String, Object> result = new HashMap<>(); 2191 List<Map<String, Object>> pages = new ArrayList<>(); 2192 Page page = _resolver.resolveById(id); 2193 pages.add(_page2Json(page)); 2194 while (page.getParent() != null && page.getParent() instanceof Page) 2195 { 2196 page = page.getParent(); 2197 pages.add(_page2Json(page)); 2198 } 2199 result.put("parents", pages); 2200 return result; 2201 } 2202 2203 /** 2204 * Get the contents of a page and its child pages 2205 * @param sitemapElement The page 2206 * @return The list of contents 2207 */ 2208 public Set<Content> getPageContents (SitemapElement sitemapElement) 2209 { 2210 return _getPageContents(sitemapElement, false).contents(); 2211 } 2212 2213 /** 2214 * Get the contents of a page and its child pages 2215 * @param sitemapElement The page 2216 * @param ignoreContentsOfNonRemovablePage true to ignore contents of non-removable pages (virtual pages) 2217 * @return The page hierarchy with all pages, sub pages and referencing contents 2218 */ 2219 private PageHierarchy _getPageContents (SitemapElement sitemapElement, boolean ignoreContentsOfNonRemovablePage) 2220 { 2221 Set<Content> contents = new HashSet<>(); 2222 2223 if ((!ignoreContentsOfNonRemovablePage || sitemapElement instanceof RemovableAmetysObject) 2224 && sitemapElement.getTemplate() != null) 2225 { 2226 for (Zone zone : sitemapElement.getZones()) 2227 { 2228 try (AmetysObjectIterable< ? extends ZoneItem> zoneItems = zone.getZoneItems()) 2229 { 2230 for (ZoneItem zoneItem : zoneItems) 2231 { 2232 if (zoneItem.getType() == ZoneItem.ZoneType.CONTENT) 2233 { 2234 contents.add(zoneItem.getContent()); 2235 } 2236 } 2237 } 2238 } 2239 } 2240 2241 Set<Page> pages = new HashSet<>(); 2242 if (sitemapElement instanceof Page page) 2243 { 2244 pages.add(page); 2245 } 2246 2247 AmetysObjectIterable< ? extends Page> childrenPages = sitemapElement.getChildrenPages(); 2248 for (Page childPage : childrenPages) 2249 { 2250 PageHierarchy pageHierarchy = _getPageContents(childPage, ignoreContentsOfNonRemovablePage); 2251 pages.addAll(pageHierarchy.pages()); 2252 contents.addAll(pageHierarchy.contents()); 2253 } 2254 2255 return new PageHierarchy(pages, contents); 2256 } 2257 2258 /** 2259 * Get the user rights on sitemap element (page or sitemap) 2260 * @param pagesCt The sitemap element 2261 * @return The user's rights 2262 */ 2263 protected Set<String> getUserRights (SitemapElement pagesCt) 2264 { 2265 UserIdentity user = _currentUserProvider.getUser(); 2266 2267 Set<String> userRights = _rightManager.getUserRights(user, pagesCt); 2268 2269 // Do some specific stuff here, because the right 'Web_Rights_Page_Delete' is a right to delete child pages and not the page itself. 2270 // So the right should be checked on parent context. 2271 if (pagesCt instanceof Page) 2272 { 2273 SitemapElement parent = pagesCt.getParent(); 2274 boolean canDelete = _rightManager.hasRight(user, "Web_Rights_Page_Delete", parent) == RightResult.RIGHT_ALLOW; 2275 if (!canDelete) 2276 { 2277 // No right on parent page, so remove the right if exists. 2278 userRights.remove("Web_Rights_Page_Delete"); 2279 } 2280 } 2281 else 2282 { 2283 // There is no right of deletion on the sitemap 2284 userRights.remove("Web_Rights_Page_Delete"); 2285 } 2286 2287 return userRights; 2288 } 2289 2290 private boolean _isReferenced (Content content, Set<Page> pages) 2291 { 2292 if (content instanceof WebContent webContent) 2293 { 2294 // If only one page referencing the content is not inside the selected page hierarchy, it is a referenced content 2295 return webContent.getReferencingPages().stream().anyMatch(Predicate.not(pages::contains)); 2296 } 2297 2298 return false; 2299 } 2300 2301 private boolean _isLocked (Content content) 2302 { 2303 if (content instanceof LockableAmetysObject) 2304 { 2305 LockableAmetysObject lockableContent = (LockableAmetysObject) content; 2306 if (lockableContent.isLocked()) 2307 { 2308 boolean canUnlockAll = _rightManager.hasRight(_currentUserProvider.getUser(), "CMS_Rights_UnlockAll", "/cms") == RightResult.RIGHT_ALLOW; 2309 if (!LockHelper.isLockOwner(lockableContent, _currentUserProvider.getUser()) && !canUnlockAll) 2310 { 2311 return true; 2312 } 2313 } 2314 } 2315 2316 return false; 2317 } 2318 2319 private boolean _isNew (Content content) 2320 { 2321 boolean isNew = false; 2322 if (content instanceof WorkflowAwareContent) 2323 { 2324 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 2325 long workflowId = waContent.getWorkflowId(); 2326 2327 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 2328 isNew = workflow.getHistorySteps(workflowId).isEmpty(); 2329 } 2330 return isNew; 2331 } 2332 2333 private Map<String, Object> _page2Json (Page page) 2334 { 2335 Map<String, Object> page2json = new HashMap<>(); 2336 page2json.put("id", page.getId()); 2337 page2json.put("title", page.getTitle()); 2338 page2json.put("siteName", page.getSiteName()); 2339 page2json.put("path", page.getPathInSitemap()); 2340 return page2json; 2341 } 2342 2343 private Map<String, Object> _content2Json (Content content, Locale locale) 2344 { 2345 Map<String, Object> content2json = new HashMap<>(); 2346 content2json.put("id", content.getId()); 2347 content2json.put("title", content.getTitle(locale)); 2348 content2json.put("name", content.getName()); 2349 2350 List<Map<String, Object>> pages = new ArrayList<>(); 2351 if (content instanceof WebContent) 2352 { 2353 content2json.put("siteName", ((WebContent) content).getSiteName()); 2354 Collection<Page> refPages = ((WebContent) content).getReferencingPages(); 2355 for (Page refPage : refPages) 2356 { 2357 pages.add(_page2Json(refPage)); 2358 } 2359 content2json.put("pages", pages); 2360 } 2361 2362 return content2json; 2363 } 2364 2365 private Map<String, Object> _publication2Json (Page page) 2366 { 2367 Map<String, Object> pub2json = new HashMap<>(); 2368 2369 if (page.hasValue(DefaultPage.METADATA_PUBLICATION_START_DATE, ModelItemTypeConstants.DATETIME_TYPE_ID)) 2370 { 2371 Object startDateAsJSON = page.dataToJSON(DefaultPage.METADATA_PUBLICATION_START_DATE); 2372 pub2json.put("startDate", startDateAsJSON); 2373 } 2374 2375 if (page.hasValue(DefaultPage.METADATA_PUBLICATION_END_DATE, ModelItemTypeConstants.DATETIME_TYPE_ID)) 2376 { 2377 Object endDateAsJSON = page.dataToJSON(DefaultPage.METADATA_PUBLICATION_END_DATE); 2378 pub2json.put("endDate", endDateAsJSON); 2379 } 2380 2381 return pub2json; 2382 2383 } 2384 2385 private AmetysObjectIterable<Content> _getIncomingContentReferences (String pageId) 2386 { 2387 String xpathQuery = "//element(*, ametys:content)[ametys-internal:consistency/@ametys-internal:link = 'page:" + pageId + "']"; 2388 return _resolver.query(xpathQuery); 2389 } 2390 2391 private AmetysObjectIterable<Page> _getIncomingPageReferences (String pageId) 2392 { 2393 String xpathQuery = "//element(*, ametys:page)[@ametys-internal:type = 'LINK' and @ametys-internal:url= '" + pageId + "']"; 2394 return _resolver.query(xpathQuery); 2395 } 2396 2397 private void _updateContentsAfterCopy(Page initialPage, Page createdPage) throws AmetysRepositoryException 2398 { 2399 for (Zone zone : createdPage.getZones()) 2400 { 2401 Zone initialZone = initialPage.getZone(zone.getName()); 2402 try (AmetysObjectIterable< ? extends ZoneItem> zoneItems = zone.getZoneItems(); AmetysObjectIterable< ? extends ZoneItem> initialZoneItems = initialZone.getZoneItems()) 2403 { 2404 AmetysObjectIterator<? extends ZoneItem> iterator = zoneItems.iterator(); 2405 AmetysObjectIterator< ? extends ZoneItem> initialIterator = initialZoneItems.iterator(); 2406 if (iterator.getSize() != initialIterator.getSize()) 2407 { 2408 throw new IllegalStateException("An error occured during the copy of " + initialPage.getName() + " (" + initialPage.getId() + "). The resulting page have a different number of zoneItems."); 2409 } 2410 while (iterator.hasNext()) 2411 { 2412 ZoneItem zoneItem = iterator.next(); 2413 ZoneItem initialZoneItem = initialIterator.next(); 2414 if (zoneItem.getType().equals(ZoneType.CONTENT)) 2415 { 2416 Content content = zoneItem.getContent(); 2417 WebContent initialContent = initialZoneItem.getContent(); 2418 2419 // Updaters 2420 Set<String> ids = _copyUpdaterEP.getExtensionsIds(); 2421 for (String id : ids) 2422 { 2423 _copyUpdaterEP.getExtension(id).updateContent(initialPage.getSite(), createdPage.getSite(), initialContent, content); 2424 } 2425 2426 // Convert content language if necessary 2427 if (content instanceof ModifiableContent && content.getLanguage() != null && content.getLanguage() != createdPage.getSitemapName()) 2428 { 2429 ((ModifiableContent) content).setLanguage(createdPage.getSitemapName()); 2430 ((ModifiableContent) content).saveChanges(); 2431 } 2432 2433 // Create the first version 2434 if (content instanceof VersionableAmetysObject) 2435 { 2436 ((VersionableAmetysObject) content).checkpoint(); 2437 } 2438 } 2439 } 2440 } 2441 } 2442 2443 // Browse child pages 2444 for (Page childPage : createdPage.getChildrenPages()) 2445 { 2446 Page initialChildPage = initialPage.getChild(childPage.getName()); 2447 _updateContentsAfterCopy (initialChildPage, childPage); 2448 } 2449 } 2450 2451 private List<String> _getChildrenPageIds (Page page) 2452 { 2453 List<String> childIds = new ArrayList<>(); 2454 2455 for (Page childPage : page.getChildrenPages()) 2456 { 2457 childIds.add(childPage.getId()); 2458 childIds.addAll(_getChildrenPageIds(childPage)); 2459 } 2460 2461 return childIds; 2462 } 2463 2464 /** 2465 * Check each parent and return true if one of them is invisible 2466 * @param page page to check 2467 * @return true if at least one parent is invisible 2468 */ 2469 private boolean _isParentInvisible (Page page) 2470 { 2471 AmetysObject parent = page.getParent(); 2472 while (parent != null && parent instanceof Page) 2473 { 2474 boolean invisible = !((Page) parent).isVisible(); 2475 if (invisible) 2476 { 2477 return true; 2478 } 2479 parent = parent.getParent(); 2480 } 2481 return false; 2482 } 2483 2484 /* start of a group of methods for _isPreviewable */ 2485 /** 2486 * Determine if this page is previewable 2487 * @param page The page to look at 2488 * @return true if the page can be previewed 2489 */ 2490 private boolean _isPreviewable(Page page) 2491 { 2492 // Check for infinitive loop redirection 2493 ArrayList<String> pagesSequence = new ArrayList<>(); 2494 pagesSequence.add(page.getId()); 2495 if (_isInfiniteRedirection (page, pagesSequence)) 2496 { 2497 getLogger().error("An infinite loop redirection was detected for page '" + page.getPathInSitemap() + "'"); 2498 return false; 2499 } 2500 2501 if (page.getType() == PageType.LINK && LinkType.PAGE.equals(page.getURLType())) 2502 { 2503 return _isPageExist(page.getURL()) && _isPreviewable((Page) _resolver.resolveById(page.getURL())); 2504 } 2505 2506 if (page.getType() != PageType.NODE) 2507 { 2508 return true; 2509 } 2510 else 2511 { 2512 try (AmetysObjectIterable< ? extends Page> childrenPages = page.getChildrenPages()) 2513 { 2514 for (Page subPage : childrenPages) 2515 { 2516 if (_isPreviewable(subPage)) 2517 { 2518 return true; 2519 } 2520 } 2521 } 2522 } 2523 return false; 2524 } 2525 2526 private boolean _isPageExist (String id) 2527 { 2528 try 2529 { 2530 _resolver.resolveById(id); 2531 return true; 2532 } 2533 catch (UnknownAmetysObjectException e) 2534 { 2535 return false; 2536 } 2537 } 2538 2539 private boolean _isInfiniteRedirection (Page page, List<String> pagesSequence) 2540 { 2541 Page redirectPage = _getPageRedirection (page); 2542 if (redirectPage == null) 2543 { 2544 return false; 2545 } 2546 2547 if (pagesSequence.contains(redirectPage.getId())) 2548 { 2549 return true; 2550 } 2551 2552 pagesSequence.add(redirectPage.getId()); 2553 return _isInfiniteRedirection (redirectPage, pagesSequence); 2554 } 2555 2556 private Page _getPageRedirection (Page page) 2557 { 2558 if (PageType.LINK.equals(page.getType()) && LinkType.PAGE.equals(page.getURLType())) 2559 { 2560 try 2561 { 2562 String pageId = page.getURL(); 2563 return _resolver.resolveById(pageId); 2564 } 2565 catch (AmetysRepositoryException e) 2566 { 2567 return null; 2568 } 2569 } 2570 else if (PageType.NODE.equals(page.getType())) 2571 { 2572 AmetysObjectIterable<? extends Page> childPages = page.getChildrenPages(); 2573 Iterator<? extends Page> it = childPages.iterator(); 2574 if (it.hasNext()) 2575 { 2576 return it.next(); 2577 } 2578 } 2579 2580 return null; 2581 } 2582 /* end of a group of methods for _isPreviewable */ 2583 2584 private static final class PageTagCacheKey extends AbstractCacheKey 2585 { 2586 private PageTagCacheKey(String sitename, String lang, String tag) 2587 { 2588 super(sitename, lang, tag); 2589 } 2590 2591 private PageTagCacheKey(String sitename, String lang) 2592 { 2593 super(sitename, lang); 2594 } 2595 2596 static PageTagCacheKey of(String sitename, String lang) 2597 { 2598 return new PageTagCacheKey(sitename, lang, null); 2599 } 2600 2601 static PageTagCacheKey of(String sitename, String lang, String tag) 2602 { 2603 return new PageTagCacheKey(sitename, lang, tag); 2604 } 2605 } 2606 2607 private record PageHierarchy(Set<Page> pages, Set<Content> contents) { /* empty */ } 2608}