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