001/* 002 * Copyright 2020 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.plugins.workspaces.project; 017 018import java.time.ZonedDateTime; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.LinkedHashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Objects; 030import java.util.Optional; 031import java.util.Set; 032import java.util.function.Predicate; 033import java.util.regex.Pattern; 034import java.util.stream.Collectors; 035import java.util.stream.Stream; 036import java.util.stream.StreamSupport; 037 038import javax.jcr.Node; 039import javax.jcr.PathNotFoundException; 040import javax.jcr.Property; 041import javax.jcr.RepositoryException; 042import javax.jcr.Session; 043import javax.jcr.Value; 044 045import org.apache.avalon.framework.activity.Initializable; 046import org.apache.avalon.framework.component.Component; 047import org.apache.avalon.framework.context.Context; 048import org.apache.avalon.framework.context.ContextException; 049import org.apache.avalon.framework.context.Contextualizable; 050import org.apache.avalon.framework.logger.AbstractLogEnabled; 051import org.apache.avalon.framework.service.ServiceException; 052import org.apache.avalon.framework.service.ServiceManager; 053import org.apache.avalon.framework.service.Serviceable; 054import org.apache.cocoon.components.ContextHelper; 055import org.apache.cocoon.environment.Request; 056import org.apache.commons.collections.CollectionUtils; 057import org.apache.commons.lang.ArrayUtils; 058import org.apache.commons.lang3.StringUtils; 059import org.apache.commons.lang3.tuple.Pair; 060 061import org.ametys.cms.repository.ContentDAO.TagMode; 062import org.ametys.cms.tag.Tag; 063import org.ametys.core.cache.AbstractCacheManager; 064import org.ametys.core.cache.AbstractCacheManager.CacheType; 065import org.ametys.core.cache.Cache; 066import org.ametys.core.cache.CacheException; 067import org.ametys.core.group.GroupDirectoryContextHelper; 068import org.ametys.core.group.GroupIdentity; 069import org.ametys.core.observation.Event; 070import org.ametys.core.observation.ObservationManager; 071import org.ametys.core.observation.Observer; 072import org.ametys.core.right.RightManager; 073import org.ametys.core.right.RightManager.RightResult; 074import org.ametys.core.ui.Callable; 075import org.ametys.core.user.CurrentUserProvider; 076import org.ametys.core.user.UserIdentity; 077import org.ametys.core.user.population.PopulationContextHelper; 078import org.ametys.core.util.I18nUtils; 079import org.ametys.core.util.LambdaUtils; 080import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 081import org.ametys.plugins.core.search.UserAndGroupSearchManager; 082import org.ametys.plugins.core.user.UserHelper; 083import org.ametys.plugins.explorer.ExplorerNode; 084import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 085import org.ametys.plugins.repository.AmetysObject; 086import org.ametys.plugins.repository.AmetysObjectIterable; 087import org.ametys.plugins.repository.AmetysObjectResolver; 088import org.ametys.plugins.repository.AmetysRepositoryException; 089import org.ametys.plugins.repository.CollectionIterable; 090import org.ametys.plugins.repository.ModifiableAmetysObject; 091import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 092import org.ametys.plugins.repository.RepositoryConstants; 093import org.ametys.plugins.repository.UnknownAmetysObjectException; 094import org.ametys.plugins.repository.jcr.JCRAmetysObject; 095import org.ametys.plugins.repository.jcr.NodeTypeHelper; 096import org.ametys.plugins.repository.provider.AbstractRepository; 097import org.ametys.plugins.repository.provider.JackrabbitRepository; 098import org.ametys.plugins.repository.provider.WorkspaceSelector; 099import org.ametys.plugins.repository.query.expression.Expression; 100import org.ametys.plugins.repository.query.expression.Expression.Operator; 101import org.ametys.plugins.repository.query.expression.StringExpression; 102import org.ametys.plugins.workspaces.ObservationConstants; 103import org.ametys.plugins.workspaces.catalog.CatalogSiteType; 104import org.ametys.plugins.workspaces.categories.Category; 105import org.ametys.plugins.workspaces.categories.CategoryProviderExtensionPoint; 106import org.ametys.plugins.workspaces.members.JCRProjectMember; 107import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType; 108import org.ametys.plugins.workspaces.members.ProjectMemberManager; 109import org.ametys.plugins.workspaces.members.ProjectMemberManager.ProjectMember; 110import org.ametys.plugins.workspaces.project.modules.WorkspaceModule; 111import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 112import org.ametys.plugins.workspaces.project.objects.Project; 113import org.ametys.plugins.workspaces.project.objects.Project.InscriptionStatus; 114import org.ametys.plugins.workspaces.project.rights.ProjectRightHelper; 115import org.ametys.plugins.workspaces.tags.ProjectTagProviderExtensionPoint; 116import org.ametys.plugins.workspaces.util.StatisticColumn; 117import org.ametys.plugins.workspaces.util.StatisticsColumnType; 118import org.ametys.runtime.config.Config; 119import org.ametys.runtime.i18n.I18nizableText; 120import org.ametys.runtime.plugin.component.PluginAware; 121import org.ametys.web.repository.page.ModifiablePage; 122import org.ametys.web.repository.page.Page; 123import org.ametys.web.repository.page.PageQueryHelper; 124import org.ametys.web.repository.page.SitemapElement; 125import org.ametys.web.repository.site.Site; 126import org.ametys.web.repository.site.SiteDAO; 127import org.ametys.web.repository.site.SiteManager; 128import org.ametys.web.repository.sitemap.Sitemap; 129import org.ametys.web.site.SiteConfigurationManager; 130 131/** 132 * Helper component for managing project workspaces 133 */ 134public class ProjectManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable, PluginAware, Initializable, Observer 135{ 136 /** Avalon Role */ 137 public static final String ROLE = ProjectManager.class.getName(); 138 139 /** Constant for the {@link Cache} id (the {@link Cache} is in {@link CacheType#REQUEST REQUEST} attribute) for the {@link Project}s objects 140 * in cache by {@link RequestProjectCacheKey} (composition of project name and workspace name). */ 141 public static final String REQUEST_PROJECTBYID_CACHE = ProjectManager.class.getName() + "$ProjectById"; 142 143 /** Constant for the {@link Cache} id for the {@link Project} ids (as {@link String}s) in cache by project name (for whole application). */ 144 public static final String MEMORY_PROJECTIDBYNAMECACHE = ProjectManager.class.getName() + "$UUID"; 145 146 /** Constant for the {@link Cache} id (the {@link Cache} is in {@link CacheType#REQUEST REQUEST} attribute) for the {@link Page}s objects 147 * in cache by {@link RequestModuleCacheKey} (composition of project name and module name). */ 148 public static final String REQUEST_PAGESBYPROJECTANDMODULE_CACHE = ProjectManager.class.getName() + "$PagesByModule"; 149 150 /** Constant for the {@link Cache} id for the {@link Project} ids (as {@link String}s) in cache by project name (for whole application). */ 151 public static final String MEMORY_PAGESBYIDCACHE = ProjectManager.class.getName() + "$PageUUID"; 152 153 /** Constant for the {@link Cache} id for the {@link Project} ids (as {@link String}s) in cache by site name (for whole application). */ 154 public static final String MEMORY_SITEASSOCIATION_CACHE = ProjectManager.class.getName() + "$SiteAssociation"; 155 156 /** Workspaces plugin node name */ 157 private static final String __WORKSPACES_PLUGIN_NODE_NAME = "workspaces"; 158 159 /** Workspaces plugin node name */ 160 private static final String __WORKSPACES_PLUGIN_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":unstructured"; 161 162 /** The name of the projects root node */ 163 private static final String __PROJECTS_ROOT_NODE_NAME = "projects"; 164 165 /** The type of the projects root node */ 166 private static final String __PROJECTS_ROOT_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":unstructured"; 167 168 /** Constants for tags metadata */ 169 private static final String __PROJECTS_TAGS_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":tags"; 170 171 /** Constants for places metadata */ 172 private static final String __PROJECTS_PLACES_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":places"; 173 174 private static final String __PAGE_MODULES_VALUE = "workspaces-modules"; 175 176 private static final String __IS_CACHE_FILLED = "###iscachefilled###"; 177 178 /** Ametys object resolver */ 179 protected AmetysObjectResolver _resolver; 180 181 /** The i18n utils. */ 182 protected I18nUtils _i18nUtils; 183 184 /** Site manager */ 185 protected SiteManager _siteManager; 186 187 /** Site DAO */ 188 protected SiteDAO _siteDao; 189 190 /** Site configuration manager */ 191 protected SiteConfigurationManager _siteConfigurationManager; 192 193 /** Module Managers EP */ 194 protected WorkspaceModuleExtensionPoint _moduleManagerEP; 195 196 /** Helper for user population */ 197 protected PopulationContextHelper _populationContextHelper; 198 199 /** Helper for group directory's context */ 200 protected GroupDirectoryContextHelper _groupDirectoryContextHelper; 201 202 /** The project members' manager */ 203 protected ProjectMemberManager _projectMemberManager; 204 205 /** Avalon context */ 206 protected Context _context; 207 208 private ObservationManager _observationManager; 209 210 private CurrentUserProvider _currentUserProvider; 211 212 private ProjectMemberManager _projectMembers; 213 214 private String _pluginName; 215 216 private ProjectRightHelper _projectRightHelper; 217 218 private ProjectTagProviderExtensionPoint _projectTagProviderEP; 219 220 private CategoryProviderExtensionPoint _categoryProviderEP; 221 222 private UserHelper _userHelper; 223 224 private AbstractCacheManager _cacheManager; 225 226 private UserAndGroupSearchManager _userAndGroupSearchManager; 227 228 private JackrabbitRepository _repository; 229 230 private WorkspaceSelector _workspaceSelector; 231 232 private RightManager _rightManager; 233 234 private GroupDirectoryContextHelper _directoryContextHelper; 235 236 @Override 237 public void contextualize(Context context) throws ContextException 238 { 239 _context = context; 240 } 241 242 @Override 243 public void service(ServiceManager manager) throws ServiceException 244 { 245 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 246 _repository = (JackrabbitRepository) manager.lookup(AbstractRepository.ROLE); 247 _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE); 248 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 249 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 250 _siteDao = (SiteDAO) manager.lookup(SiteDAO.ROLE); 251 _siteConfigurationManager = (SiteConfigurationManager) manager.lookup(SiteConfigurationManager.ROLE); 252 _projectMembers = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 253 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 254 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 255 _moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 256 _projectRightHelper = (ProjectRightHelper) manager.lookup(ProjectRightHelper.ROLE); 257 _projectTagProviderEP = (ProjectTagProviderExtensionPoint) manager.lookup(ProjectTagProviderExtensionPoint.ROLE); 258 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 259 _categoryProviderEP = (CategoryProviderExtensionPoint) manager.lookup(CategoryProviderExtensionPoint.ROLE); 260 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 261 _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE); 262 _groupDirectoryContextHelper = (GroupDirectoryContextHelper) manager.lookup(GroupDirectoryContextHelper.ROLE); 263 _projectMemberManager = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 264 _userAndGroupSearchManager = (UserAndGroupSearchManager) manager.lookup(UserAndGroupSearchManager.ROLE); 265 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 266 _directoryContextHelper = (GroupDirectoryContextHelper) manager.lookup(GroupDirectoryContextHelper.ROLE); 267 } 268 269 public void initialize() throws Exception 270 { 271 _createCaches(); 272 _observationManager.registerObserver(this); 273 } 274 275 @Override 276 public void setPluginInfo(String pluginName, String featureName, String id) 277 { 278 _pluginName = pluginName; 279 } 280 281 /** 282 * Enumeration for the profile to assign for new module 283 */ 284 public enum ProfileForNewModule 285 { 286 /** No profile assigned for new modules */ 287 NONE, 288 289 /** Default profile assigned for new modules */ 290 DEFAULT_MEMBER_PROFILE 291 292 } 293 294 /** 295 * Retrieves all projects 296 * @return the projects 297 */ 298 public AmetysObjectIterable<Project> getProjects() 299 { 300 return getProjects(true); 301 } 302 303 /** 304 * Retrieves all projects 305 * @param onlyWorking true to retrieve only working projects with non null sites 306 * @return the projects 307 */ 308 public AmetysObjectIterable<Project> getProjects(boolean onlyWorking) 309 { 310 // As cache is computed from default JCR workspace, we need to filter on sites that exist into the current JCR workspace 311 Set<Project> projects = _getUUIDCache().values().stream() 312 .filter(_resolver::hasAmetysObjectForId) 313 .map(_resolver::<Project>resolveById) 314 // If needed, check if the site is not null, as it can happen if site was deleted from admin side 315 .filter(project -> !onlyWorking || Objects.nonNull(project.getSite())) 316 .collect(Collectors.toSet()); 317 318 return new CollectionIterable<>(projects); 319 } 320 321 /** 322 * Retrieves projects filtered by categories 323 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 324 * @return the projects 325 */ 326 public List<Project> getProjects(Set<String> filteredCategories) 327 { 328 return getProjects(filteredCategories, null, null, true); 329 } 330 331 /** 332 * Retrieves projects filtered by categories and/or keywords 333 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 334 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 335 * @param anyMatch true to get projects matching categories OR keywords OR pattern 336 * @return the projects 337 */ 338 public List<Project> getProjects(Set<String> filteredCategories, Set<String> filteredKeywords, boolean anyMatch) 339 { 340 return getProjects(filteredCategories, filteredKeywords, null, anyMatch); 341 } 342 343 /** 344 * Retrieves projects filtered by categories and/or keywords and/or pattern 345 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 346 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 347 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 348 * @param anyMatch true to get projects matching categories OR keywords OR pattern 349 * @return the projects 350 */ 351 public List<Project> getProjects(Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch) 352 { 353 return getProjects(filteredCategories, filteredKeywords, null, anyMatch, false); 354 } 355 356 /** 357 * Retrieves projects filtered by categories and/or keywords and/or pattern 358 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 359 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 360 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 361 * @param anyMatch true to get projects matching categories OR keywords OR pattern 362 * @param excludePrivate true to exclude private projects 363 * @return the projects 364 */ 365 public List<Project> getProjects(Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch, boolean excludePrivate) 366 { 367 Predicate<Project> matchCategories = p -> filteredCategories == null || filteredCategories.isEmpty() || !Collections.disjoint(p.getCategories(), filteredCategories); 368 Predicate<Project> matchKeywords = p -> filteredKeywords == null || filteredKeywords.isEmpty() || !Collections.disjoint(Arrays.asList(p.getKeywords()), filteredKeywords); 369 370 Pattern patternFilter = StringUtils.isNotEmpty(pattern) ? Pattern.compile(pattern, Pattern.CASE_INSENSITIVE) : null; 371 Predicate<Project> matchPattern = p -> patternFilter == null 372 || p.getTitle() != null && patternFilter.matcher(p.getTitle()).find() 373 || p.getDescription() != null && patternFilter.matcher(p.getDescription()).find(); 374 375 Predicate<Project> fullMatch = null; 376 if (anyMatch) 377 { 378 fullMatch = matchCategories.or(matchKeywords).or(matchPattern); 379 } 380 else 381 { 382 fullMatch = matchCategories.and(matchKeywords).and(matchPattern); 383 } 384 385 Predicate<Project> matchStatus = p -> !excludePrivate || p.getInscriptionStatus() != InscriptionStatus.PRIVATE; 386 387 return getProjects() 388 .stream() 389 .filter(matchStatus) 390 .filter(fullMatch) 391 .collect(Collectors.toList()); 392 } 393 394 /** 395 * Get the projects categories 396 * @return the projects categories 397 */ 398 public Set<Category> getProjectsCategories() 399 { 400 return getProjects().stream() 401 .map(Project::getCategories) 402 .flatMap(Collection::stream) 403 .map(id -> _categoryProviderEP.getTag(id, null)) 404 .filter(Objects::nonNull) 405 .collect(Collectors.toSet()); 406 } 407 408 /** 409 * Get the user's projects categories 410 * @param user the user 411 * @return the user's projects categories 412 */ 413 public Set<Category> getUserProjectsCategories(UserIdentity user) 414 { 415 return getUserProjects(user).keySet() 416 .stream() 417 .map(Project::getCategories) 418 .flatMap(Collection::stream) 419 .map(id -> _categoryProviderEP.getTag(id, null)) 420 .filter(Objects::nonNull) 421 .collect(Collectors.toSet()); 422 } 423 424 /** 425 * Get the projects modules 426 * @return the projects modules 427 */ 428 public Set<WorkspaceModule> getProjectsModules() 429 { 430 return getProjects().stream() 431 .map(Project::getModules) 432 .flatMap(Arrays::stream) 433 .map(id -> _moduleManagerEP.<WorkspaceModule>getModule(id)) 434 .filter(Objects::nonNull) 435 .collect(Collectors.toSet()); 436 } 437 438 /** 439 * Get the projects modules 440 * @param user the user 441 * @return the projects modules 442 */ 443 public Set<WorkspaceModule> getUserProjectsModules(UserIdentity user) 444 { 445 return getUserProjects(user).keySet() 446 .stream() 447 .map(Project::getModules) 448 .flatMap(Arrays::stream) 449 .map(id -> _moduleManagerEP.<WorkspaceModule>getModule(id)) 450 .filter(Objects::nonNull) 451 .collect(Collectors.toSet()); 452 } 453 454 /** 455 * Retrieves all projects for client side 456 * @return the projects 457 */ 458 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 459 public List<Map<String, Object>> getProjectsForClientSide() 460 { 461 return getProjects(false) 462 .stream() 463 .map(p -> getProjectProperties(p)) 464 .collect(Collectors.toList()); 465 } 466 467 468 /** 469 * Retrieves a project by its name 470 * @param projectName The project name 471 * @return the project or <code>null</code> if not found 472 */ 473 public Project getProject(String projectName) 474 { 475 if (StringUtils.isBlank(projectName)) 476 { 477 return null; 478 } 479 480 Request request = _getRequest(); 481 if (request == null) 482 { 483 // There is no request to store cache 484 return _computeProject(projectName); 485 } 486 487 Cache<RequestProjectCacheKey, Project> projectsCache = _getRequestProjectCache(); 488 489 // The site key in the cache is of the form {site + workspace}. 490 String currentWorkspace = _workspaceSelector.getWorkspace(); 491 RequestProjectCacheKey projectKey = RequestProjectCacheKey.of(projectName, currentWorkspace); 492 493 try 494 { 495 Project project = projectsCache.get(projectKey, __ -> _computeProject(projectName)); 496 return project; 497 } 498 catch (CacheException e) 499 { 500 if (e.getCause() instanceof UnknownAmetysObjectException) 501 { 502 throw new UnknownAmetysObjectException(e.getMessage()); 503 } 504 else 505 { 506 throw e; 507 } 508 } 509 } 510 511 /** 512 * Get the list of existing projects that current user is a member of. 513 * @return a map of the projects 514 */ 515 @Callable 516 public Map<String, Object> getUserProjects2JSON() 517 { 518 Map<String, Object> result = new HashMap<>(); 519 520 UserIdentity user = _currentUserProvider.getUser(); 521 522 List<Map<String, Object>> projects = new ArrayList<>(); 523 Map<Project, MemberType> userProjects = getUserProjects(user); 524 525 projects.addAll(userProjects.keySet() 526 .stream() 527 .map(p -> getProjectProperties(p)) 528 .collect(Collectors.toList())); 529 530 result.put("projects", projects); 531 return result; 532 } 533 534 /** 535 * Get the user's projects 536 * @param user the user 537 * @return the user's projects 538 */ 539 public Map<Project, MemberType> getUserProjects(UserIdentity user) 540 { 541 return getUserProjects(user, Set.of()); 542 } 543 544 /** 545 * Get the user's projects filtered by categories 546 * @param user the user 547 * @param filteredCategories the filtered categories. Can be empty to no filter by categories 548 * @return the user's projects 549 */ 550 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories) 551 { 552 return getUserProjects(user, filteredCategories, null, null, true); 553 } 554 555 /** 556 * Get the user's projects filtered by categories OR keywords 557 * @param user the user 558 * @param filteredCategories the filtered categories. Can be empty to no filter by categories 559 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords 560 * @return the user's projects 561 */ 562 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories, Set<String> filteredKeywords) 563 { 564 return getUserProjects(user, filteredCategories, filteredKeywords, null, true); 565 } 566 567 /** 568 * Get the user's projects filtered by categories, keywords and/or pattern 569 * @param user the user 570 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 571 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 572 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 573 * @param anyMatch true to get projects matching categories OR keywords OR pattern 574 * @return the user's projects 575 */ 576 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch) 577 { 578 return getUserProjects(user, filteredCategories, filteredKeywords, pattern, true, false); 579 } 580 581 /** 582 * Get the user's projects filtered by categories, keywords and/or pattern 583 * @param user the user 584 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 585 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 586 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 587 * @param anyMatch true to get projects matching categories OR keywords OR pattern 588 * @param excludePrivate true to exclude private projects 589 * @return the user's projects 590 */ 591 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch, boolean excludePrivate) 592 { 593 Map<Project, MemberType> userProjects = new HashMap<>(); 594 595 List<Project> projects = getProjects(filteredCategories, filteredKeywords, pattern, anyMatch, excludePrivate); 596 for (Project project : projects) 597 { 598 ProjectMember member = _projectMembers.getProjectMember(project, user); 599 if (member != null) 600 { 601 userProjects.put(project, member.getType()); 602 } 603 } 604 605 return userProjects; 606 } 607 608 /** 609 * Get the projects managed by the user 610 * @param user the user 611 * @return the projects for which the user is a manager 612 */ 613 public List<Project> getManagedProjects(UserIdentity user) 614 { 615 return getManagedProjects(user, Set.of()); 616 } 617 618 /** 619 * Get the projects managed by the user 620 * @param user the user 621 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 622 * @return the projects for which the user is a manager 623 */ 624 public List<Project> getManagedProjects(UserIdentity user, Set<String> filteredCategories) 625 { 626 return getProjects(filteredCategories) 627 .stream() 628 .filter(p -> ArrayUtils.contains(p.getManagers(), user)) 629 .collect(Collectors.toList()); 630 } 631 632 /** 633 * Returns true if the given project exists. 634 * @param projectName the project name. 635 * @return true if the given project exists. 636 */ 637 public boolean hasProject(String projectName) 638 { 639 Map<String, String> uuidCache = _getUUIDCache(); 640 if (uuidCache.containsKey(projectName)) 641 { 642 // As cache is computed from default JCR workspace, we need to check if the project exists into the current JCR workspace 643 return _resolver.hasAmetysObjectForId(uuidCache.get(projectName)); 644 } 645 return false; 646 } 647 648 /** 649 * Get all managers 650 * @return the managers 651 */ 652 public Set<UserIdentity> getManagers() 653 { 654 return getProjects() 655 .stream() 656 .map(Project::getManagers) 657 .flatMap(Arrays::stream) 658 .collect(Collectors.toSet()); 659 } 660 661 /** 662 * Determines if the current user is a manager of at least one project 663 * @return true if the user is a manager 664 */ 665 public boolean isManager() 666 { 667 return isManager(_currentUserProvider.getUser()); 668 } 669 670 /** 671 * Determines if the user is a manager of at least one project 672 * @param user the user 673 * @return true if the user is a manager 674 */ 675 public boolean isManager(UserIdentity user) 676 { 677 AmetysObjectIterable<Project> projects = getProjects(); 678 for (Project project : projects) 679 { 680 if (isManager(project, user)) 681 { 682 return true; 683 } 684 } 685 return false; 686 } 687 688 /** 689 * Determines if the user is a manager of the project 690 * @param projectName the project name 691 * @param user the user 692 * @return true if the user is a manager 693 */ 694 public boolean isManager(String projectName, UserIdentity user) 695 { 696 Project project = getProject(projectName); 697 if (project != null) 698 { 699 return isManager(project, user); 700 } 701 return false; 702 } 703 704 /** 705 * Determines if the user is a manager of the project 706 * @param project the project 707 * @param user the user 708 * @return true if the user is a manager 709 */ 710 public boolean isManager(Project project, UserIdentity user) 711 { 712 return ArrayUtils.contains(project.getManagers(), user); 713 } 714 715 /** 716 * Can the current user access backoffice on the site of the current project 717 * @param project The non null project to analyse 718 * @return true if the user can access to the backoffice 719 */ 720 public boolean canAccessBO(Project project) 721 { 722 Site site = project.getSite(); 723 if (site == null) 724 { 725 return false; 726 } 727 728 Request request = ContextHelper.getRequest(_context); 729 String currentSiteName = (String) request.getAttribute("siteName"); 730 try 731 { 732 request.setAttribute("siteName", site.getName()); // Setting temporarily this attribute to check user rights on any object on this site 733 return !_rightManager.getUserRights(_currentUserProvider.getUser(), "/cms").isEmpty(); 734 } 735 finally 736 { 737 request.setAttribute("siteName", currentSiteName); 738 } 739 } 740 741 /** 742 * Can the current user leave the project 743 * @param project The non null project to analyse 744 * @return true if the user can leave the project 745 */ 746 public boolean canLeaveProject(Project project) 747 { 748 if (project == null) 749 { 750 return false; 751 } 752 753 Site site = project.getSite(); 754 if (site == null) 755 { 756 return false; 757 } 758 759 UserIdentity userIdentity = _currentUserProvider.getUser(); 760 761 // As a user in a group can't leave the project, check if he is registered as User member 762 Set<ProjectMember> members = _projectMemberManager.getProjectMembers(project, false); 763 ProjectMember projectMember = members.stream() 764 .filter(member -> MemberType.USER == member.getType()) 765 .filter(member -> userIdentity.equals(member.getUser().getIdentity())) 766 .findFirst() 767 .orElse(null); 768 769 // The user is either on a group, or has not been found 770 if (projectMember == null) 771 { 772 return false; 773 } 774 775 // The user is the only remaining manager, and therefore can not leave the project 776 if (_projectMemberManager.isOnlyManager(project, userIdentity)) 777 { 778 return false; 779 } 780 781 return true; 782 } 783 784 /** 785 * Retrieves the mapping of all the projects name with their title on which the current user has access 786 * @return the map (projectName, projectTitle) for all projects 787 */ 788 @Callable 789 public List<Map<String, Object>> getUserProjectsData() 790 { 791 return getUserProjects(_currentUserProvider.getUser()) 792 .keySet() 793 .stream() 794 .map(p -> _project2json(p)) 795 .collect(Collectors.toList()); 796 } 797 798 /** 799 * Retrieves the users that have not been yet added to a project with a given criteria 800 * @param projectName the project name 801 * @param limit limit of request 802 * @param criteria the criteria of the search 803 * @param previousSearchData the previous search data to compute offset. Null if first search 804 * @return list of users 805 */ 806 @SuppressWarnings("unchecked") 807 @Callable 808 public Map<String, Object> searchUserByProject(String projectName, int limit, String criteria, Map<String, Object> previousSearchData) 809 { 810 811 Map<String, Object> results = new HashMap<>(); 812 Project project = this.getProject(projectName); 813 Site site = project.getSite(); 814 815 Set<String> projectMemberList = _projectMemberManager.getProjectMembers(project, false) 816 .stream() 817 .map(member -> 818 { 819 if (member.getType() == MemberType.USER) 820 { 821 return UserIdentity.userIdentityToString(member.getUser().getIdentity()); 822 } 823 else 824 { 825 GroupIdentity groupIdentityAsString = member.getGroup().getIdentity(); 826 return GroupIdentity.groupIdentityToString(groupIdentityAsString); 827 } 828 }) 829 .collect(Collectors.toSet()); 830 831 Set<String> contexts = new HashSet<>(Arrays.asList("/sites/" + site.getName(), "/sites-fo/" + site.getName())); 832 833 Map<String, Object> params = new HashMap<>(); 834 params.put("pattern", criteria); 835 836 Map<String, Object> result = new HashMap<>(); 837 Map<String, Object> searchData = previousSearchData; 838 List<Map<String, Object>> memberList = new ArrayList<>(); 839 840 Set<String> groupDirectories = _directoryContextHelper.getGroupDirectoriesOnContexts(contexts); 841 Set<String> userPopulations = getPopulation(site, true); 842 843 do 844 { 845 result = _userAndGroupSearchManager.searchUsersAndGroup(userPopulations, groupDirectories, limit - memberList.size(), searchData, params, true); 846 List<Map<String, Object>> filteredMembers = ((List<Map<String, Object>>) result.get("results")) 847 .stream() 848 .filter(member -> 849 { 850 return !projectMemberList.contains(member.get("login") + "#" + member.get("populationId")) && !projectMemberList.contains(member.get("id") + "#" + member.get("groupDirectory")); 851 }).collect(Collectors.toList()); 852 searchData = (Map<String, Object>) result.get("searchData"); 853 memberList.addAll(filteredMembers); 854 } 855 while (!result.containsKey("finished") && memberList.size() < limit); 856 857 results.put("searchData", searchData); 858 results.put("memberList", memberList); 859 return results; 860 } 861 862 /** 863 * Get the populations of the project 864 * @param site the project site 865 * @param excludeConfigurationPopulations true to exclude populations configured in the catalog site or in the project site 866 * @return the populations of the project 867 */ 868 public Set<String> getPopulation(Site site, boolean excludeConfigurationPopulations) 869 { 870 Set<String> contexts = new HashSet<>(Arrays.asList("/sites/" + site.getName(), "/sites-fo/" + site.getName())); 871 872 Set<String> userPopulations = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, true); 873 874 if (excludeConfigurationPopulations) 875 { 876 Site catalogSite = _siteManager.getSite(getCatalogSiteName()); 877 878 // Get the excluded population configuration 879 String excludedPopulationsString = catalogSite.getValue(CatalogSiteType.PROJECT_EXCLUDED_POPULATIONS_SITE_PARAM); 880 881 // Check if project site overrides catalog configuration 882 if (site.getValue(ProjectWorkspaceSiteType.PROJECT_OVERRIDE_EXCLUDED_POPULATIONS_SITE_PARAM, false, false)) 883 { 884 excludedPopulationsString = site.getValue(ProjectWorkspaceSiteType.PROJECT_EXCLUDED_POPULATIONS_PROJECT_SITE_PARAM); 885 } 886 887 List<String> excludedPopulationsList = Arrays.asList(StringUtils.split(StringUtils.defaultString(excludedPopulationsString), ",")); 888 889 if (!excludedPopulationsList.isEmpty()) 890 { 891 userPopulations.removeAll(excludedPopulationsList); 892 } 893 } 894 895 return userPopulations; 896 } 897 898 /** 899 * Retrieves the mapping of all the projects name with their title (regarless user rights) 900 * @return the map (projectName, projectTitle) for all projects 901 */ 902 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 903 public List<Map<String, Object>> getProjectsData() 904 { 905 return getProjects() 906 .stream() 907 .map(p -> _project2json(p)) 908 .collect(Collectors.toList()); 909 } 910 911 /** 912 * Get the project's main properties as json object 913 * @param project the project 914 * @return the json representation of project 915 */ 916 protected Map<String, Object> _project2json(Project project) 917 { 918 Map<String, Object> json = new HashMap<>(); 919 920 json.put("id", project.getId()); 921 json.put("name", project.getName()); 922 json.put("title", project.getTitle()); 923 json.put("url", getProjectUrl(project, StringUtils.EMPTY)); 924 925 return json; 926 } 927 /** 928 * Retrieves the project names 929 * @return the project names 930 */ 931 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 932 public Collection<String> getProjectNames() 933 { 934 // As cache is computed from default JCR workspace, we need to filter on sites that exist into the current JCR workspace 935 return _getUUIDCache().entrySet().stream() 936 .filter(e -> _resolver.hasAmetysObjectForId(e.getValue())) 937 .map(Map.Entry::getKey) 938 .collect(Collectors.toList()); 939 } 940 941 /** 942 * Return the root for projects 943 * The root node will be created if necessary 944 * @return The root for projects 945 */ 946 public ModifiableTraversableAmetysObject getProjectsRoot() 947 { 948 try 949 { 950 ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins"); 951 ModifiableTraversableAmetysObject workspacesPluginNode = _getOrCreateObject(pluginsNode, __WORKSPACES_PLUGIN_NODE_NAME, __WORKSPACES_PLUGIN_NODE_TYPE); 952 return _getOrCreateObject(workspacesPluginNode, __PROJECTS_ROOT_NODE_NAME, __PROJECTS_ROOT_NODE_TYPE); 953 } 954 catch (AmetysRepositoryException e) 955 { 956 throw new AmetysRepositoryException("Error getting the projects root node.", e); 957 } 958 } 959 960 /** 961 * Retrieves the standard information of a project 962 * @param projectId Identifier of the project 963 * @return The map of information 964 */ 965 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 966 public Map<String, Object> getProjectProperties(String projectId) 967 { 968 return getProjectProperties((Project) _resolver.resolveById(projectId)); 969 } 970 971 /** 972 * Retrieves the standard information of a project 973 * @param project The project 974 * @return The map of information 975 */ 976 public Map<String, Object> getProjectProperties(Project project) 977 { 978 Map<String, Object> info = new HashMap<>(); 979 980 info.put("id", project.getId()); 981 info.put("name", project.getName()); 982 info.put("type", "project"); 983 984 info.put("title", project.getTitle()); 985 info.put("description", project.getDescription()); 986 info.put("inscriptionStatus", project.getInscriptionStatus().toString()); 987 info.put("defaultProfile", project.getDefaultProfile()); 988 989 info.put("creationDate", project.getCreationDate()); 990 991 // check if the project workspace configuration is valid 992 Site site = project.getSite(); 993 boolean valid = site != null && _siteConfigurationManager.isSiteConfigurationValid(site); 994 995 Set<String> categories = project.getCategories(); 996 info.put("categories", categories.stream() 997 .map(c -> _categoryProviderEP.getTag(c, new HashMap<>())) 998 .filter(Objects::nonNull) 999 .map(t -> _tag2json(t)) 1000 .collect(Collectors.toList())); 1001 1002 Set<String> tags = project.getTags(); 1003 info.put("tags", tags.stream() 1004 .map(c -> _projectTagProviderEP.getTag(c, new HashMap<>())) 1005 .filter(Objects::nonNull) 1006 .map(t -> _tag2json(t)) 1007 .collect(Collectors.toList())); 1008 1009 info.put("valid", valid); 1010 1011 UserIdentity[] managers = project.getManagers(); 1012 info.put("managers", Arrays.stream(managers) 1013 .map(u -> _userHelper.user2json(u)) 1014 .collect(Collectors.toList())); 1015 1016 Map<String, String> siteProps = new HashMap<>(); 1017 // site map with id ,name, title and url property 1018 // { id: site id, name: site name, title: site title, url: site url } 1019 if (site != null) 1020 { 1021 siteProps.put("id", site.getId()); 1022 siteProps.put("name", site.getName()); 1023 siteProps.put("title", site.getTitle()); 1024 siteProps.put("url", site.getUrl()); 1025 } 1026 info.put("site", siteProps); 1027 1028 return info; 1029 } 1030 1031 private Map<String, Object> _tag2json(Tag tag) 1032 { 1033 Map<String, Object> json = new HashMap<>(); 1034 json.put("id", tag.getId()); 1035 json.put("name", tag.getName()); 1036 json.put("title", tag.getTitle()); 1037 return json; 1038 } 1039 1040 /** 1041 * Get the project URL. 1042 * @param project The project 1043 * @param defaultValue The default value to use if there is no site 1044 * @return The project URL if a site is configured, otherwise return the default value. 1045 */ 1046 public String getProjectUrl(Project project, String defaultValue) 1047 { 1048 Site site = project.getSite(); 1049 if (site == null) 1050 { 1051 return defaultValue; 1052 } 1053 else 1054 { 1055 return site.getUrl(); 1056 } 1057 } 1058 1059 /** 1060 * Create a project 1061 * @param name The project name 1062 * @param title The project title 1063 * @param description The project description 1064 * @param emailList Project mailing list 1065 * @param inscriptionStatus The inscription status of the project 1066 * @param defaultProfile The default profile for new members 1067 * @return A map containing the id of the new project or an error key. 1068 */ 1069 @Callable 1070 public Map<String, Object> createProject(String name, String title, String description, String emailList, String inscriptionStatus, String defaultProfile) 1071 { 1072 checkRightsForProjectCreation(InscriptionStatus.valueOf(inscriptionStatus.toUpperCase()), null); 1073 1074 Map<String, Object> result = new HashMap<>(); 1075 List<String> errors = new ArrayList<>(); 1076 1077 Map<String, Object> additionalValues = new HashMap<>(); 1078 additionalValues.put("description", description); 1079 additionalValues.put("emailList", emailList); 1080 additionalValues.put("inscriptionStatus", inscriptionStatus); 1081 additionalValues.put("defaultProfile", defaultProfile); 1082 1083 Project project = createProject(name, title, additionalValues, null, errors); 1084 1085 if (CollectionUtils.isEmpty(errors)) 1086 { 1087 result.put("id", project.getId()); 1088 } 1089 else 1090 { 1091 result.put("error", errors.get(0)); 1092 } 1093 1094 return result; 1095 } 1096 1097 /** 1098 * Create a project 1099 * @param name The project name 1100 * @param title The project title 1101 * @param additionalValues A list of optional additional values. Accepted values are : description, mailingList, inscriptionStatus, defaultProfile, tags, categoryTags, keywords and language 1102 * @param modulesIds The list of modules to activate. Can be null to activate all modules 1103 * @param errors A list that will be populated with the encountered errors. If null, errors will not be tracked. 1104 * @return The id of the new project 1105 */ 1106 public Project createProject(String name, String title, Map<String, Object> additionalValues, Set<String> modulesIds, List<String> errors) 1107 { 1108 if (StringUtils.isEmpty(title)) 1109 { 1110 throw new IllegalArgumentException(String.format("Cannot create project. Title is mandatory")); 1111 } 1112 1113 ModifiableTraversableAmetysObject projectsRoot = getProjectsRoot(); 1114 1115 // Project name should be unique 1116 if (hasProject(name)) 1117 { 1118 if (getLogger().isWarnEnabled()) 1119 { 1120 getLogger().warn(String.format("A project with the name '%s' already exists", name)); 1121 } 1122 1123 if (errors != null) 1124 { 1125 errors.add("project-exists"); 1126 } 1127 1128 return null; 1129 } 1130 1131 Project project = projectsRoot.createChild(name, Project.NODE_TYPE); 1132 project.setTitle(title); 1133 String description = (String) additionalValues.getOrDefault("description", null); 1134 if (StringUtils.isNotEmpty(description)) 1135 { 1136 project.setDescription(description); 1137 } 1138 String mailingList = (String) additionalValues.getOrDefault("emailList", null); 1139 if (StringUtils.isNotEmpty(mailingList)) 1140 { 1141 project.setMailingList(mailingList); 1142 } 1143 1144 String inscriptionStatus = (String) additionalValues.getOrDefault("inscriptionStatus", null); 1145 if (StringUtils.isNotEmpty(inscriptionStatus)) 1146 { 1147 project.setInscriptionStatus(inscriptionStatus); 1148 } 1149 1150 String defaultProfile = (String) additionalValues.getOrDefault("defaultProfile", null); 1151 if (StringUtils.isNotEmpty(defaultProfile)) 1152 { 1153 project.setDefaultProfile(defaultProfile); 1154 } 1155 1156 @SuppressWarnings("unchecked") 1157 List<String> tags = (List<String>) additionalValues.getOrDefault("tags", null); 1158 if (tags != null) 1159 { 1160 project.setTags(tags); 1161 } 1162 @SuppressWarnings("unchecked") 1163 List<String> categoryTags = (List<String>) additionalValues.getOrDefault("categoryTags", null); 1164 if (categoryTags != null) 1165 { 1166 project.setCategoryTags(categoryTags); 1167 } 1168 1169 @SuppressWarnings("unchecked") 1170 List<String> keywords = (List<String>) additionalValues.getOrDefault("keywords", null); 1171 if (keywords != null) 1172 { 1173 project.setKeywords(keywords.toArray(new String[keywords.size()])); 1174 } 1175 1176 project.setCreationDate(ZonedDateTime.now()); 1177 1178 // Create the project workspace = a site + a set of pages 1179 _createProjectWorkspace(project, errors); 1180 1181 activateModules(project, modulesIds, additionalValues); 1182 1183 if (CollectionUtils.isEmpty(errors)) 1184 { 1185 project.saveChanges(); 1186 1187 // Notify observers 1188 Map<String, Object> eventParams = new HashMap<>(); 1189 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 1190 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_ADDED, _currentUserProvider.getUser(), eventParams)); 1191 1192 } 1193 else 1194 { 1195 deleteProject(project); 1196 } 1197 1198 clearCaches(); 1199 1200 return project; 1201 } 1202 1203 /** 1204 * Edit a project 1205 * @param id The project identifier 1206 * @param title The title to set 1207 * @param description The description to set 1208 * @param mailingList Project mailing list 1209 * @param inscriptionStatus The inscription status of the project 1210 * @param defaultProfile The default profile for new members 1211 */ 1212 @Callable(rights = ProjectConstants.RIGHT_PROJECT_EDIT, context = "/admin") 1213 public void editProject(String id, String title, String description, String mailingList, String inscriptionStatus, String defaultProfile) 1214 { 1215 Project project = _resolver.resolveById(id); 1216 editProject(project, title, description, mailingList, inscriptionStatus, defaultProfile); 1217 } 1218 1219 /** 1220 * Edit a project 1221 * @param project The project 1222 * @param title The title to set 1223 * @param description The description to set 1224 * @param mailingList Project mailing list 1225 * @param inscriptionStatus The inscription status of the project 1226 * @param defaultProfile The default profile for new members 1227 */ 1228 public void editProject(Project project, String title, String description, String mailingList, String inscriptionStatus, String defaultProfile) 1229 { 1230 checkRightsForProjectEdition(project, InscriptionStatus.valueOf(inscriptionStatus.toUpperCase()), null); 1231 1232 project.setTitle(title); 1233 1234 if (StringUtils.isNotEmpty(description)) 1235 { 1236 project.setDescription(description); 1237 } 1238 else 1239 { 1240 project.removeDescription(); 1241 } 1242 1243 if (StringUtils.isNotEmpty(mailingList)) 1244 { 1245 project.setMailingList(mailingList); 1246 } 1247 else 1248 { 1249 project.removeMailingList(); 1250 } 1251 1252 project.setInscriptionStatus(inscriptionStatus); 1253 project.setDefaultProfile(defaultProfile); 1254 1255 project.saveChanges(); 1256 1257 // Notify observers 1258 Map<String, Object> eventParams = new HashMap<>(); 1259 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 1260 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_UPDATED, _currentUserProvider.getUser(), eventParams)); 1261 } 1262 1263 /** 1264 * Delete a list of project. 1265 * @param ids The ids of projects to delete 1266 * @return The ids of the deleted projects, unknowns projects and the deleted sites 1267 */ 1268 @Callable(rights = ProjectConstants.RIGHT_PROJECT_DELETE, context = "/admin") 1269 public Map<String, Object> deleteProjectsByIds(List<String> ids) 1270 { 1271 Map<String, Object> result = new HashMap<>(); 1272 List<Map<String, Object>> deleted = new ArrayList<>(); 1273 List<String> unknowns = new ArrayList<>(); 1274 1275 for (String id : ids) 1276 { 1277 try 1278 { 1279 Project project = _resolver.resolveById(id); 1280 1281 Map<String, Object> projectInfo = new HashMap<>(); 1282 projectInfo.put("id", id); 1283 projectInfo.put("title", project.getTitle()); 1284 projectInfo.put("sites", deleteProject(project)); 1285 1286 deleted.add(projectInfo); 1287 } 1288 catch (UnknownAmetysObjectException e) 1289 { 1290 getLogger().warn(String.format("Unable to delete the definition of id '%s', because it does not exist.", id), e); 1291 unknowns.add(id); 1292 } 1293 } 1294 1295 result.put("deleted", deleted); 1296 result.put("unknowns", unknowns); 1297 1298 return result; 1299 } 1300 1301 /** 1302 * Delete a project. 1303 * @param projects The list of projects to delete 1304 * @return list of deleted sites (each list entry contains a data map with 1305 * the id and the name of the delete site). 1306 */ 1307 public List<Map<String, String>> deleteProject(List<Project> projects) 1308 { 1309 List<Map<String, String>> deletedSitesInfo = new ArrayList<>(); 1310 1311 for (Project project : projects) 1312 { 1313 deletedSitesInfo.addAll(deleteProject(project)); 1314 } 1315 1316 return deletedSitesInfo; 1317 } 1318 1319 /** 1320 * Delete a project and its sites 1321 * @param project The project to delete 1322 * @return list of deleted sites (each list entry contains a data map with 1323 * the id and the name of the delete site). 1324 */ 1325 public List<Map<String, String>> deleteProject(Project project) 1326 { 1327 ModifiableAmetysObject parent = project.getParent(); 1328 1329 1330 // list of map entry with id, name and title property 1331 // { id: site id, name: site name } 1332 List<Map<String, String>> deletedSitesInfo = new ArrayList<>(); 1333 1334 Site site = project.getSite(); 1335 if (site != null) 1336 { 1337 try 1338 { 1339 Map<String, String> siteProps = new HashMap<>(); 1340 siteProps.put("id", site.getId()); 1341 siteProps.put("name", site.getName()); 1342 1343 _siteDao.deleteSite(site.getId()); 1344 deletedSitesInfo.add(siteProps); 1345 } 1346 catch (RepositoryException e) 1347 { 1348 String errorMsg = String.format("Error while trying to delete the site '%s' for the project '%s'.", site.getName(), project.getName()); 1349 getLogger().error(errorMsg, e); 1350 } 1351 } 1352 1353 String projectId = project.getId(); 1354 Collection<ProjectMember> projectMembers = _projectMembers.getProjectMembers(project, true); 1355 project.remove(); 1356 parent.saveChanges(); 1357 1358 // Notify observers 1359 Map<String, Object> eventParams = new HashMap<>(); 1360 eventParams.put(ObservationConstants.ARGS_PROJECT_ID, projectId); 1361 eventParams.put(ObservationConstants.ARGS_PROJECT_NAME, project.getName()); 1362 eventParams.put(ObservationConstants.ARGS_PROJECT_MEMBERS, projectMembers); 1363 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_DELETED, _currentUserProvider.getUser(), eventParams)); 1364 1365 clearCaches(); 1366 1367 return deletedSitesInfo; 1368 } 1369 1370 /** 1371 * Utility method to get or create an ametys object 1372 * @param <A> A sub class of AmetysObject 1373 * @param parent The parent object 1374 * @param name The ametys object name 1375 * @param type The ametys object type 1376 * @return ametys object 1377 * @throws AmetysRepositoryException if an repository error occurs 1378 */ 1379 private <A extends AmetysObject> A _getOrCreateObject(ModifiableTraversableAmetysObject parent, String name, String type) throws AmetysRepositoryException 1380 { 1381 A object; 1382 1383 if (parent.hasChild(name)) 1384 { 1385 object = parent.getChild(name); 1386 } 1387 else 1388 { 1389 object = parent.createChild(name, type); 1390 parent.saveChanges(); 1391 } 1392 1393 return object; 1394 } 1395 1396 /** 1397 * Get the project of an ametys object inside a project. 1398 * It can be an explorer node, or any type of resource in a module. 1399 * @param id The identifier of the ametys object 1400 * @return the project or null if not found 1401 */ 1402 public Project getParentProject(String id) 1403 { 1404 return getParentProject(_resolver.<AmetysObject>resolveById(id)); 1405 } 1406 1407 /** 1408 * Get the project of an ametys object inside a project. 1409 * It can be an explorer node, or any type of resource in a module. 1410 * @param object The ametys object 1411 * @return the project or null if not found 1412 */ 1413 public Project getParentProject(AmetysObject object) 1414 { 1415 AmetysObject ametysObject = object; 1416 // Go back to the local explorer root. 1417 do 1418 { 1419 ametysObject = ametysObject.getParent(); 1420 } 1421 while (ametysObject instanceof ExplorerNode); 1422 1423 if (!(ametysObject instanceof Project)) 1424 { 1425 getLogger().warn(String.format("No project found for ametys object with id '%s'", ametysObject.getId())); 1426 return null; 1427 } 1428 1429 return (Project) ametysObject; 1430 } 1431 1432 /** 1433 * Get the list of project names for a given site 1434 * @param siteName The site name 1435 * @return the list of project names 1436 */ 1437 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 1438 public List<String> getProjectsForSite(String siteName) 1439 { 1440 Cache<String, List<Pair<String, String>>> cache = _getMemorySiteAssociationCache(); 1441 if (cache.hasKey(siteName)) 1442 { 1443 return cache.get(siteName).stream() 1444 .map(p -> p.getRight()) 1445 .collect(Collectors.toList()); 1446 } 1447 else 1448 { 1449 List<String> projectNames = new ArrayList<>(); 1450 1451 if (_siteManager.hasSite(siteName)) 1452 { 1453 Site site = _siteManager.getSite(siteName); 1454 getProjectsForSite(site) 1455 .stream() 1456 .map(Project::getName) 1457 .forEach(projectNames::add); 1458 } 1459 1460 return projectNames; 1461 } 1462 } 1463 1464 /** 1465 * Get the list of project for a given site 1466 * @param site The site 1467 * @return the list of project 1468 */ 1469 public List<Project> getProjectsForSite(Site site) 1470 { 1471 Cache<String, List<Pair<String, String>>> cache = _getMemorySiteAssociationCache(); 1472 if (cache.hasKey(site.getName())) 1473 { 1474 return cache.get(site.getName()).stream() 1475 .map(p -> _resolver.<Project>resolveById(p.getLeft())) 1476 .collect(Collectors.toList()); 1477 } 1478 1479 try 1480 { 1481 // Stream over the weak reference properties pointing to this 1482 // node to find referencing projects 1483 Iterator<Property> propertyIterator = site.getNode().getWeakReferences(); 1484 Iterable<Property> propertyIterable = () -> propertyIterator; 1485 1486 List<Project> projects = StreamSupport.stream(propertyIterable.spliterator(), false) 1487 .map(p -> 1488 { 1489 try 1490 { 1491 Node parent = p.getParent(); 1492 1493 // Check if the parent is a project" 1494 if (NodeTypeHelper.isNodeType(parent, "ametys:project")) 1495 { 1496 Project project = _resolver.resolve(parent, false); 1497 return project; 1498 } 1499 } 1500 catch (Exception e) 1501 { 1502 if (getLogger().isWarnEnabled()) 1503 { 1504 // this weak reference is not from a project 1505 String propertyPath = null; 1506 try 1507 { 1508 propertyPath = p.getPath(); 1509 } 1510 catch (Exception e2) 1511 { 1512 // ignore 1513 } 1514 1515 String warnMsg = String.format("Site '%s' is pointed by a weak reference '%s' which is not representing a relation with project. This reference is ignored.", site.getName(), propertyPath); 1516 getLogger().warn(warnMsg); 1517 } 1518 } 1519 1520 return null; 1521 }) 1522 .filter(Objects::nonNull) 1523 .collect(Collectors.toList()); 1524 1525 List<Pair<String, String>> projectsPairs = projects.stream().map(p -> Pair.of(p.getId(), p.getName())).collect(Collectors.toList()); 1526 cache.put(site.getName(), projectsPairs); 1527 return projects; 1528 } 1529 catch (RepositoryException e) 1530 { 1531 getLogger().error(String.format("Unable to find projects for site '%s'", site.getName()), e); 1532 } 1533 1534 return new ArrayList<>(); 1535 } 1536 1537 /** 1538 * Create the project workspace for a given project. 1539 * @param project The project for which the workspace must be created 1540 * @param errors A list of possible errors to populate. Can be null if the caller is not interested in error tracking. 1541 * @return The site created for this workspace 1542 */ 1543 protected Site _createProjectWorkspace(Project project, List<String> errors) 1544 { 1545 String initialSiteName = project.getName(); 1546 Site site = null; 1547 1548 Site catalogSite = _siteManager.getSite(getCatalogSiteName()); 1549 String rootId = catalogSite != null ? catalogSite.getId() : null; 1550 1551 Map<String, Object> result = _siteDao.createSite(rootId, initialSiteName, ProjectWorkspaceSiteType.TYPE_ID, true); 1552 1553 String siteId = (String) result.get("id"); 1554 String siteName = (String) result.get("name"); 1555 if (StringUtils.isNotEmpty(siteId)) 1556 { 1557 // Creation success 1558 site = _siteManager.getSite(siteName); 1559 1560 setProjectSiteTitle(site, project.getTitle()); 1561 1562 // Add site to project 1563 project.setSite(site); 1564 1565 site.saveChanges(); 1566 } 1567 1568 return site; 1569 } 1570 1571 /** 1572 * Get the project's tags 1573 * @return The project's tags 1574 */ 1575 @Callable 1576 public List<String> getTags() 1577 { 1578 AmetysObject projectsRootNode = getProjectsRoot(); 1579 if (projectsRootNode instanceof JCRAmetysObject) 1580 { 1581 Node node = ((JCRAmetysObject) projectsRootNode).getNode(); 1582 1583 try 1584 { 1585 return Arrays.stream(node.getProperty(__PROJECTS_TAGS_PROPERTY).getValues()) 1586 .map(LambdaUtils.wrap(Value::getString)) 1587 .collect(Collectors.toList()); 1588 } 1589 catch (PathNotFoundException e) 1590 { 1591 // property is not set, empty list will be returned. 1592 } 1593 catch (RepositoryException e) 1594 { 1595 throw new AmetysRepositoryException(e); 1596 } 1597 } 1598 1599 return new ArrayList<>(); 1600 } 1601 1602 /** 1603 * Set the tags 1604 * @param tags The tags to set 1605 */ 1606 @Callable 1607 public synchronized void setTags(List<String> tags) 1608 { 1609 AmetysObject projectsRootNode = getProjectsRoot(); 1610 if (projectsRootNode instanceof JCRAmetysObject) 1611 { 1612 JCRAmetysObject jcrProjectsRootNode = (JCRAmetysObject) projectsRootNode; 1613 1614 if (CollectionUtils.isNotEmpty(tags)) 1615 { 1616 String[] tagsArray = tags.stream() 1617 .map(String::trim) 1618 .map(String::toLowerCase) 1619 .filter(StringUtils::isNotEmpty) 1620 .distinct() 1621 .toArray(String[]::new); 1622 1623 try 1624 { 1625 jcrProjectsRootNode.getNode().setProperty(__PROJECTS_TAGS_PROPERTY, tagsArray); 1626 jcrProjectsRootNode.saveChanges(); 1627 } 1628 catch (RepositoryException e) 1629 { 1630 throw new AmetysRepositoryException(e); 1631 } 1632 } 1633 else 1634 { 1635 Node node = jcrProjectsRootNode.getNode(); 1636 try 1637 { 1638 if (node.hasProperty(__PROJECTS_TAGS_PROPERTY)) 1639 { 1640 node.getProperty(__PROJECTS_TAGS_PROPERTY).remove(); 1641 jcrProjectsRootNode.saveChanges(); 1642 } 1643 } 1644 catch (RepositoryException e) 1645 { 1646 throw new AmetysRepositoryException(e); 1647 } 1648 } 1649 } 1650 } 1651 1652 /** 1653 * Add project's tags 1654 * @param newTags The new tags to add 1655 */ 1656 @Callable 1657 public synchronized void addTags(Collection<String> newTags) 1658 { 1659 if (CollectionUtils.isNotEmpty(newTags)) 1660 { 1661 AmetysObject projectsRootNode = getProjectsRoot(); 1662 if (projectsRootNode instanceof JCRAmetysObject) 1663 { 1664 // Concat existing tags with new lowercased tags 1665 String[] tags = Stream.concat(getTags().stream(), newTags.stream().map(String::trim).map(String::toLowerCase).filter(StringUtils::isNotEmpty)) 1666 .distinct() 1667 .toArray(String[]::new); 1668 1669 try 1670 { 1671 ((JCRAmetysObject) projectsRootNode).getNode().setProperty(__PROJECTS_TAGS_PROPERTY, tags); 1672 } 1673 catch (RepositoryException e) 1674 { 1675 throw new AmetysRepositoryException(e); 1676 } 1677 } 1678 } 1679 } 1680 1681 /** 1682 * Get the project's places 1683 * @return The project's places 1684 */ 1685 @Callable 1686 public List<String> getPlaces() 1687 { 1688 AmetysObject projectsRootNode = getProjectsRoot(); 1689 if (projectsRootNode instanceof JCRAmetysObject) 1690 { 1691 Node node = ((JCRAmetysObject) projectsRootNode).getNode(); 1692 1693 try 1694 { 1695 return Arrays.stream(node.getProperty(__PROJECTS_PLACES_PROPERTY).getValues()) 1696 .map(LambdaUtils.wrap(Value::getString)) 1697 .collect(Collectors.toList()); 1698 } 1699 catch (PathNotFoundException e) 1700 { 1701 // property is not set, empty list will be returned. 1702 } 1703 catch (RepositoryException e) 1704 { 1705 throw new AmetysRepositoryException(e); 1706 } 1707 } 1708 1709 return new ArrayList<>(); 1710 } 1711 1712 /** 1713 * Add project's places 1714 * @param newPlaces The new places to add 1715 */ 1716 public synchronized void addPlaces(Collection<String> newPlaces) 1717 { 1718 if (CollectionUtils.isNotEmpty(newPlaces)) 1719 { 1720 AmetysObject projectsRootNode = getProjectsRoot(); 1721 if (projectsRootNode instanceof JCRAmetysObject) 1722 { 1723 Set<String> lowercasedPlaces = new HashSet<>(); 1724 1725 // Concat existing places with new places 1726 String[] places = Stream.concat(getPlaces().stream(), newPlaces.stream().map(String::trim).filter(StringUtils::isNotEmpty)) 1727 // duplicates are filtered out 1728 .filter(p -> lowercasedPlaces.add(p.toLowerCase())) 1729 .toArray(String[]::new); 1730 1731 try 1732 { 1733 ((JCRAmetysObject) projectsRootNode).getNode().setProperty(__PROJECTS_PLACES_PROPERTY, places); 1734 } 1735 catch (RepositoryException e) 1736 { 1737 throw new AmetysRepositoryException(e); 1738 } 1739 } 1740 } 1741 } 1742 1743 /** 1744 * Set the places 1745 * @param places The places to set 1746 */ 1747 @Callable 1748 public synchronized void setPlaces(List<String> places) 1749 { 1750 AmetysObject projectsRootNode = getProjectsRoot(); 1751 if (projectsRootNode instanceof JCRAmetysObject) 1752 { 1753 JCRAmetysObject jcrProjectsRootNode = (JCRAmetysObject) projectsRootNode; 1754 1755 if (CollectionUtils.isNotEmpty(places)) 1756 { 1757 Set<String> lowercasedPlaces = new HashSet<>(); 1758 1759 String[] placesArray = places.stream() 1760 .map(String::trim) 1761 .filter(StringUtils::isNotEmpty) 1762 // duplicates are filtered out 1763 .filter(p -> lowercasedPlaces.add(p.toLowerCase())) 1764 .toArray(String[]::new); 1765 1766 try 1767 { 1768 jcrProjectsRootNode.getNode().setProperty(__PROJECTS_PLACES_PROPERTY, placesArray); 1769 jcrProjectsRootNode.saveChanges(); 1770 } 1771 catch (RepositoryException e) 1772 { 1773 throw new AmetysRepositoryException(e); 1774 } 1775 } 1776 else 1777 { 1778 Node node = jcrProjectsRootNode.getNode(); 1779 try 1780 { 1781 if (node.hasProperty(__PROJECTS_PLACES_PROPERTY)) 1782 { 1783 node.getProperty(__PROJECTS_PLACES_PROPERTY).remove(); 1784 jcrProjectsRootNode.saveChanges(); 1785 } 1786 } 1787 catch (RepositoryException e) 1788 { 1789 throw new AmetysRepositoryException(e); 1790 } 1791 } 1792 } 1793 } 1794 1795 /** 1796 * Get the list of activated modules for a project 1797 * @param project The project 1798 * @return The list of activated modules 1799 */ 1800 public List<WorkspaceModule> getModules(Project project) 1801 { 1802 return _moduleManagerEP.getModules().stream() 1803 .filter(module -> isModuleActivated(project, module.getId())) 1804 .collect(Collectors.toList()); 1805 } 1806 1807 /** 1808 * Retrieves the page of the module for all available languages 1809 * @param project The project 1810 * @param moduleId The project module id 1811 * @return the page or null if not found 1812 */ 1813 public Set<Page> getModulePages(Project project, String moduleId) 1814 { 1815 if (_moduleManagerEP.hasExtension(moduleId)) 1816 { 1817 WorkspaceModule module = _moduleManagerEP.getExtension(moduleId); 1818 return getModulePages(project, module); 1819 } 1820 return null; 1821 } 1822 1823 /** 1824 * Return the possible module roots associated to a page 1825 * @param page The given page 1826 * @return A non null set of the data of the linked modules 1827 */ 1828 public Set<ModifiableResourceCollection> pageToModuleRoot(Page page) 1829 { 1830 Set<ModifiableResourceCollection> data = new LinkedHashSet<>(); 1831 1832 Page rootPage = page; 1833 SitemapElement parent = page.getParent(); 1834 while (!(parent instanceof Sitemap) && !page.hasValue(__PAGE_MODULES_VALUE)) 1835 { 1836 rootPage = (Page) parent; 1837 parent = parent.getParent(); 1838 } 1839 1840 String[] modulesRootsIds = rootPage.getValue(__PAGE_MODULES_VALUE, new String[0]); 1841 if (modulesRootsIds.length > 0) 1842 { 1843 for (String moduleRootId : modulesRootsIds) 1844 { 1845 try 1846 { 1847 ModifiableResourceCollection moduleRoot = _resolver.resolveById(moduleRootId); 1848 data.add(moduleRoot); 1849 } 1850 catch (UnknownAmetysObjectException e) 1851 { 1852 // Ignore obsolete data 1853 } 1854 } 1855 } 1856 1857 return data; 1858 } 1859 1860 /** 1861 * Mark the given page as this module page. The modified page will not be saved. 1862 * @param page The page to change 1863 * @param moduleRoot The workspace module that use this page 1864 */ 1865 public void tagProjectPage(ModifiablePage page, ModifiableResourceCollection moduleRoot) 1866 { 1867 String[] currentModules = page.getValue(__PAGE_MODULES_VALUE, new String[0]); 1868 1869 Set<String> modules = new LinkedHashSet<>(Arrays.asList(currentModules)); 1870 modules.add(moduleRoot.getId()); 1871 1872 String[] newModules = new String[modules.size()]; 1873 modules.toArray(newModules); 1874 1875 page.setValue(__PAGE_MODULES_VALUE, newModules); 1876 } 1877 1878 /** 1879 * Remove the mark on the given page of this module. The modified page will not be saved. 1880 * @param page The page to change 1881 * @param moduleRoot The workspace module that use this page 1882 */ 1883 public void untagProjectPage(ModifiablePage page, ModifiableResourceCollection moduleRoot) 1884 { 1885 if (moduleRoot != null) 1886 { 1887 String[] currentModules = page.getValue(__PAGE_MODULES_VALUE, new String[0]); 1888 1889 Set<String> modules = new LinkedHashSet<>(Arrays.asList(currentModules)); 1890 modules.remove(moduleRoot.getId()); 1891 1892 String[] newModules = new String[modules.size()]; 1893 modules.toArray(newModules); 1894 1895 page.setValue(__PAGE_MODULES_VALUE, newModules); 1896 } 1897 } 1898 1899 /** 1900 * Get a page in the site of a given project with a specific tag 1901 * @param project The project 1902 * @param workspaceModule the module 1903 * @return The module's pages 1904 */ 1905 public Set<Page> getModulePages(Project project, WorkspaceModule workspaceModule) 1906 { 1907 Request request = _getRequest(); 1908 if (request == null) 1909 { 1910 // There is no request to store cache 1911 return _computePages(project, workspaceModule); 1912 } 1913 1914 Cache<RequestModuleCacheKey, Set<Page>> pagesCache = _getRequestPageCache(); 1915 1916 // The site key in the cache is of the form {site + workspace}. 1917 String currentWorkspace = _workspaceSelector.getWorkspace(); 1918 RequestModuleCacheKey pagesKey = RequestModuleCacheKey.of(project.getName(), workspaceModule.getId(), currentWorkspace); 1919 1920 try 1921 { 1922 return pagesCache.get(pagesKey, __ -> _computePages(project, workspaceModule)); 1923 } 1924 catch (CacheException e) 1925 { 1926 if (e.getCause() instanceof UnknownAmetysObjectException) 1927 { 1928 throw (UnknownAmetysObjectException) e.getCause(); 1929 } 1930 else 1931 { 1932 throw new RuntimeException("An error occurred while computing page of module " + workspaceModule.getModuleName() + " in project " + project.getName(), e); 1933 } 1934 } 1935 } 1936 1937 private Set<Page> _computePages(Project project, WorkspaceModule workspaceModule) 1938 { 1939 Set<String> pagesUUids = _getMemoryPageCache().get(ModuleCacheKey.of(project.getName(), workspaceModule.getId()), __ -> _computePagesIds(project, workspaceModule)); 1940 if (pagesUUids != null) 1941 { 1942 return pagesUUids.stream().map(uuid -> _resolver.<Page>resolveById(uuid)).collect(Collectors.toSet()); 1943 } 1944 else 1945 { 1946 // Project may be present in cache for 'default' workspace but does not exist in current JCR workspace 1947 throw new UnknownAmetysObjectException("There is no pages for '" + project.getName() + "', module '" + workspaceModule.getModuleName() + "'"); 1948 } 1949 } 1950 1951 private Set<String> _computePagesIds(Project project, WorkspaceModule workspaceModule) 1952 { 1953 Site site = project.getSite(); 1954 String siteName = site != null ? site.getName() : null; 1955 if (StringUtils.isEmpty(siteName)) 1956 { 1957 return null; 1958 } 1959 1960 ModifiableResourceCollection moduleRoot = workspaceModule.getModuleRoot(project, false); 1961 if (moduleRoot != null) 1962 { 1963 Expression expression = new StringExpression(__PAGE_MODULES_VALUE, Operator.EQ, moduleRoot.getId()); 1964 String query = PageQueryHelper.getPageXPathQuery(siteName, null, null, expression, null); 1965 1966 return StreamSupport.stream(_resolver.query(query).spliterator(), false) 1967 .map(page -> page.getId()) 1968 .collect(Collectors.toSet()); 1969 } 1970 else 1971 { 1972 return Set.of(); 1973 } 1974 } 1975 1976 /** 1977 * Activate the list of module of the project 1978 * @param project The project 1979 * @param moduleIds The list of modules. Can be null to activate all modules 1980 * @param additionalValues A list of optional additional values. Accepted values are : description, mailingList, inscriptionStatus, defaultProfile, tags, categoryTags, keywords and language 1981 */ 1982 public void activateModules(Project project, Set<String> moduleIds, Map<String, Object> additionalValues) 1983 { 1984 Set<String> modules = moduleIds == null ? _moduleManagerEP.getExtensionsIds() : moduleIds; 1985 1986 for (String moduleId : modules) 1987 { 1988 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 1989 if (module != null && !isModuleActivated(project, moduleId)) 1990 { 1991 module.activateModule(project, additionalValues); 1992 project.addModule(moduleId); 1993 } 1994 } 1995 1996 _setDefaultProfileForMembers(project, modules); 1997 1998 project.saveChanges(); 1999 } 2000 2001 private void _setDefaultProfileForMembers(Project project, Set<String> modules) 2002 { 2003 String profileForNewModule = StringUtils.defaultString(Config.getInstance().getValue("workspaces.profile.new.module")); 2004 2005 if (profileForNewModule.equals(ProfileForNewModule.DEFAULT_MEMBER_PROFILE.name())) 2006 { 2007 Set<String> defaultProfiles = Set.of(StringUtils.defaultString(Config.getInstance().getValue("workspaces.profile.default"))); 2008 Map<JCRProjectMember, Object> projectMembers = _projectMembers.getJCRProjectMembers(project); 2009 2010 for (String moduleId : modules) 2011 { 2012 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 2013 for (JCRProjectMember member : projectMembers.keySet()) 2014 { 2015 2016 _projectMembers.setProfileOnModule(member, project, module, defaultProfiles); 2017 } 2018 } 2019 } 2020 } 2021 2022 /** 2023 * Initialize the sitemap with the active module of the project 2024 * @param project The project 2025 * @param sitemap The sitemap 2026 */ 2027 public void initializeModulesSitemap(Project project, Sitemap sitemap) 2028 { 2029 Set<String> modules = _moduleManagerEP.getExtensionsIds(); 2030 2031 for (String moduleId : modules) 2032 { 2033 if (_moduleManagerEP.hasExtension(moduleId)) 2034 { 2035 WorkspaceModule module = _moduleManagerEP.getExtension(moduleId); 2036 2037 if (isModuleActivated(project, moduleId)) 2038 { 2039 module.initializeSitemap(project, sitemap); 2040 } 2041 } 2042 } 2043 } 2044 2045 /** 2046 * Determines if a module is activated 2047 * @param project The project 2048 * @param moduleId The id of module 2049 * @return true if the module the currently activated 2050 */ 2051 public boolean isModuleActivated(Project project, String moduleId) 2052 { 2053 return ArrayUtils.contains(project.getModules(), moduleId); 2054 } 2055 2056 /** 2057 * Remove the explorer root node of the project module, remove all events 2058 * related to that module and set it to deactivated 2059 * @param project The project 2060 * @param moduleIds The id of module to activate 2061 */ 2062 public void deactivateModules(Project project, Set<String> moduleIds) 2063 { 2064 for (String moduleId : moduleIds) 2065 { 2066 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 2067 if (module != null && isModuleActivated(project, moduleId)) 2068 { 2069 module.deactivateModule(project); 2070 project.removeModule(moduleId); 2071 } 2072 } 2073 2074 project.saveChanges(); 2075 } 2076 2077 2078 /** 2079 * Get the list of profiles configured for the workspaces' projects 2080 * @return The list of profiles as JSON 2081 */ 2082 @Callable 2083 public Map<String, Object> getProjectProfiles() 2084 { 2085 Map<String, Object> result = new HashMap<>(); 2086 List<Map<String, Object>> profiles = _projectRightHelper.getProfiles().stream().map(p -> p.toJSON()).collect(Collectors.toList()); 2087 result.put("profiles", profiles); 2088 return result; 2089 } 2090 2091 /** 2092 * Get the tags from the projects 2093 * @param projectIds The ids of the projects 2094 * @return the tags of the projects 2095 */ 2096 @Callable 2097 public Set<String> getTags(List<String> projectIds) 2098 { 2099 Set<String> tags = new HashSet<>(); 2100 2101 for (String projectId : projectIds) 2102 { 2103 Project project = _resolver.resolveById(projectId); 2104 tags.addAll(project.getTags()); 2105 } 2106 2107 return tags; 2108 } 2109 2110 /** 2111 * Tag the projects 2112 * @param projectIds the project ids 2113 * @param tagNames the tag names 2114 * @param contextualParameters the contextuals parameters 2115 * @return results 2116 */ 2117 @Callable 2118 public Map<String, Object> tag(List<String> projectIds, List<String> tagNames, Map<String, Object> contextualParameters) 2119 { 2120 return tag(projectIds, tagNames, TagMode.REPLACE.toString(), contextualParameters); 2121 } 2122 2123 /** 2124 * Tag the projects 2125 * @param projectIds the project ids 2126 * @param tagNames the tag names 2127 * @param mode the mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 2128 * @param contextualParameters the contextual parameters 2129 * @return results 2130 */ 2131 @Callable 2132 public Map<String, Object> tag(List<String> projectIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters) 2133 { 2134 Map<String, Object> result = new HashMap<>(); 2135 2136 result.put("invalid-tags", new ArrayList<>()); 2137 result.put("allright-projects", new ArrayList<>()); 2138 2139 for (String projectId : projectIds) 2140 { 2141 Project project = _resolver.resolveById(projectId); 2142 2143 Map<String, Object> project2json = new HashMap<>(); 2144 project2json.put("id", project.getId()); 2145 project2json.put("title", project.getTitle()); 2146 2147 TagMode tagMode = TagMode.valueOf(mode); 2148 2149 Set<String> oldTags = project.getTags(); 2150 if (TagMode.REPLACE.equals(tagMode)) 2151 { 2152 // First delete old tags 2153 for (String tagName : oldTags) 2154 { 2155 project.untag(tagName); 2156 } 2157 } 2158 2159 // Then set new tags 2160 for (String tagName : tagNames) 2161 { 2162 if (isTagValid(tagName)) 2163 { 2164 if (TagMode.REMOVE.equals(tagMode)) 2165 { 2166 project.untag(tagName); 2167 } 2168 else if (TagMode.REPLACE.equals(tagMode) || !oldTags.contains(tagName)) 2169 { 2170 project.tag(tagName); 2171 } 2172 } 2173 else 2174 { 2175 @SuppressWarnings("unchecked") 2176 List<String> invalidTags = (List<String>) result.get("invalid-tags"); 2177 invalidTags.add(tagName); 2178 } 2179 } 2180 2181 project.saveChanges(); 2182 2183 project2json.put("tags", project.getTags()); 2184 @SuppressWarnings("unchecked") 2185 List<Map<String, Object>> allRightProjects = (List<Map<String, Object>>) result.get("allright-projects"); 2186 allRightProjects.add(project2json); 2187 2188 if (!oldTags.equals(project.getTags())) 2189 { 2190 // Notify observers that the project has been tagged 2191 Map<String, Object> eventParams = new HashMap<>(); 2192 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 2193 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_UPDATED, _currentUserProvider.getUser(), eventParams)); 2194 } 2195 } 2196 2197 return result; 2198 } 2199 2200 /** 2201 * Test if a tag is valid 2202 * @param tagName The tag name 2203 * @return True if the tag is valid 2204 */ 2205 public boolean isTagValid (String tagName) 2206 { 2207 Map<String, Object> params = new HashMap<>(); 2208 Tag tag = _projectTagProviderEP.getTag(tagName, params); 2209 2210 return tag != null; 2211 } 2212 2213 /** 2214 * Get the site name holding the catalog of projects 2215 * @return the catalog's site name 2216 * @throws UnknownCatalogSiteException when the config is invalid 2217 */ 2218 public String getCatalogSiteName() throws UnknownCatalogSiteException 2219 { 2220 String catalogSiteName = Config.getInstance().getValue("workspaces.catalog.site.name"); 2221 if (!_siteManager.hasSite(catalogSiteName)) 2222 { 2223 throw new UnknownCatalogSiteException("Unknown site '" + catalogSiteName + "'. The global Ametys configuration is invalid for the parameter 'workspaces.catalog.site.name'"); 2224 } 2225 return catalogSiteName; 2226 } 2227 2228 /** 2229 * Get the site name holding the users directory 2230 * @return the users directory's site name 2231 * @throws UnknownUserDirectorySiteException when the config is invalid 2232 */ 2233 public String getUsersDirectorySiteName() throws UnknownUserDirectorySiteException 2234 { 2235 String udSiteName = Config.getInstance().getValue("workspaces.member.userdirectory.site.name"); 2236 if (!_siteManager.hasSite(udSiteName)) 2237 { 2238 throw new UnknownUserDirectorySiteException("Unknown site '" + udSiteName + "'. The global Ametys configuration is invalid for the parameter 'workspaces.member.userdirectory.site.name'"); 2239 } 2240 return udSiteName; 2241 } 2242 2243 @Override 2244 public boolean supports(Event event) 2245 { 2246 return event.getId().equals(ObservationConstants.EVENT_PROJECT_DELETED) 2247 || event.getId().equals(ObservationConstants.EVENT_PROJECT_UPDATED) 2248 || event.getId().equals(ObservationConstants.EVENT_PROJECT_ADDED) 2249 || event.getId().equals(org.ametys.web.ObservationConstants.EVENT_PAGE_ADDED) 2250 || event.getId().equals(org.ametys.web.ObservationConstants.EVENT_PAGE_DELETED); 2251 } 2252 2253 public int getPriority() 2254 { 2255 return 0; 2256 } 2257 2258 public void observe(Event event, Map<String, Object> transientVars) throws Exception 2259 { 2260 clearCaches(); 2261 } 2262 2263 /** 2264 * Prefix project title 2265 * @param site the site 2266 * @param title the title 2267 */ 2268 public void setProjectSiteTitle(Site site, String title) 2269 { 2270 I18nizableText i18nSiteTitle = new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_DEFAULT_PROJECT_WORKSPACE_TITLE", Arrays.asList(title)); 2271 site.setTitle(_i18nUtils.translate(i18nSiteTitle)); 2272 } 2273 2274 private Project _computeProject(String projectName) 2275 { 2276 if (hasProject(projectName)) 2277 { 2278 String uuid = _getUUIDCache().get(projectName); 2279 return _resolver.<Project>resolveById(uuid); 2280 } 2281 else 2282 { 2283 // Project may be present in cache for 'default' workspace but does not exist in current JCR workspace 2284 throw new UnknownAmetysObjectException("There is no site named '" + projectName + "'"); 2285 } 2286 } 2287 2288 /** 2289 * Check rights to create project 2290 * @param inscriptionStatus the inscription status 2291 * @param catalogPage the catalog page where projects are created 2292 */ 2293 public void checkRightsForProjectCreation(InscriptionStatus inscriptionStatus, SitemapElement catalogPage) 2294 { 2295 switch (inscriptionStatus) 2296 { 2297 case PRIVATE: 2298 boolean hasRightToCreatePrivateProjetOnPages = catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, catalogPage) == RightResult.RIGHT_ALLOW : false; 2299 if (!hasRightToCreatePrivateProjetOnPages && _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, "/${WorkspaceName}") != RightResult.RIGHT_ALLOW) 2300 { 2301 throw new IllegalAccessError("Can't have rights to create private project"); 2302 } 2303 break; 2304 case MODERATED: 2305 boolean hasRightToCreateModeratedProjetOnPages = catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, catalogPage) == RightResult.RIGHT_ALLOW : false; 2306 if (!hasRightToCreateModeratedProjetOnPages && _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, "/${WorkspaceName}") != RightResult.RIGHT_ALLOW) 2307 { 2308 throw new IllegalAccessError("Can't have rights to create public project with moderation"); 2309 } 2310 break; 2311 case OPEN: 2312 boolean hasRightToCreateOpenProjetOnPages = catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, catalogPage) == RightResult.RIGHT_ALLOW : false; 2313 if (!hasRightToCreateOpenProjetOnPages && _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, "/${WorkspaceName}") != RightResult.RIGHT_ALLOW) 2314 { 2315 throw new IllegalAccessError("Can't have rights to create public project"); 2316 } 2317 break; 2318 default: 2319 throw new IllegalArgumentException("Inscription status '" + inscriptionStatus.toString() + "' is unknown"); 2320 } 2321 } 2322 2323 /** 2324 * Check rights to edit project 2325 * @param project the project 2326 * @param inscriptionStatus the inscription status 2327 * @param catalogPage the catalog page where projects are created 2328 */ 2329 public void checkRightsForProjectEdition(Project project, InscriptionStatus inscriptionStatus, SitemapElement catalogPage) 2330 { 2331 InscriptionStatus oldInscriptionStatus = project.getInscriptionStatus(); 2332 if (oldInscriptionStatus != inscriptionStatus) 2333 { 2334 boolean canCreatePrivateProjet = _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW 2335 || (catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, catalogPage) == RightResult.RIGHT_ALLOW : false); 2336 boolean canCreatePublicProjetWithModeration = _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW 2337 || (catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, catalogPage) == RightResult.RIGHT_ALLOW : false); 2338 boolean canCreatePublicProjet = _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW 2339 || (catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, catalogPage) == RightResult.RIGHT_ALLOW : false); 2340 2341 switch (oldInscriptionStatus) 2342 { 2343 case PRIVATE: 2344 if (!canCreatePrivateProjet) 2345 { 2346 throw new IllegalAccessError("Can't have rights to change the inscription status of private project"); 2347 } 2348 break; 2349 case MODERATED: 2350 if (!canCreatePublicProjetWithModeration) 2351 { 2352 throw new IllegalAccessError("Can't have rights to change the inscription status of public project with moderation"); 2353 } 2354 break; 2355 case OPEN: 2356 if (!canCreatePublicProjet) 2357 { 2358 throw new IllegalAccessError("Can't have rights to change the inscription status of public project"); 2359 } 2360 break; 2361 default: 2362 throw new IllegalArgumentException("Inscription status '" + oldInscriptionStatus.toString() + "' is unknown"); 2363 } 2364 2365 switch (inscriptionStatus) 2366 { 2367 case PRIVATE: 2368 if (!canCreatePrivateProjet) 2369 { 2370 throw new IllegalAccessError("Can't have rights to change the project to private project"); 2371 } 2372 break; 2373 case MODERATED: 2374 if (!canCreatePublicProjetWithModeration) 2375 { 2376 throw new IllegalAccessError("Can't have rights to change the project to public project with moderation"); 2377 } 2378 break; 2379 case OPEN: 2380 if (!canCreatePublicProjet) 2381 { 2382 throw new IllegalAccessError("Can't have rights to change the project to public project"); 2383 } 2384 break; 2385 default: 2386 throw new IllegalArgumentException("Inscription status '" + inscriptionStatus.toString() + "' is unknown"); 2387 } 2388 } 2389 } 2390 2391 /** 2392 * Clear the site cache 2393 */ 2394 public void clearCaches () 2395 { 2396 _getMemorySiteAssociationCache().invalidateAll(); 2397 _getMemoryProjectCache().invalidateAll(); 2398 _getMemoryPageCache().invalidateAll(); 2399 _getRequestProjectCache().invalidateAll(); 2400 _getRequestPageCache().invalidateAll(); 2401 } 2402 2403 private Cache<String, List<Pair<String, String>>> _getMemorySiteAssociationCache() 2404 { 2405 return _cacheManager.get(MEMORY_SITEASSOCIATION_CACHE); 2406 } 2407 2408 private Cache<String, String> _getMemoryProjectCache() 2409 { 2410 return _cacheManager.get(MEMORY_PROJECTIDBYNAMECACHE); 2411 } 2412 2413 private Cache<ModuleCacheKey, Set<String>> _getMemoryPageCache() 2414 { 2415 return _cacheManager.get(MEMORY_PAGESBYIDCACHE); 2416 } 2417 2418 private Cache<RequestProjectCacheKey, Project> _getRequestProjectCache() 2419 { 2420 return _cacheManager.get(REQUEST_PROJECTBYID_CACHE); 2421 } 2422 2423 private Cache<RequestModuleCacheKey, Set<Page>> _getRequestPageCache() 2424 { 2425 return _cacheManager.get(REQUEST_PAGESBYPROJECTANDMODULE_CACHE); 2426 } 2427 2428 2429 /** 2430 * Creates the caches 2431 */ 2432 protected void _createCaches() 2433 { 2434 _cacheManager.createMemoryCache(MEMORY_SITEASSOCIATION_CACHE, 2435 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CACHE_PROJECT_MANAGER_LABEL"), 2436 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CACHE_PROJECT_MANAGER_DESCRIPTION"), 2437 true, 2438 null); 2439 _cacheManager.createMemoryCache(MEMORY_PROJECTIDBYNAMECACHE, 2440 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_UUID_CACHE_LABEL"), 2441 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_UUID_CACHE_DESCRIPTION"), 2442 true, 2443 null); 2444 _cacheManager.createMemoryCache(MEMORY_PAGESBYIDCACHE, 2445 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEUUID_CACHE_LABEL"), 2446 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEUUID_CACHE_DESCRIPTION"), 2447 true, 2448 null); 2449 _cacheManager.createRequestCache(REQUEST_PROJECTBYID_CACHE, 2450 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_REQUEST_CACHE_LABEL"), 2451 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_REQUEST_CACHE_DESCRIPTION"), 2452 false); 2453 _cacheManager.createRequestCache(REQUEST_PAGESBYPROJECTANDMODULE_CACHE, 2454 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEREQUEST_CACHE_LABEL"), 2455 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEREQUEST_CACHE_DESCRIPTION"), 2456 false); 2457 } 2458 2459 private synchronized Map<String, String> _getUUIDCache() 2460 { 2461 if (!_getMemoryProjectCache().hasKey(__IS_CACHE_FILLED)) 2462 { 2463 Session defaultSession = null; 2464 try 2465 { 2466 // Force default workspace to execute query 2467 defaultSession = _repository.login(RepositoryConstants.DEFAULT_WORKSPACE); 2468 2469 String jcrQuery = "//element(*, ametys:project)"; 2470 2471 AmetysObjectIterable<Project> projects = _resolver.query(jcrQuery, defaultSession); 2472 2473 for (Project project : projects) 2474 { 2475 _getMemoryProjectCache().put(project.getName(), project.getId()); 2476 } 2477 2478 _getMemoryProjectCache().put(__IS_CACHE_FILLED, null); 2479 } 2480 catch (RepositoryException e) 2481 { 2482 throw new AmetysRepositoryException(e); 2483 } 2484 finally 2485 { 2486 if (defaultSession != null) 2487 { 2488 defaultSession.logout(); 2489 } 2490 } 2491 } 2492 2493 Map<String, String> cacheAsMap = _getMemoryProjectCache().asMap(); 2494 cacheAsMap.remove(__IS_CACHE_FILLED); 2495 return cacheAsMap; 2496 } 2497 2498 private static final class RequestProjectCacheKey extends AbstractCacheKey 2499 { 2500 private RequestProjectCacheKey(String projectName, String workspaceName) 2501 { 2502 super(projectName, workspaceName); 2503 } 2504 2505 static RequestProjectCacheKey of(String projectName, String workspaceName) 2506 { 2507 return new RequestProjectCacheKey(projectName, workspaceName); 2508 } 2509 } 2510 2511 private static final class ModuleCacheKey extends AbstractCacheKey 2512 { 2513 private ModuleCacheKey(String projectName, String moduleId) 2514 { 2515 super(projectName, moduleId); 2516 } 2517 2518 static ModuleCacheKey of(String projectName, String moduleId) 2519 { 2520 return new ModuleCacheKey(projectName, moduleId); 2521 } 2522 } 2523 2524 private static final class RequestModuleCacheKey extends AbstractCacheKey 2525 { 2526 private RequestModuleCacheKey(String projectName, String moduleId, String workspaceName) 2527 { 2528 super(projectName, moduleId, workspaceName); 2529 } 2530 2531 static RequestModuleCacheKey of(String projectName, String moduleId, String workspaceName) 2532 { 2533 return new RequestModuleCacheKey(projectName, moduleId, workspaceName); 2534 } 2535 } 2536 2537 private Request _getRequest () 2538 { 2539 try 2540 { 2541 return (Request) _context.get(ContextHelper.CONTEXT_REQUEST_OBJECT); 2542 } 2543 catch (ContextException ce) 2544 { 2545 getLogger().info("Unable to get the request", ce); 2546 return null; 2547 } 2548 } 2549 2550 /** 2551 * Retrieves all projects for client side 2552 * @return the projects 2553 */ 2554 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 2555 public List<Map<String, Object>> getProjectsStatisticsForClientSide() 2556 { 2557 return getProjects() 2558 .stream() 2559 .map(p -> getProjectStatistics(p)) 2560 .collect(Collectors.toList()); 2561 } 2562 2563 /** 2564 * Retrieves the standard information of a project 2565 * @param project The project 2566 * @return The map of information 2567 */ 2568 public Map<String, Object> getProjectStatistics(Project project) 2569 { 2570 Map<String, Object> statistics = new HashMap<>(); 2571 2572 statistics.put("title", project.getTitle()); 2573 2574 long totalSize = 0; 2575 for (WorkspaceModule moduleManager : _moduleManagerEP.getModules()) 2576 { 2577 Map<String, Object> moduleStatistics = moduleManager.getStatistics(project); 2578 statistics.putAll(moduleStatistics); 2579 Long size = (Long) moduleStatistics.get(moduleManager.getModuleSizeKey()); 2580 totalSize += (size != null && size >= 0) ? (Long) moduleStatistics.get(moduleManager.getModuleSizeKey()) : 0; 2581 } 2582 2583 statistics.put("totalSize", totalSize); 2584 2585 ZonedDateTime creationDate = project.getCreationDate(); 2586 2587 statistics.put("creationDate", creationDate); 2588 statistics.put("managers", Arrays.stream(project.getManagers()) 2589 .map(u -> _userHelper.user2json(u)) 2590 .collect(Collectors.toList())); 2591 2592 return statistics; 2593 } 2594 2595 /** 2596 * Retrieves all projects for client side 2597 * @return the projects 2598 */ 2599 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 2600 public List<Map<String, Object>> getProjectsStatisticsColumnsModel() 2601 { 2602 return getStatisticHeaders() 2603 .stream() 2604 .map(p -> p.convertToJSON()) 2605 .collect(Collectors.toList()); 2606 } 2607 2608 private List<StatisticColumn> getStatisticHeaders() 2609 { 2610 2611 List<StatisticColumn> flatStatisticHeaders = getFlatStatisticHeaders(); 2612 List<StatisticColumn> headers = new ArrayList<>(); 2613 for (StatisticColumn statisticColumn : flatStatisticHeaders) 2614 { 2615 // this column have a parent, we have to find it and attach it 2616 if (statisticColumn.getGroup() != null) 2617 { 2618 Optional<StatisticColumn> parent = flatStatisticHeaders.stream() 2619 .filter(column -> column.getId().equals(statisticColumn.getGroup())) 2620 .findAny(); 2621 if (parent.isPresent()) 2622 { 2623 parent.get().addSubColumn(statisticColumn); 2624 } 2625 } 2626 else 2627 { 2628 headers.add(statisticColumn); 2629 } 2630 } 2631 2632 return headers; 2633 } 2634 2635 private List<StatisticColumn> getFlatStatisticHeaders() 2636 { 2637 List<StatisticColumn> headers = new ArrayList<>(); 2638 headers.add(new StatisticColumn("title", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_TITLE")) 2639 .withType(StatisticsColumnType.STRING) 2640 .withWidth(200) 2641 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderTitle")); 2642 headers.add(new StatisticColumn("creationDate", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_CREATION")) 2643 .withType(StatisticsColumnType.DATE) 2644 .withWidth(150)); 2645 headers.add(new StatisticColumn("managers", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MANAGERS")) 2646 .withRenderer("Ametys.grid.GridColumnHelper.renderUser") 2647 .withFilter(false)); 2648 for (WorkspaceModule moduleManager : _moduleManagerEP.getModules()) 2649 { 2650 headers.addAll(moduleManager.getStatisticModel()); 2651 } 2652 2653 StatisticColumn elements = new StatisticColumn(WorkspaceModule.GROUP_HEADER_ELEMENTS_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_ELEMENTS")) 2654 .withFilter(false); 2655 headers.add(elements); 2656 2657 StatisticColumn activatedModules = new StatisticColumn(WorkspaceModule.GROUP_HEADER_ACTIVATED_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_ACTIVE_MODULES")) 2658 .isHidden(true) 2659 .withFilter(false); 2660 headers.add(activatedModules); 2661 2662 StatisticColumn lastActivity = new StatisticColumn(WorkspaceModule.GROUP_HEADER_LAST_ACTIVITY_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_LAST_ACTIVITY")).isHidden(true); 2663 headers.add(lastActivity); 2664 2665 StatisticColumn modulesSize = new StatisticColumn(WorkspaceModule.GROUP_HEADER_SIZE_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MODULES_SIZE")) 2666 .withFilter(false); 2667 modulesSize.addSubColumn(new StatisticColumn("totalSize", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MODULES_SIZE_TOTAL")) 2668 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderSize") 2669 .withType(StatisticsColumnType.LONG)); 2670 2671 headers.add(modulesSize); 2672 2673 return headers; 2674 } 2675 2676 /** 2677 * Check if the user is in one of the populations of project 2678 * @param project the project 2679 * @param user the user 2680 * @return true if the user is in one of the populations of project 2681 */ 2682 public boolean isUserInProjectPopulations(Project project, UserIdentity user) 2683 { 2684 Site site = project.getSite(); 2685 2686 if (site == null) 2687 { 2688 throw new IllegalArgumentException("Cannot determine if user " + UserIdentity.userIdentityToString(user) + " can connect to the project " + project.getName() + " since the project has no associated site to determine the populations"); 2689 } 2690 String siteName = site.getName(); 2691 2692 Set<String> populations = _populationContextHelper.getUserPopulationsOnContext("/sites/" + siteName, false); 2693 Set<String> frontPopulations = _populationContextHelper.getUserPopulationsOnContext("/sites-fo/" + siteName, false); 2694 2695 return populations.contains(user.getPopulationId()) || frontPopulations.contains(user.getPopulationId()); 2696 } 2697 2698 /** 2699 * Thrown to indicate that the catalog site is unknown 2700 */ 2701 public class UnknownCatalogSiteException extends IllegalArgumentException 2702 { 2703 /** 2704 * Construct a {@code UnknownCatalogSiteException} with the specified message 2705 * @param message the message 2706 */ 2707 public UnknownCatalogSiteException(String message) 2708 { 2709 super(message); 2710 } 2711 } 2712 2713 /** 2714 * Thrown to indicate that the user directory site is unknown 2715 */ 2716 public class UnknownUserDirectorySiteException extends IllegalArgumentException 2717 { 2718 /** 2719 * Construct a {@code UnknownUserDirectorySiteException} with the specified message 2720 * @param message the message 2721 */ 2722 public UnknownUserDirectorySiteException(String message) 2723 { 2724 super(message); 2725 } 2726 } 2727}