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