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