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