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