001/* 002 * Copyright 2025 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.odf.observation.skill; 017 018import java.util.Arrays; 019import java.util.List; 020import java.util.Map; 021import java.util.Optional; 022 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025 026import org.ametys.cms.ObservationConstants; 027import org.ametys.cms.data.ContentValue; 028import org.ametys.cms.data.holder.group.ModifiableIndexableRepeater; 029import org.ametys.cms.data.holder.group.ModifiableIndexableRepeaterEntry; 030import org.ametys.cms.repository.Content; 031import org.ametys.core.observation.Event; 032import org.ametys.odf.ODFHelper; 033import org.ametys.odf.ProgramItem; 034import org.ametys.odf.course.Course; 035import org.ametys.odf.program.Program; 036 037/** 038 * Observer to remove tranvsersal skills from children courses if they have been removed from the parent Program. 039 * When the modified content is a Program, remove the tranvsersal macro skills, that got removed from the program, from the children courses 040 */ 041public class UpdateProgramSkillStep2Observer extends AbstractSkillsStepObserver 042{ 043 /** The ODF helper */ 044 protected ODFHelper _odfHelper; 045 046 @Override 047 public void service(ServiceManager manager) throws ServiceException 048 { 049 super.service(manager); 050 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 051 } 052 053 @Override 054 protected String getSupportedEventId() 055 { 056 return ObservationConstants.EVENT_CONTENT_MODIFIED; 057 } 058 059 @Override 060 protected boolean supportsContent(Content content) 061 { 062 // Only support Programs 063 return content instanceof Program; 064 } 065 066 @Override 067 public void observe(Event event, Map<String, Object> transientVars) throws Exception 068 { 069 Program program = (Program) event.getArguments().get(ObservationConstants.ARGS_CONTENT); 070 071 // When the content is modified, we need to check if the transversal skills are still linked to the content 072 ContentValue[] previousTransversalSkills = _getRequestAttribute("previousTransversalSkills", program); 073 if (previousTransversalSkills != null) 074 { 075 ContentValue[] currentTransversalSkills = program.getValue(Program.TRANSVERSAL_SKILLS, false, new ContentValue[0]); 076 077 // Compare the previous transversal skills with the current ones to retrieve the skills which are not linked to the program anymore and remove them from children courses 078 ContentValue[] transversalSkillsToDeleteContentValue = getSkillsToDelete(previousTransversalSkills, currentTransversalSkills); 079 if (transversalSkillsToDeleteContentValue != null) 080 { 081 List<String> microSkillsToDeleteIds = Arrays.stream(transversalSkillsToDeleteContentValue) 082 .map(ContentValue::getContentIfExists) 083 .flatMap(Optional::stream) 084 .flatMap(skill -> Arrays.stream((ContentValue[]) skill.getValue("microSkills"))) 085 .map(ContentValue::getContentId) 086 .toList(); 087 088 if (!microSkillsToDeleteIds.isEmpty()) 089 { 090 // Remove the orphan transversal skills from children courses 091 _removeTransversalSkillsRecursively(program, microSkillsToDeleteIds); 092 } 093 } 094 } 095 } 096 097 private void _removeTransversalSkillsRecursively(Program program, List<String> microSkillsToDeleteIds) 098 { 099 // Remove the blocking micro skills that where from removed macro skills 100 ContentValue[] blockingSkills = program.getValue(Program.BLOCKING_SKILLS); 101 ContentValue[] blockingSkillsToSet = Arrays.stream(blockingSkills) 102 // Only retrieve the microSkills that did not get removed from the program 103 .filter(microSkill -> !microSkillsToDeleteIds.contains(microSkill.getContentId())) 104 .toArray(ContentValue[]::new); 105 106 if (blockingSkillsToSet.length == 0) 107 { 108 program.removeValue(Program.BLOCKING_SKILLS); 109 } 110 else 111 { 112 program.setValue(Program.BLOCKING_SKILLS, blockingSkillsToSet); 113 } 114 program.saveChanges(); 115 116 _removeTransversalSkillsRecursively(program, program, microSkillsToDeleteIds); 117 } 118 119 private void _removeTransversalSkillsRecursively(Program program, ProgramItem programItem, List<String> microSkillsToDeleteIds) 120 { 121 if (programItem instanceof Course course) 122 { 123 if (course.hasValue(Course.ACQUIRED_MICRO_SKILLS)) 124 { 125 ModifiableIndexableRepeater repeater = course.getRepeater(Course.ACQUIRED_MICRO_SKILLS); 126 ModifiableIndexableRepeaterEntry programEntry = repeater.getEntries().stream() 127 .filter(entry -> program.getId().equals(((ContentValue) entry.getValue(Course.ACQUIRED_MICRO_SKILLS_PROGRAM)).getContentId())) 128 .findFirst() 129 .orElse(null); 130 if (programEntry != null) 131 { 132 ContentValue[] microSkills = programEntry.getValue(Course.ACQUIRED_MICRO_SKILLS_SKILLS, false, null); 133 134 if (microSkills != null) 135 { 136 ContentValue[] microSkillsToSet = Arrays.stream(microSkills) 137 // Only retrieve the microSkills that did not get removed from the program 138 .filter(microSkill -> !microSkillsToDeleteIds.contains(microSkill.getContentId())) 139 .toArray(ContentValue[]::new); 140 141 // If there are no micro skills for this program in this course anymore, remove the whole entry 142 if (microSkillsToSet.length == 0) 143 { 144 repeater.removeEntry(programEntry.getPosition()); 145 } 146 // If there are still some micro skills, set them 147 else 148 { 149 programEntry.setValue(Course.ACQUIRED_MICRO_SKILLS_SKILLS, microSkillsToSet); 150 } 151 152 course.saveChanges(); 153 } 154 } 155 } 156 } 157 else 158 { 159 List<ProgramItem> children = _odfHelper.getChildProgramItems(programItem); 160 for (ProgramItem childProgramItem : children) 161 { 162 _removeTransversalSkillsRecursively(program, childProgramItem, microSkillsToDeleteIds); 163 } 164 } 165 } 166}