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