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