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