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 // in case of reorder, check right of creation on current parent page 733 if (!_canCreate(srcParent)) 734 { 735 throw new IllegalStateException("You do not have to reorder a page under '/" + srcParent.getSitemapName() + "/" + srcParent.getPathInSitemap() + "'"); 736 } 737 738 Page brother = srcParent.getChildPageAt(index); 739 ((MoveablePage) page).orderBefore(brother); 740 } 741 catch (UnknownAmetysObjectException e) 742 { 743 // Move the last child position 744 ((MoveablePage) page).orderBefore(null); 745 } 746 747 // Path is not modified 748 newPathInSitemap = oldPathInSitemap; 749 } 750 else 751 { 752 SitemapElement newParentPage = _resolver.resolveById(parentId); 753 754 // check right of creation on new parent page 755 if (!_canCreate(newParentPage)) 756 { 757 throw new IllegalStateException("You do not have the rights to create a page under '/" + newParentPage.getSitemapName() + "/" + newParentPage.getPathInSitemap() + "'"); 758 } 759 760 ((MoveablePage) page).moveTo(newParentPage, true); 761 if (index != -1) 762 { 763 Page brother = newParentPage.getChildPageAt(index); 764 765 ((MoveablePage) page).orderBefore(brother); 766 } 767 768 // Path is modified 769 newPathInSitemap = page.getPathInSitemap(); 770 } 771 772 if (sitemap.needsSave()) 773 { 774 sitemap.saveChanges(); 775 } 776 777 // Notify observers that the page has been moved 778 Map<String, Object> eventParams = new HashMap<>(); 779 eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap); 780 eventParams.put(ObservationConstants.ARGS_PAGE, page); 781 eventParams.put("page.old.path", oldPathInSitemap); 782 eventParams.put("page.old.parent", srcParent); 783 eventParams.put(ObservationConstants.ARGS_PAGE_PATH, newPathInSitemap); 784 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_MOVED, _currentUserProvider.getUser(), eventParams)); 785 786 result.put("id", page.getId()); 787 result.put("parentId", page.getParent().getId()); 788 789 return result; 790 } 791 792 private boolean _canCreate(SitemapElement parentPage) 793 { 794 UserIdentity user = _currentUserProvider.getUser(); 795 if (_rightManager.hasRight(user, "Web_Rights_Page_Create", parentPage) == RightResult.RIGHT_ALLOW) 796 { 797 return true; 798 } 799 800 if (getLogger().isInfoEnabled()) 801 { 802 getLogger().info("The user '" + user + "' tried to create page under '/" + parentPage.getSitemapName() + "/" + parentPage.getPathInSitemap() + "' without sufficient rights"); 803 } 804 805 return false; 806 } 807 808 private boolean _canDelete(Page page) 809 { 810 UserIdentity user = _currentUserProvider.getUser(); 811 SitemapElement parent = page.getParent(); 812 if (_rightManager.hasRight(user, "Web_Rights_Page_Delete", parent) == RightResult.RIGHT_ALLOW) 813 { 814 return true; 815 } 816 817 if (getLogger().isInfoEnabled()) 818 { 819 getLogger().info("The user '" + user + "' tried to move page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "' without sufficient rights"); 820 } 821 822 return false; 823 } 824 825 /** 826 * Set pages as redirection 827 * @param pageIds the id of pages to modify 828 * @param url the url of redirection 829 * @param urlType the type of redirection 830 * @return the id of pages which succeeded or failed. 831 */ 832 @Callable 833 public Map<String, Object> setLink (List<String> pageIds, String url, String urlType) 834 { 835 Map<String, Object> result = new HashMap<>(); 836 837 if (StringUtils.isEmpty(url)) 838 { 839 throw new IllegalArgumentException("Can not set page as a redirection with an empty url"); 840 } 841 842 List<String> successes = new ArrayList<>(); 843 List<Map<String, Object>> failures = new ArrayList<>(); 844 845 for (String pageId : pageIds) 846 { 847 try 848 { 849 Page page = _resolver.resolveById(pageId); 850 if (!(page instanceof ModifiablePage)) 851 { 852 throw new IllegalArgumentException("Can not set page as a redirection on a non-modifiable page " + pageId); 853 } 854 855 ModifiablePage mPage = (ModifiablePage) page; 856 857 if (page.getType().equals(PageType.CONTAINER)) 858 { 859 // Remove zones 860 for (ModifiableZone zone : mPage.getZones()) 861 { 862 zone.remove(); 863 } 864 } 865 866 if (pageId.equals(url)) 867 { 868 throw new IllegalArgumentException("A page can not redirect to itself"); 869 } 870 871 mPage.setType(PageType.LINK); 872 mPage.setURL(LinkType.valueOf(urlType), url); 873 mPage.getSitemap().saveChanges(); 874 875 successes.add(pageId); 876 877 Map<String, Object> eventParams = new HashMap<>(); 878 eventParams.put(ObservationConstants.ARGS_PAGE, page); 879 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 880 } 881 catch (Exception e) 882 { 883 getLogger().error("Cannot set the page '" + pageId + "' as link [" + url + ", " + urlType.toString() + "]", e); 884 885 Map<String, Object> failure = new HashMap<>(); 886 failure.put("id", pageId); 887 failure.put("error", e.toString()); 888 failures.add(failure); 889 } 890 } 891 892 result.put("success", successes); 893 result.put("failure", failures); 894 895 return result; 896 } 897 898 /** 899 * Get available template for specified page 900 * @param pageId The page's id 901 * @return the list of available template 902 */ 903 @Callable 904 public List<Map<String, Object>> getAvailableTemplates (String pageId) 905 { 906 List<Map<String, Object>> templates = new ArrayList<>(); 907 908 Page page = _resolver.resolveById(pageId); 909 910 Set<String> availableTemplateIds = _templatesHandler.getAvailablesTemplates(page); 911 for (String templateName : availableTemplateIds) 912 { 913 String skinId = page.getSite().getSkinId(); 914 Skin skin = _skinsManager.getSkin(skinId); 915 916 SkinTemplate template = skin.getTemplate(templateName); 917 918 Map<String, Object> template2json = new HashMap<>(); 919 template2json.put("id", template.getId()); 920 template2json.put("label", template.getLabel()); 921 template2json.put("description", template.getDescription()); 922 template2json.put("iconSmall", template.getSmallImage()); 923 template2json.put("iconMedium", template.getMediumImage()); 924 template2json.put("iconLarge", template.getLargeImage()); 925 template2json.put("zone", template.getDefaultZoneId()); 926 927 templates.add(template2json); 928 } 929 930 return templates; 931 } 932 933 /** 934 * Get available content types for specified page 935 * @param pageId The page's id 936 * @param zoneName the name of the zone 937 * @return the list of available content types 938 */ 939 @Callable 940 public List<Map<String, Object>> getAvailableContentTypes (String pageId, String zoneName) 941 { 942 List<Map<String, Object>> contenttypes = new ArrayList<>(); 943 944 Page page = _resolver.resolveById(pageId); 945 946 Set<String> contentTypeIds = _cTypeHandler.getAvailableContentTypes(page, zoneName); 947 for (String contentTypeId : contentTypeIds) 948 { 949 ContentType cType = _contentTypeExtensionPoint.getExtension(contentTypeId); 950 951 if (cType != null && _hasRight(cType, page)) 952 { 953 Map<String, Object> ctype2json = new HashMap<>(); 954 ctype2json.put("id", cType.getId()); 955 ctype2json.put("label", cType.getLabel()); 956 ctype2json.put("description", cType.getDescription()); 957 ctype2json.put("iconGlyph", cType.getIconGlyph()); 958 ctype2json.put("iconDecorator", cType.getIconDecorator()); 959 ctype2json.put("iconSmall", cType.getSmallIcon()); 960 ctype2json.put("iconMedium", cType.getMediumIcon()); 961 ctype2json.put("iconLarge", cType.getLargeIcon()); 962 ctype2json.put("defaultTitle", cType.getDefaultTitle()); 963 ctype2json.put("viewNames", cType.getViewNames(true)); 964 ctype2json.put("category", cType.getCategory().isI18n() ? cType.getCategory().getKey() : cType.getCategory().getLabel()); 965 ctype2json.put("categoryLabel", cType.getCategory()); 966 967 contenttypes.add(ctype2json); 968 } 969 } 970 971 return contenttypes; 972 } 973 974 /** 975 * Get available content types for a page being created 976 * @param pageId The page's id. Can be null of the page is not yet created 977 * @param zoneName the name of the zone 978 * @param parentId The id of parent page 979 * @param pageTitle The title of page to create 980 * @param template The template of page to create 981 * @return the list of available services 982 */ 983 @Callable 984 public List<Map<String, Object>> getAvailableContentTypesForCreation(String pageId, String zoneName, String parentId, String pageTitle, String template) 985 { 986 if (StringUtils.isNotEmpty(pageId)) 987 { 988 // Get available services for a page 989 return getAvailableContentTypes(pageId, zoneName); 990 } 991 else if (StringUtils.isNotEmpty(parentId)) 992 { 993 // Get available services for a not yet existing page 994 SitemapElement parent = _resolver.resolveById(parentId); 995 996 // Create page temporarily 997 Page page = _createPage(parent, pageTitle, template); 998 999 List<Map<String, Object>> availableContentTypes = getAvailableContentTypes(page.getId(), zoneName); 1000 1001 // Cancel page creation 1002 page.getSitemap().revertChanges(); 1003 1004 return availableContentTypes; 1005 } 1006 1007 return Collections.EMPTY_LIST; 1008 } 1009 1010 private boolean _hasRight(ContentType contentType, Page page) 1011 { 1012 String right = contentType.getRight(); 1013 1014 if (right == null) 1015 { 1016 return true; 1017 } 1018 else 1019 { 1020 UserIdentity user = _currentUserProvider.getUser(); 1021 return _rightManager.hasRight(user, right, page) == RightResult.RIGHT_ALLOW; 1022 } 1023 } 1024 1025 /** 1026 * Get available services for specified page 1027 * @param pageId The page's id 1028 * @param zoneName the name of the zone 1029 * @return the list of available services 1030 */ 1031 @Callable 1032 public List<Map<String, Object>> getAvailableServices (String pageId, String zoneName) 1033 { 1034 List<Map<String, Object>> services = new ArrayList<>(); 1035 1036 SitemapElement sitemapElement = _resolver.resolveById(pageId); 1037 1038 Set<String> serviceIds = _serviceHandler.getAvailableServices(sitemapElement, zoneName); 1039 for (String serviceId : serviceIds) 1040 { 1041 Service service = _serviceExtensionPoint.getExtension(serviceId); 1042 if (service != null && _hasRight(service, sitemapElement)) 1043 { 1044 Map<String, Object> serviceMap = new HashMap<>(); 1045 serviceMap.put("id", service.getId()); 1046 serviceMap.put("label", service.getLabel()); 1047 serviceMap.put("description", service.getDescription()); 1048 serviceMap.put("iconGlyph", service.getIconGlyph()); 1049 serviceMap.put("iconDecorator", service.getIconDecorator()); 1050 serviceMap.put("iconSmall", service.getSmallIcon()); 1051 serviceMap.put("iconMedium", service.getMediumIcon()); 1052 serviceMap.put("iconLarge", service.getLargeIcon()); 1053 serviceMap.put("parametersAction", service.getParametersScript().getScriptClassname()); 1054 serviceMap.put("category", service.getCategory().isI18n() ? service.getCategory().getKey() : service.getCategory().getLabel()); 1055 serviceMap.put("categoryLabel", service.getCategory()); 1056 1057 services.add(serviceMap); 1058 } 1059 } 1060 1061 return services; 1062 } 1063 1064 /** 1065 * Get available services for a page being created 1066 * @param pageId The page's id. Can be null of the page is not yet created 1067 * @param zoneName the name of the zone 1068 * @param parentId The id of parent page 1069 * @param pageTitle The title of page to create 1070 * @param template The template of page to create 1071 * @return the list of available services 1072 */ 1073 @Callable 1074 public List<Map<String, Object>> getAvailableServicesForCreation(String pageId, String zoneName, String parentId, String pageTitle, String template) 1075 { 1076 if (StringUtils.isNotEmpty(pageId)) 1077 { 1078 // Get available services for a page 1079 return getAvailableServices(pageId, zoneName); 1080 } 1081 else if (StringUtils.isNotEmpty(parentId)) 1082 { 1083 // Get available services for a not yet existing page 1084 SitemapElement parent = _resolver.resolveById(parentId); 1085 1086 // Create page temporarily 1087 Page page = _createPage(parent, pageTitle, template); 1088 1089 List<Map<String, Object>> availableServices = getAvailableServices(page.getId(), zoneName); 1090 1091 // Cancel page creation 1092 page.getSitemap().revertChanges(); 1093 1094 return availableServices; 1095 } 1096 1097 return Collections.EMPTY_LIST; 1098 } 1099 1100 private Page _createPage (SitemapElement parent, String pageTitle, String template) 1101 { 1102 Site site = parent.getSite(); 1103 String originalPageName = NameHelper.filterName(pageTitle); 1104 1105 String pageName = originalPageName; 1106 int index = 2; 1107 while (parent.hasChild(pageName)) 1108 { 1109 pageName = originalPageName + "-" + index++; 1110 } 1111 1112 ModifiablePage page = ((ModifiableTraversableAmetysObject) parent).createChild(pageName, "ametys:defaultPage"); 1113 1114 page.setTitle(pageTitle); 1115 page.setType(PageType.NODE); 1116 page.setSiteName(site.getName()); 1117 page.setSitemapName(page.getSitemap().getName()); 1118 1119 if (template != null) 1120 { 1121 String skinId = page.getSite().getSkinId(); 1122 SkinTemplate tpl = _skinsManager.getSkin(skinId).getTemplate(template); 1123 if (tpl == null) 1124 { 1125 throw new IllegalStateException("Template '" + template + "' does not exist on skin '" + skinId + "'"); 1126 } 1127 1128 // Set temporary the template to get available services 1129 page.setType(PageType.CONTAINER); 1130 page.setTemplate(template); 1131 } 1132 1133 return page; 1134 } 1135 1136 private boolean _hasRight(Service service, SitemapElement sitemapElement) 1137 { 1138 String right = service.getRight(); 1139 1140 if (right == null) 1141 { 1142 return true; 1143 } 1144 else 1145 { 1146 UserIdentity user = _currentUserProvider.getUser(); 1147 return _rightManager.hasRight(user, right, sitemapElement) == RightResult.RIGHT_ALLOW; 1148 } 1149 } 1150 1151 /** 1152 * Get available template for specified pages 1153 * @param pageIds The id of pages 1154 * @return the list of available template 1155 */ 1156 @Callable 1157 public List<Map<String, Object>> getAvailableTemplates (List<String> pageIds) 1158 { 1159 List<String> templateIds = new ArrayList<>(); 1160 1161 List<Map<String, Object>> templates = new ArrayList<>(); 1162 1163 for (String pageId : pageIds) 1164 { 1165 List<Map<String, Object>> pageTemplates = getAvailableTemplates(pageId); 1166 1167 for (Map<String, Object> template : pageTemplates) 1168 { 1169 String templateName = (String) template.get("id"); 1170 1171 if (!templateIds.contains(templateName)) 1172 { 1173 templateIds.add(templateName); 1174 templates.add(template); 1175 } 1176 } 1177 } 1178 1179 return templates; 1180 } 1181 1182 /** 1183 * Get available content types for a page being created 1184 * @param pageId The page's id. Can be null of the page is not yet created 1185 * @param parentId The id of parent page 1186 * @param pageTitle The title of page to create 1187 * @return the list of available services 1188 */ 1189 @Callable 1190 public List<Map<String, Object>> getAvailableTemplatesForCreation (String pageId, String parentId, String pageTitle) 1191 { 1192 if (StringUtils.isNotEmpty(pageId)) 1193 { 1194 // Get available template for a page 1195 return getAvailableTemplates(pageId); 1196 } 1197 else if (StringUtils.isNotEmpty(parentId)) 1198 { 1199 // Get available template for a not yet existing page 1200 SitemapElement parent = _resolver.resolveById(parentId); 1201 1202 // Create page temporarily 1203 Page page = _createPage(parent, pageTitle, null); 1204 1205 List<Map<String, Object>> availableTemplates = getAvailableTemplates(page.getId()); 1206 1207 // Cancel page creation 1208 page.getSitemap().revertChanges(); 1209 1210 return availableTemplates; 1211 } 1212 1213 return Collections.EMPTY_LIST; 1214 } 1215 1216 /** 1217 * Get service info 1218 * @param pageId Optional, the page id of the service. To get some basic info about the page. 1219 * @param serviceId The id of the service 1220 * @return a Map containing some info about the service (label, url..) 1221 */ 1222 @Callable 1223 public Map<String, Object> getServiceInfo(String pageId, String serviceId) 1224 { 1225 Map<String, Object> info = new HashMap<>(); 1226 1227 if (StringUtils.isNotEmpty(pageId)) 1228 { 1229 SitemapElement sitemapElement = _resolver.resolveById(pageId); 1230 info.put("page-id", sitemapElement.getId()); 1231 info.put("page-title", sitemapElement.getTitle()); 1232 } 1233 1234 Service service = _serviceExtensionPoint.getExtension(serviceId); 1235 info.put("id", service.getId()); 1236 info.put("label", service.getLabel()); 1237 info.put("url", service.getURL()); 1238 info.put("smallIcon", service.getSmallIcon()); 1239 info.put("iconGlyph", service.getIconGlyph()); 1240 info.put("iconDecorator", service.getIconDecorator()); 1241 return info; 1242 } 1243 1244 /** 1245 * Rename a page 1246 * @param pageId The id of page to rename 1247 * @param title The page's title 1248 * @param longTitle The page's long title. 1249 * @param updatePath true to update page's path 1250 * @param createAlias true to create a alias 1251 * @return the result map 1252 */ 1253 @Callable (right = "Web_Rights_Page_Rename", rightContext = PageRightAssignmentContext.ID, paramIndex = 0) 1254 public Map<String, Object> renamePage (String pageId, String title, String longTitle, boolean updatePath, boolean createAlias) 1255 { 1256 Map<String, Object> result = new HashMap<>(); 1257 1258 Page page = _resolver.resolveById(pageId); 1259 1260 if (!(page instanceof ModifiablePage)) 1261 { 1262 throw new IllegalArgumentException("Can not rename a non-modifiable page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "'"); 1263 } 1264 1265 ModifiablePage mPage = (ModifiablePage) page; 1266 mPage.setTitle(title); 1267 mPage.setLongTitle(longTitle); 1268 1269 if (updatePath) 1270 { 1271 String oldPathInSitemap = page.getPathInSitemap(); 1272 String oldPath = "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + ".html"; 1273 String oldPathForChild = "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "/**.html"; 1274 1275 String pageName = ""; 1276 try 1277 { 1278 pageName = NameHelper.filterName(title); 1279 } 1280 catch (IllegalArgumentException e) 1281 { 1282 result.put("invalid-name", title); 1283 return result; 1284 } 1285 1286 if (!page.getName().equals(pageName)) 1287 { 1288 int index = 1; 1289 String initialPageName = pageName; 1290 SitemapElement parent = page.getParent(); 1291 while (parent.hasChild(pageName)) 1292 { 1293 pageName = initialPageName + "-" + (index++); 1294 } 1295 1296 mPage.rename(pageName); 1297 1298 if (createAlias) 1299 { 1300 ModifiableTraversableAmetysObject rootNode = AliasHelper.getRootNode(page.getSite()); 1301 1302 DefaultAlias alias = rootNode.createChild(AliasHelper.getAliasNextUniqueName(rootNode), "ametys:alias"); 1303 alias.setUrl(oldPath); 1304 alias.setTarget(page.getId()); 1305 alias.setType(TargetType.PAGE); 1306 alias.setCreationDate(new Date()); 1307 1308 // Alias for child pages 1309 alias = rootNode.createChild(AliasHelper.getAliasNextUniqueName(rootNode), "ametys:alias"); 1310 alias.setUrl(oldPathForChild); 1311 alias.setTarget("/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "/{1}.html"); 1312 alias.setType(TargetType.URL); 1313 alias.setCreationDate(new Date()); 1314 1315 rootNode.saveChanges(); 1316 } 1317 1318 // Notify observers that the page has been renamed 1319 Map<String, Object> eventParams = new HashMap<>(); 1320 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1321 eventParams.put("path.old.path", oldPathInSitemap); 1322 eventParams.put(ObservationConstants.ARGS_PAGE_PATH, page.getPathInSitemap()); 1323 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_RENAMED, _currentUserProvider.getUser(), eventParams)); 1324 1325 } 1326 else 1327 { 1328 // Notify observers that the page's title has been modified 1329 Map<String, Object> eventParams = new HashMap<>(); 1330 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1331 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1332 } 1333 1334 } 1335 else 1336 { 1337 // Notify observers that the page's title has been modified 1338 Map<String, Object> eventParams = new HashMap<>(); 1339 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1340 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1341 } 1342 1343 Sitemap sitemap = page.getSitemap(); 1344 if (sitemap.needsSave()) 1345 { 1346 sitemap.saveChanges(); 1347 } 1348 1349 result.put("id", page.getId()); 1350 result.put("path", page.getPath()); 1351 result.put("title", page.getTitle()); 1352 1353 return result; 1354 } 1355 1356 /** 1357 * Delete a page and its sub-pages. 1358 * The contents that belong only to the deleted page will be deleted if 'deleteBelongingContents' is set to true. 1359 * The newly created contents are deleted whatever the value if 'deleteBelongingContents'. 1360 * @param pageId the id of page to delete 1361 * @param deleteBelongingContents true to delete the contents that belong to the page and its sub-pages only 1362 * @return The id of deleted pages 1363 */ 1364 @Callable 1365 public Map<String, Object> deletePage(String pageId, boolean deleteBelongingContents) 1366 { 1367 ModifiablePage page = _resolver.resolveById(pageId); 1368 1369 // Check rights on parent 1370 if (!_canDelete(page)) 1371 { 1372 throw new IllegalStateException("You do not have the rights to delete the page '/" + page.getSitemapName() + "/" + page.getPathInSitemap() + "'"); 1373 } 1374 1375 return deletePage(page, deleteBelongingContents); 1376 } 1377 1378 /** 1379 * Delete a page and its sub-pages. 1380 * The contents that belong only to the deleted page will be deleted if 'deleteBelongingContents' is set to true. 1381 * The newly created contents are deleted whatever the value if 'deleteBelongingContents'. 1382 * @param page the page to delete 1383 * @param deleteBelongingContents true to delete the contents that belong to the page and its sub-pages only 1384 * @return The id of deleted pages 1385 */ 1386 public Map<String, Object> deletePage(ModifiablePage page, boolean deleteBelongingContents) 1387 { 1388 Map<String, Object> result = new HashMap<>(); 1389 1390 List<String> contentToDelete = getDeleteablePageContentIds(page.getId(), !deleteBelongingContents); 1391 1392 Sitemap sitemap = page.getSitemap(); 1393 SitemapElement parent = page.getParent(); 1394 String pagePathInSitemap = page.getPathInSitemap(); 1395 1396 List<String> childPagesIds = _getChildrenPageIds(page); 1397 1398 Map<String, Object> eventParams = new HashMap<>(); 1399 eventParams.put(ObservationConstants.ARGS_PAGE_ID, page.getId()); 1400 eventParams.put(ObservationConstants.ARGS_PAGE_PARENT, parent); 1401 eventParams.put(ObservationConstants.ARGS_PAGE_PATH, pagePathInSitemap); 1402 eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap); 1403 eventParams.put(ObservationConstants.ARGS_PAGE_CONTENTS, getPageContents(page)); 1404 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_DELETING, _currentUserProvider.getUser(), eventParams)); 1405 1406 // FIXME API test if this is not modifiable 1407 page.getParent(); 1408 page.remove(); 1409 ((ModifiableAmetysObject) parent).saveChanges(); 1410 1411 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_DELETED, _currentUserProvider.getUser(), eventParams)); 1412 1413 result.put("id", page.getId()); 1414 result.put("childPages", childPagesIds); 1415 1416 result.putAll(_contentDAO.deleteContents(contentToDelete, true)); 1417 return result; 1418 } 1419 1420 /** 1421 * Set pages as blank page 1422 * @param pageIds the id of pages to modify 1423 * @return the id of pages which succeeded or failed. 1424 */ 1425 @Callable 1426 public Map<String, Object> setBlank (List<String> pageIds) 1427 { 1428 Map<String, Object> result = new HashMap<>(); 1429 1430 List<String> successes = new ArrayList<>(); 1431 List<Map<String, Object>> failures = new ArrayList<>(); 1432 1433 for (String pageId : pageIds) 1434 { 1435 try 1436 { 1437 Page page = _resolver.resolveById(pageId); 1438 if (!(page instanceof ModifiablePage)) 1439 { 1440 throw new IllegalArgumentException("Can not set page as blank a non-modifiable page " + pageId); 1441 } 1442 1443 ModifiablePage mPage = (ModifiablePage) page; 1444 1445 if (page.getType().equals(PageType.CONTAINER)) 1446 { 1447 // Remove zones 1448 for (ModifiableZone zone : mPage.getZones()) 1449 { 1450 zone.remove(); 1451 } 1452 } 1453 1454 mPage.setType(PageType.NODE); 1455 mPage.getSitemap().saveChanges(); 1456 1457 successes.add(pageId); 1458 1459 Map<String, Object> eventParams = new HashMap<>(); 1460 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1461 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 1462 } 1463 catch (Exception e) 1464 { 1465 getLogger().error("Cannot set the page '" + pageId + "' as blank page", e); 1466 1467 Map<String, Object> failure = new HashMap<>(); 1468 failure.put("id", pageId); 1469 failure.put("error", e.toString()); 1470 failures.add(failure); 1471 } 1472 } 1473 1474 result.put("success", successes); 1475 result.put("failure", failures); 1476 1477 return result; 1478 } 1479 1480 /** 1481 * Set a template to pages 1482 * @param pageIds the id of pages to update 1483 * @param templateName The template name 1484 * @return the id of pages which succeeded 1485 */ 1486 @Callable 1487 public Map<String, Object> setTemplate (List<String> pageIds, String templateName) 1488 { 1489 return setTemplate(pageIds, templateName, true); 1490 } 1491 1492 /** 1493 * Set a template to pages 1494 * @param pageIds the id of pages to update 1495 * @param templateName The template name 1496 * @param checkAvailableTemplate true if you want to check available template 1497 * @return the id of pages which succeeded 1498 */ 1499 public Map<String, Object> setTemplate (List<String> pageIds, String templateName, boolean checkAvailableTemplate) 1500 { 1501 Map<String, Object> result = new HashMap<>(); 1502 1503 List<String> successes = new ArrayList<>(); 1504 1505 String defaultZoneName = null; 1506 1507 for (String pageId : pageIds) 1508 { 1509 Page page = _resolver.resolveById(pageId); 1510 if (!(page instanceof ModifiablePage)) 1511 { 1512 throw new IllegalArgumentException("Can not set template a non-modifiable page " + pageId); 1513 } 1514 1515 ModifiablePage mPage = (ModifiablePage) page; 1516 1517 if (defaultZoneName == null) 1518 { 1519 String skinId = page.getSite().getSkinId(); 1520 SkinTemplate tpl = _skinsManager.getSkin(skinId).getTemplate(templateName); 1521 if (tpl == null) 1522 { 1523 throw new IllegalStateException("Template '" + templateName + "' does not exist on skin '" + skinId + "'"); 1524 } 1525 1526 defaultZoneName = tpl.getDefaultZoneId(); 1527 } 1528 1529 if (checkAvailableTemplate && !_templatesHandler.getAvailablesTemplates(mPage).contains(templateName)) 1530 { 1531 throw new IllegalStateException("Template '" + templateName + "' is not available for page '" + pageId + "'"); 1532 } 1533 1534 if (page.getType().equals(PageType.CONTAINER)) 1535 { 1536 _removeOldZones (mPage, templateName); 1537 } 1538 1539 mPage.setTemplate(templateName); 1540 mPage.setType(PageType.CONTAINER); 1541 mPage.getSitemap().saveChanges(); 1542 1543 successes.add(pageId); 1544 1545 Map<String, Object> eventParams = new HashMap<>(); 1546 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1547 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams)); 1548 } 1549 1550 if (defaultZoneName != null) 1551 { 1552 result.put("zonename", defaultZoneName); 1553 } 1554 1555 result.put("success", successes); 1556 1557 return result; 1558 } 1559 1560 /** 1561 * Get the script class name to execute to add or update this service 1562 * @param serviceId the service id 1563 * @return the script class name. Can be empty 1564 */ 1565 @Callable 1566 public String getServiceParametersAction (String serviceId) 1567 { 1568 try 1569 { 1570 Service service = _serviceExtensionPoint.getExtension(serviceId); 1571 return service.getParametersScript().getScriptClassname(); 1572 } 1573 catch (IllegalArgumentException e) 1574 { 1575 throw new IllegalArgumentException("Service with id '" + serviceId + "' does not exist", e); 1576 } 1577 } 1578 1579 private void _removeOldZones (ModifiablePage page, String templateName) 1580 { 1581 String skinId = page.getSite().getSkinId(); 1582 1583 SkinTemplate oldTemplate = _skinsManager.getSkin(skinId).getTemplate(templateName); 1584 1585 Map<String, SkinTemplateZone> templateZones = oldTemplate.getZones(); 1586 1587 for (ModifiableZone zone : page.getZones()) 1588 { 1589 if (!templateZones.containsKey(zone.getName())) 1590 { 1591 zone.remove(); 1592 } 1593 } 1594 } 1595 1596 /** 1597 * Get the tags from the pages 1598 * @param pageIds The ids of the pages 1599 * @return the tags of the pages 1600 */ 1601 @Callable 1602 public Set<String> getTags (List<String> pageIds) 1603 { 1604 Set<String> tags = new HashSet<>(); 1605 1606 for (String pageId : pageIds) 1607 { 1608 Page page = _resolver.resolveById(pageId); 1609 tags.addAll(page.getTags()); 1610 } 1611 1612 return tags; 1613 } 1614 1615 /** 1616 * Tag a list of pages 1617 * @param pageIds The ids of pages to tag 1618 * @param tagNames The tags 1619 * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 1620 * @param contextualParameters Contextual parameters. Must contain the site name 1621 * @return the result 1622 */ 1623 @Callable 1624 public Map<String, Object> tag (List<String> pageIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters) 1625 { 1626 Map<String, Object> result = new HashMap<>(); 1627 1628 result.put("nomodifiable-pages", new ArrayList<Map<String, Object>>()); 1629 result.put("invalid-tags", new ArrayList<String>()); 1630 result.put("allright-pages", new ArrayList<Map<String, Object>>()); 1631 1632 for (String pageId : pageIds) 1633 { 1634 Page page = _resolver.resolveById(pageId); 1635 1636 Map<String, Object> page2json = new HashMap<>(); 1637 page2json.put("id", page.getId()); 1638 page2json.put("title", page.getTitle()); 1639 1640 if (page instanceof ModifiablePage) 1641 { 1642 ModifiablePage mPage = (ModifiablePage) page; 1643 1644 TagMode tagMode = TagMode.valueOf(mode); 1645 1646 Set<String> oldTags = mPage.getTags(); 1647 if (TagMode.REPLACE.equals(tagMode)) 1648 { 1649 // First delete old tags 1650 for (String tagName : oldTags) 1651 { 1652 mPage.untag(tagName); 1653 } 1654 } 1655 1656 1657 // Then set new tags 1658 for (String tagName : tagNames) 1659 { 1660 if (_isTagValid(page, tagName)) 1661 { 1662 if (TagMode.REMOVE.equals(tagMode)) 1663 { 1664 mPage.untag(tagName); 1665 } 1666 else if (TagMode.REPLACE.equals(tagMode) || !oldTags.contains(tagName)) 1667 { 1668 mPage.tag(tagName); 1669 } 1670 } 1671 else 1672 { 1673 @SuppressWarnings("unchecked") 1674 List<String> invalidTags = (List<String>) result.get("invalid-tags"); 1675 invalidTags.add(tagName); 1676 } 1677 } 1678 1679 mPage.saveChanges(); 1680 1681 page2json.put("tags", page.getTags()); 1682 @SuppressWarnings("unchecked") 1683 List<Map<String, Object>> allRightPages = (List<Map<String, Object>>) result.get("allright-pages"); 1684 allRightPages.add(page2json); 1685 1686 if (!oldTags.equals(page.getTags())) 1687 { 1688 // Notify observers that the content has been tagged 1689 Map<String, Object> eventParams = new HashMap<>(); 1690 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1691 eventParams.put(ObservationConstants.ARGS_PAGE_TAGS, page.getTags()); 1692 eventParams.put(ObservationConstants.ARGS_PAGE_OLD_TAGS, oldTags); 1693 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1694 } 1695 } 1696 else 1697 { 1698 @SuppressWarnings("unchecked") 1699 List<Map<String, Object>> nomodifiablePages = (List<Map<String, Object>>) result.get("nomodifiable-pages"); 1700 nomodifiablePages.add(page2json); 1701 } 1702 } 1703 1704 return result; 1705 } 1706 1707 /** 1708 * Tag a list of contents with the given tags 1709 * @param pageIds The ids of pages to tag 1710 * @param contentIds The ids of contents to tag 1711 * @param tagNames The tags 1712 * @param contextualParameters The contextual parameters 1713 * @return the result map 1714 */ 1715 @Callable 1716 public Map<String, Object> tag (List<String> pageIds, List<String> contentIds, List<String> tagNames, Map<String, Object> contextualParameters) 1717 { 1718 return tag(pageIds, contentIds, tagNames, TagMode.REPLACE.toString(), contextualParameters); 1719 } 1720 1721 /** 1722 * Tag a list of contents and/org pages 1723 * @param pageIds The ids of pages to tag 1724 * @param contentIds The ids of contents to tag 1725 * @param tagNames The tags 1726 * @param mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 1727 * @param contextualParameters The contextual parameters 1728 * @return the result 1729 */ 1730 @Callable 1731 public Map<String, Object> tag (List<String> pageIds, List<String> contentIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters) 1732 { 1733 // Tag pages 1734 Map<String, Object> result = tag(pageIds, tagNames, mode, contextualParameters); 1735 1736 // Tag contents 1737 result.putAll(_contentDAO.tag(contentIds, tagNames, mode, contextualParameters)); 1738 1739 // Invalid tags are ignored 1740 result.remove("invalid-tags"); 1741 return result; 1742 } 1743 1744 /** 1745 * Test if a tag is valid for a specific page 1746 * @param page The page 1747 * @param tagName The tag name 1748 * @return True if the tag is valid 1749 */ 1750 public boolean _isTagValid (Page page, String tagName) 1751 { 1752 Map<String, Object> params = new HashMap<>(); 1753 params.put("siteName", page.getSiteName()); 1754 CMSTag tag = _tagProvider.getTag(tagName, params); 1755 1756 return tag.getTarget().getName().equals("PAGE"); 1757 } 1758 1759 /** 1760 * Returns the ids of the pages matching the tag 1761 * @param sitename The site id 1762 * @param lang The language code 1763 * @param tag The tag id 1764 * @return Array of pages ids 1765 */ 1766 public List<String> findPagedIdsByTag(String sitename, String lang, String tag) 1767 { 1768 return _getMemoryPageTagCache().get(PageTagCacheKey.of(sitename, lang, tag), __ -> _computePagesIds(sitename, lang, tag)) 1769 .stream() 1770 .filter(_resolver::hasAmetysObjectForId) // Cache remember for tag in default, we have to check if page exists in current workspace 1771 .collect(Collectors.toList()); 1772 } 1773 1774 private List<String> _computePagesIds(String sitename, String lang, String tag) 1775 { 1776 1777 Session defaultSession = null; 1778 try 1779 { 1780 List<String> pagesIds = new ArrayList<>(); 1781 1782 // Force default workspace to execute query 1783 defaultSession = _repository.login(RepositoryConstants.DEFAULT_WORKSPACE); 1784 1785 String xpath = PageQueryHelper.getPageXPathQuery(sitename, lang, null, new TagExpression(Operator.EQ, tag), null); 1786 AmetysObjectIterable<Page> pages = _ametysObjectResolver.query(xpath, defaultSession); 1787 Iterator<Page> it = pages.iterator(); 1788 1789 while (it.hasNext()) 1790 { 1791 pagesIds.add(it.next().getId()); 1792 } 1793 1794 return pagesIds; 1795 } 1796 catch (RepositoryException e) 1797 { 1798 throw new AmetysRepositoryException(e); 1799 } 1800 finally 1801 { 1802 if (defaultSession != null) 1803 { 1804 defaultSession.logout(); 1805 } 1806 } 1807 } 1808 1809 private Cache<PageTagCacheKey, List<String>> _getMemoryPageTagCache() 1810 { 1811 return _cacheManager.get(MEMORY_PAGESTAGCACHE); 1812 } 1813 1814 /** 1815 * Set the visible of pages 1816 * @param pageIds The id of pages 1817 * @param visible <code>true</code> to set pages as visible, <code>false</code> otherwise 1818 * @return The result map 1819 */ 1820 @Callable 1821 public Map<String, Object> setVisibility (List<String> pageIds, boolean visible) 1822 { 1823 Map<String, Object> result = new HashMap<>(); 1824 1825 result.put("nomodifiable-pages", new ArrayList<Map<String, Object>>()); 1826 result.put("allright-pages", new ArrayList<Map<String, Object>>()); 1827 1828 for (String id : pageIds) 1829 { 1830 Page page = _resolver.resolveById(id); 1831 1832 Map<String, Object> page2json = new HashMap<>(); 1833 page2json.put("id", page.getId()); 1834 page2json.put("title", page.getTitle()); 1835 1836 if (page instanceof ModifiablePage) 1837 { 1838 ModifiablePage mPage = (ModifiablePage) page; 1839 mPage.setVisible(visible); 1840 mPage.saveChanges(); 1841 1842 Map<String, Object> eventParams = new HashMap<>(); 1843 eventParams.put(ObservationConstants.ARGS_PAGE, page); 1844 eventParams.put(ObservationConstants.ARGS_PAGE_ID, page.getId()); 1845 _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams)); 1846 1847 @SuppressWarnings("unchecked") 1848 List<Map<String, Object>> allRightPages = (List<Map<String, Object>>) result.get("allright-pages"); 1849 allRightPages.add(page2json); 1850 } 1851 else 1852 { 1853 @SuppressWarnings("unchecked") 1854 List<Map<String, Object>> nomodifiablePages = (List<Map<String, Object>>) result.get("nomodifiable-pages"); 1855 nomodifiablePages.add(page2json); 1856 } 1857 } 1858 1859 return result; 1860 } 1861 1862 /** 1863 * Get the contents of a {@link Page} and its subpages which can be deleted. 1864 * A content is deleteable if user has right, the content is not locked and not referenced by other pages. 1865 * @param id The id of page 1866 * @return The list of deletable contents 1867 */ 1868 @Callable 1869 public Map<String, Object> getDeleteablePageContents (String id) 1870 { 1871 Map<String, Object> results = new HashMap<>(); 1872 1873 results.put("deleteable-contents", new ArrayList<Map<String, Object>>()); 1874 results.put("referenced-contents", new ArrayList<Map<String, Object>>()); 1875 results.put("unauthorized-contents", new ArrayList<Map<String, Object>>()); 1876 results.put("locked-contents", new ArrayList<Map<String, Object>>()); 1877 1878 Page page = _resolver.resolveById(id); 1879 List<Content> contents = getPageContents(page, true); 1880 1881 for (Content content : contents) 1882 { 1883 Map<String, Object> contentParams = new HashMap<>(); 1884 contentParams.put("id", content.getId()); 1885 contentParams.put("title", content.getTitle(new Locale(page.getSitemapName()))); 1886 contentParams.put("name", content.getName()); 1887 1888 if (_isReferenced(content)) 1889 { 1890 // Content is referenced by at least another page 1891 @SuppressWarnings("unchecked") 1892 List<Map<String, Object>> referencedContents = (List<Map<String, Object>>) results.get("referenced-contents"); 1893 referencedContents.add(contentParams); 1894 } 1895 else if (!_contentDAO.canDelete(content)) 1896 { 1897 @SuppressWarnings("unchecked") 1898 List<Map<String, Object>> unauthorizedContents = (List<Map<String, Object>>) results.get("unauthorized-contents"); 1899 unauthorizedContents.add(contentParams); 1900 } 1901 else if (_isLocked(content)) 1902 { 1903 // If the content is locked by other 1904 @SuppressWarnings("unchecked") 1905 List<Map<String, Object>> lockedContents = (List<Map<String, Object>>) results.get("locked-contents"); 1906 lockedContents.add(contentParams); 1907 } 1908 else 1909 { 1910 Map<String, Object> content2json = new HashMap<>(); 1911 content2json.put("id", content.getId()); 1912 content2json.put("name", content.getName()); 1913 content2json.put("title", content.getTitle(new Locale(page.getSitemapName()))); 1914 content2json.put("isNew", _isNew(content)); 1915 content2json.put("isShared", content instanceof SharedContent); 1916 content2json.put("hasShared", _sharedContentManager.hasSharedContents(content)); 1917 1918 @SuppressWarnings("unchecked") 1919 List<Map<String, Object>> allrightContents = (List<Map<String, Object>>) results.get("deleteable-contents"); 1920 allrightContents.add(content2json); 1921 } 1922 } 1923 1924 return results; 1925 } 1926 1927 /** 1928 * Get the contents that belong to the {@link Page} and its sub-pages and that can be deleted. 1929 * A content is deleteable if user has right, the content is not locked and it's not referenced by other pages. 1930 * If 'onlyNewlyCreatedContents' is set to 'true', only newly created contents will be returned 1931 * @param pageId The id of page 1932 * @param onlyNewlyCreatedContents true to return only the newly created contents 1933 * @return The ids of deleteable contents 1934 */ 1935 public List<String> getDeleteablePageContentIds (String pageId, boolean onlyNewlyCreatedContents) 1936 { 1937 List<String> contentsId = new ArrayList<>(); 1938 1939 Page page = _resolver.resolveById(pageId); 1940 1941 List<Content> contents = getPageContents(page, true); 1942 1943 for (Content content : contents) 1944 { 1945 if (_contentDAO.canDelete(content) && !_isLocked(content) && !_isReferenced(content)) 1946 { 1947 if (!onlyNewlyCreatedContents || _isNew(content)) 1948 { 1949 contentsId.add(content.getId()); 1950 } 1951 } 1952 } 1953 1954 return contentsId; 1955 } 1956 1957 /** 1958 * Get the unreferenced contents of a {@link Page} or a {@link ZoneItem} 1959 * @param id The id of page or zone item 1960 * @return The list of unreferenced contents 1961 */ 1962 @Callable 1963 public List<Map<String, Object>> getUnreferencedContents (String id) 1964 { 1965 List<Map<String, Object>> unreferencedContents = new ArrayList<>(); 1966 1967 Page page = _resolver.resolveById(id); 1968 List<Content> contents = getPageContents(page, true); 1969 1970 for (Content content : contents) 1971 { 1972 if (!_isReferenced(content)) 1973 { 1974 Map<String, Object> content2json = new HashMap<>(); 1975 content2json.put("id", content.getId()); 1976 content2json.put("name", content.getName()); 1977 content2json.put("title", content.getTitle(new Locale(page.getSitemapName()))); 1978 content2json.put("isNew", _isNew(content)); 1979 content2json.put("isShared", content instanceof SharedContent); 1980 content2json.put("hasShared", _sharedContentManager.hasSharedContents(content)); 1981 1982 unreferencedContents.add(content2json); 1983 } 1984 } 1985 1986 return unreferencedContents; 1987 } 1988 1989 /** 1990 * Returns the page's attachments root node 1991 * @param id the page's id 1992 * @return The attachments' root node informations 1993 */ 1994 @Callable 1995 public Map<String, Object> getAttachmentsRootNode (String id) 1996 { 1997 Map<String, Object> result = new HashMap<>(); 1998 1999 Page page = _resolver.resolveById(id); 2000 2001 result.put("title", page.getTitle()); 2002 result.put("contentId", page.getId()); 2003 2004 TraversableAmetysObject attachments = page.getRootAttachments(); 2005 2006 if (attachments != null) 2007 { 2008 result.put("id", attachments.getId()); 2009 if (attachments instanceof ModifiableAmetysObject) 2010 { 2011 result.put("isModifiable", true); 2012 } 2013 if (attachments instanceof ModifiableResourceCollection) 2014 { 2015 result.put("canCreateChild", true); 2016 } 2017 2018 boolean hasChildNodes = false; 2019 boolean hasResources = false; 2020 2021 for (AmetysObject child : attachments.getChildren()) 2022 { 2023 if (child instanceof Resource) 2024 { 2025 hasResources = true; 2026 } 2027 else if (child instanceof ExplorerNode) 2028 { 2029 hasChildNodes = true; 2030 } 2031 } 2032 2033 if (hasChildNodes) 2034 { 2035 result.put("hasChildNodes", true); 2036 } 2037 2038 if (hasResources) 2039 { 2040 result.put("hasResources", true); 2041 } 2042 2043 return result; 2044 } 2045 2046 throw new IllegalArgumentException("Page with id '" + id + "' does not support attachments."); 2047 } 2048 /** 2049 * Returns the page's parents ids 2050 * @param id the page's id 2051 * @return The attachments' root node informations 2052 */ 2053 @Callable 2054 public Map<String, Object> getPageParents (String id) 2055 { 2056 Map<String, Object> result = new HashMap<>(); 2057 List<Map<String, Object>> pages = new ArrayList<>(); 2058 Page page = _resolver.resolveById(id); 2059 pages.add(_page2Json(page)); 2060 while (page.getParent() != null && page.getParent() instanceof Page) 2061 { 2062 page = page.getParent(); 2063 pages.add(_page2Json(page)); 2064 } 2065 result.put("parents", pages); 2066 return result; 2067 } 2068 2069 /** 2070 * Get the contents of a page and its child pages 2071 * @param sitemapElement The page 2072 * @return The list of contents 2073 */ 2074 public List<Content> getPageContents (SitemapElement sitemapElement) 2075 { 2076 return getPageContents(sitemapElement, false); 2077 } 2078 2079 /** 2080 * Get the contents of a page and its child pages 2081 * @param sitemapElement The page 2082 * @param ignoreContentsOfNonRemovablePage true to ignore contents of non-removable pages (virtual pages) 2083 * @return The list of contents 2084 */ 2085 public List<Content> getPageContents (SitemapElement sitemapElement, boolean ignoreContentsOfNonRemovablePage) 2086 { 2087 List<Content> contents = new ArrayList<>(); 2088 2089 if ((!ignoreContentsOfNonRemovablePage || sitemapElement instanceof RemovableAmetysObject) 2090 && sitemapElement.getTemplate() != null) 2091 { 2092 for (Zone zone : sitemapElement.getZones()) 2093 { 2094 try (AmetysObjectIterable< ? extends ZoneItem> zoneItems = zone.getZoneItems()) 2095 { 2096 for (ZoneItem zoneItem : zoneItems) 2097 { 2098 if (zoneItem.getType() == ZoneItem.ZoneType.CONTENT) 2099 { 2100 contents.add(zoneItem.getContent()); 2101 } 2102 } 2103 } 2104 } 2105 } 2106 2107 AmetysObjectIterable< ? extends Page> childrenPages = sitemapElement.getChildrenPages(); 2108 for (Page childPage : childrenPages) 2109 { 2110 contents.addAll(getPageContents(childPage, ignoreContentsOfNonRemovablePage)); 2111 } 2112 2113 return contents; 2114 } 2115 2116 /** 2117 * Get the user rights on sitemap element (page or sitemap) 2118 * @param pagesCt The sitemap element 2119 * @return The user's rights 2120 */ 2121 protected Set<String> getUserRights (SitemapElement pagesCt) 2122 { 2123 UserIdentity user = _currentUserProvider.getUser(); 2124 2125 Set<String> userRights = _rightManager.getUserRights(user, pagesCt); 2126 2127 // Do some specific stuff here, because the right 'Web_Rights_Page_Delete' is a right to delete child pages and not the page itself. 2128 // So the right should be checked on parent context. 2129 if (pagesCt instanceof Page) 2130 { 2131 SitemapElement parent = pagesCt.getParent(); 2132 boolean canDelete = _rightManager.hasRight(user, "Web_Rights_Page_Delete", parent) == RightResult.RIGHT_ALLOW; 2133 if (!canDelete) 2134 { 2135 // No right on parent page, so remove the right if exists. 2136 userRights.remove("Web_Rights_Page_Delete"); 2137 } 2138 } 2139 else 2140 { 2141 // There is no right of deletion on the sitemap 2142 userRights.remove("Web_Rights_Page_Delete"); 2143 } 2144 2145 return userRights; 2146 } 2147 2148 private boolean _isReferenced (Content content) 2149 { 2150 return content instanceof WebContent && ((WebContent) content).getReferencingPages().size() > 1; 2151 } 2152 2153 private boolean _isLocked (Content content) 2154 { 2155 if (content instanceof LockableAmetysObject) 2156 { 2157 LockableAmetysObject lockableContent = (LockableAmetysObject) content; 2158 if (lockableContent.isLocked()) 2159 { 2160 boolean canUnlockAll = _rightManager.hasRight(_currentUserProvider.getUser(), "CMS_Rights_UnlockAll", "/cms") == RightResult.RIGHT_ALLOW; 2161 if (!LockHelper.isLockOwner(lockableContent, _currentUserProvider.getUser()) && !canUnlockAll) 2162 { 2163 return true; 2164 } 2165 } 2166 } 2167 2168 return false; 2169 } 2170 2171 private boolean _isNew (Content content) 2172 { 2173 boolean isNew = false; 2174 if (content instanceof WorkflowAwareContent) 2175 { 2176 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 2177 long workflowId = waContent.getWorkflowId(); 2178 2179 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 2180 isNew = workflow.getHistorySteps(workflowId).isEmpty(); 2181 } 2182 return isNew; 2183 } 2184 2185 private Map<String, Object> _page2Json (Page page) 2186 { 2187 Map<String, Object> page2json = new HashMap<>(); 2188 page2json.put("id", page.getId()); 2189 page2json.put("title", page.getTitle()); 2190 page2json.put("siteName", page.getSiteName()); 2191 page2json.put("path", page.getPathInSitemap()); 2192 return page2json; 2193 } 2194 2195 private Map<String, Object> _content2Json (Content content, Locale locale) 2196 { 2197 Map<String, Object> content2json = new HashMap<>(); 2198 content2json.put("id", content.getId()); 2199 content2json.put("title", content.getTitle(locale)); 2200 content2json.put("name", content.getName()); 2201 2202 List<Map<String, Object>> pages = new ArrayList<>(); 2203 if (content instanceof WebContent) 2204 { 2205 content2json.put("siteName", ((WebContent) content).getSiteName()); 2206 Collection<Page> refPages = ((WebContent) content).getReferencingPages(); 2207 for (Page refPage : refPages) 2208 { 2209 pages.add(_page2Json(refPage)); 2210 } 2211 content2json.put("pages", pages); 2212 } 2213 2214 return content2json; 2215 } 2216 2217 private Map<String, Object> _publication2Json (Page page) 2218 { 2219 Map<String, Object> pub2json = new HashMap<>(); 2220 @SuppressWarnings("unchecked") 2221 ElementType<ZonedDateTime> dateType = (ElementType<ZonedDateTime>) _pageDataTypeExtensionPoint.getExtension(ModelItemTypeConstants.DATETIME_TYPE_ID); 2222 2223 ZonedDateTime startDate = page.getValue(DefaultPage.METADATA_PUBLICATION_START_DATE); 2224 if (startDate != null) 2225 { 2226 pub2json.put("startDate", dateType.valueToJSONForClient(startDate, DataContext.newInstance())); 2227 } 2228 2229 ZonedDateTime endDate = page.getValue(DefaultPage.METADATA_PUBLICATION_END_DATE); 2230 if (endDate != null) 2231 { 2232 pub2json.put("endDate", dateType.valueToJSONForClient(endDate, DataContext.newInstance())); 2233 } 2234 2235 return pub2json; 2236 2237 } 2238 2239 private AmetysObjectIterable<Content> _getIncomingContentReferences (String pageId) 2240 { 2241 String xpathQuery = "//element(*, ametys:content)[ametys-internal:consistency/@ametys-internal:link = 'page:" + pageId + "']"; 2242 return _resolver.query(xpathQuery); 2243 } 2244 2245 private AmetysObjectIterable<Page> _getIncomingPageReferences (String pageId) 2246 { 2247 String xpathQuery = "//element(*, ametys:page)[@ametys-internal:type = 'LINK' and @ametys-internal:url= '" + pageId + "']"; 2248 return _resolver.query(xpathQuery); 2249 } 2250 2251 private void _updateContentsAfterCopy(Page initialPage, Page createdPage) throws AmetysRepositoryException 2252 { 2253 for (Zone zone : createdPage.getZones()) 2254 { 2255 Zone initialZone = initialPage.getZone(zone.getName()); 2256 try (AmetysObjectIterable< ? extends ZoneItem> zoneItems = zone.getZoneItems(); AmetysObjectIterable< ? extends ZoneItem> initialZoneItems = initialZone.getZoneItems()) 2257 { 2258 AmetysObjectIterator<? extends ZoneItem> iterator = zoneItems.iterator(); 2259 AmetysObjectIterator< ? extends ZoneItem> initialIterator = initialZoneItems.iterator(); 2260 if (iterator.getSize() != initialIterator.getSize()) 2261 { 2262 throw new IllegalStateException("An error occured during the copy of " + initialPage.getName() + " (" + initialPage.getId() + "). The resulting page have a different number of zoneItems."); 2263 } 2264 while (iterator.hasNext()) 2265 { 2266 ZoneItem zoneItem = iterator.next(); 2267 ZoneItem initialZoneItem = initialIterator.next(); 2268 if (zoneItem.getType().equals(ZoneType.CONTENT)) 2269 { 2270 Content content = zoneItem.getContent(); 2271 WebContent initialContent = initialZoneItem.getContent(); 2272 2273 // Updaters 2274 Set<String> ids = _copyUpdaterEP.getExtensionsIds(); 2275 for (String id : ids) 2276 { 2277 _copyUpdaterEP.getExtension(id).updateContent(initialPage.getSite(), createdPage.getSite(), initialContent, content); 2278 } 2279 2280 // Convert content language if necessary 2281 if (content instanceof ModifiableContent && content.getLanguage() != null && content.getLanguage() != createdPage.getSitemapName()) 2282 { 2283 ((ModifiableContent) content).setLanguage(createdPage.getSitemapName()); 2284 ((ModifiableContent) content).saveChanges(); 2285 } 2286 2287 // Create the first version 2288 if (content instanceof VersionableAmetysObject) 2289 { 2290 ((VersionableAmetysObject) content).checkpoint(); 2291 } 2292 } 2293 } 2294 } 2295 } 2296 2297 // Browse child pages 2298 for (Page childPage : createdPage.getChildrenPages()) 2299 { 2300 Page initialChildPage = initialPage.getChild(childPage.getName()); 2301 _updateContentsAfterCopy (initialChildPage, childPage); 2302 } 2303 } 2304 2305 private List<String> _getChildrenPageIds (Page page) 2306 { 2307 List<String> childIds = new ArrayList<>(); 2308 2309 for (Page childPage : page.getChildrenPages()) 2310 { 2311 childIds.add(childPage.getId()); 2312 childIds.addAll(_getChildrenPageIds(childPage)); 2313 } 2314 2315 return childIds; 2316 } 2317 2318 /** 2319 * Check each parent and return true if one of them is invisible 2320 * @param page page to check 2321 * @return true if at least one parent is invisible 2322 */ 2323 private boolean _isParentInvisible (Page page) 2324 { 2325 AmetysObject parent = page.getParent(); 2326 while (parent != null && parent instanceof Page) 2327 { 2328 boolean invisible = !((Page) parent).isVisible(); 2329 if (invisible) 2330 { 2331 return true; 2332 } 2333 parent = parent.getParent(); 2334 } 2335 return false; 2336 } 2337 2338 /* start of a group of methods for _isPreviewable */ 2339 /** 2340 * Determine if this page is previewable 2341 * @param page The page to look at 2342 * @return true if the page can be previewed 2343 */ 2344 private boolean _isPreviewable(Page page) 2345 { 2346 // Check for infinitive loop redirection 2347 ArrayList<String> pagesSequence = new ArrayList<>(); 2348 pagesSequence.add(page.getId()); 2349 if (_isInfiniteRedirection (page, pagesSequence)) 2350 { 2351 getLogger().error("An infinite loop redirection was detected for page '" + page.getPathInSitemap() + "'"); 2352 return false; 2353 } 2354 2355 if (page.getType() == PageType.LINK && LinkType.PAGE.equals(page.getURLType())) 2356 { 2357 return _isPageExist(page.getURL()) && _isPreviewable((Page) _resolver.resolveById(page.getURL())); 2358 } 2359 2360 if (page.getType() != PageType.NODE) 2361 { 2362 return true; 2363 } 2364 else 2365 { 2366 try (AmetysObjectIterable< ? extends Page> childrenPages = page.getChildrenPages()) 2367 { 2368 for (Page subPage : childrenPages) 2369 { 2370 if (_isPreviewable(subPage)) 2371 { 2372 return true; 2373 } 2374 } 2375 } 2376 } 2377 return false; 2378 } 2379 2380 private boolean _isPageExist (String id) 2381 { 2382 try 2383 { 2384 _resolver.resolveById(id); 2385 return true; 2386 } 2387 catch (UnknownAmetysObjectException e) 2388 { 2389 return false; 2390 } 2391 } 2392 2393 private boolean _isInfiniteRedirection (Page page, List<String> pagesSequence) 2394 { 2395 Page redirectPage = _getPageRedirection (page); 2396 if (redirectPage == null) 2397 { 2398 return false; 2399 } 2400 2401 if (pagesSequence.contains(redirectPage.getId())) 2402 { 2403 return true; 2404 } 2405 2406 pagesSequence.add(redirectPage.getId()); 2407 return _isInfiniteRedirection (redirectPage, pagesSequence); 2408 } 2409 2410 private Page _getPageRedirection (Page page) 2411 { 2412 if (PageType.LINK.equals(page.getType()) && LinkType.PAGE.equals(page.getURLType())) 2413 { 2414 try 2415 { 2416 String pageId = page.getURL(); 2417 return _resolver.resolveById(pageId); 2418 } 2419 catch (AmetysRepositoryException e) 2420 { 2421 return null; 2422 } 2423 } 2424 else if (PageType.NODE.equals(page.getType())) 2425 { 2426 AmetysObjectIterable<? extends Page> childPages = page.getChildrenPages(); 2427 Iterator<? extends Page> it = childPages.iterator(); 2428 if (it.hasNext()) 2429 { 2430 return it.next(); 2431 } 2432 } 2433 2434 return null; 2435 } 2436 /* end of a group of methods for _isPreviewable */ 2437 2438 private static final class PageTagCacheKey extends AbstractCacheKey 2439 { 2440 private PageTagCacheKey(String sitename, String lang, String tag) 2441 { 2442 super(sitename, lang, tag); 2443 } 2444 2445 private PageTagCacheKey(String sitename, String lang) 2446 { 2447 super(sitename, lang); 2448 } 2449 2450 static PageTagCacheKey of(String sitename, String lang) 2451 { 2452 return new PageTagCacheKey(sitename, lang, null); 2453 } 2454 2455 static PageTagCacheKey of(String sitename, String lang, String tag) 2456 { 2457 return new PageTagCacheKey(sitename, lang, tag); 2458 } 2459 } 2460}