001/*
002 *  Copyright 2022 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 */
016
017package org.ametys.plugins.odfsync.pegase.ws.structure;
018
019import java.io.IOException;
020import java.math.BigDecimal;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.Set;
027import java.util.regex.Pattern;
028import java.util.stream.Collectors;
029import java.util.stream.Stream;
030
031import org.apache.avalon.framework.activity.Initializable;
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.commons.lang3.StringUtils;
036
037import org.ametys.cms.data.ContentValue;
038import org.ametys.cms.data.RichText;
039import org.ametys.cms.data.RichTextHelper;
040import org.ametys.cms.repository.Content;
041import org.ametys.odf.ODFHelper;
042import org.ametys.odf.ProgramItem;
043import org.ametys.odf.course.Course;
044import org.ametys.odf.course.CourseFactory;
045import org.ametys.odf.courselist.CourseList;
046import org.ametys.odf.enumeration.OdfReferenceTableEntry;
047import org.ametys.odf.enumeration.OdfReferenceTableHelper;
048import org.ametys.odf.program.AbstractProgram;
049import org.ametys.odf.program.Container;
050import org.ametys.odf.program.Program;
051import org.ametys.odf.program.ProgramFactory;
052import org.ametys.odf.program.ProgramPart;
053import org.ametys.odf.program.SubProgram;
054import org.ametys.plugins.odfsync.export.AbstractExportStructure;
055import org.ametys.plugins.odfsync.export.ExportReport;
056import org.ametys.plugins.odfsync.export.ExportReport.ExportStatus;
057import org.ametys.plugins.odfsync.export.ExportReport.ProblemTypes;
058import org.ametys.plugins.odfsync.pegase.ws.PegaseApiManager;
059import org.ametys.plugins.odfsync.pegase.ws.PegaseExportException;
060import org.ametys.runtime.config.Config;
061import org.ametys.runtime.i18n.I18nizableText;
062import org.ametys.runtime.model.ElementDefinition;
063
064import fr.pcscol.pegase.cof.ApiException;
065import fr.pcscol.pegase.cof.model.Enfant;
066import fr.pcscol.pegase.cof.model.EnfantDetails;
067import fr.pcscol.pegase.cof.model.Formation;
068import fr.pcscol.pegase.cof.model.FormationRef;
069import fr.pcscol.pegase.cof.model.FormationTypeFormation;
070import fr.pcscol.pegase.cof.model.Groupement;
071import fr.pcscol.pegase.cof.model.ObjetFormation;
072import fr.pcscol.pegase.cof.model.ObjetFormationType;
073import fr.pcscol.pegase.cof.model.ObjetMaquette;
074import fr.pcscol.pegase.cof.model.Pageable;
075import fr.pcscol.pegase.cof.model.PagedObjetMaquette;
076import fr.pcscol.pegase.cof.model.Ref;
077import fr.pcscol.pegase.cof.model.ValeurNum;
078
079/**
080 * The structure to export the program in Pegase
081 */
082public class PegaseProgramStructure extends AbstractExportStructure implements Component, Initializable
083{
084    /** Role */
085    public static final String ROLE = PegaseProgramStructure.class.getName();
086    
087    /* Constants */
088    private static final String __PEGASE_SYNC_CODE = "pegaseSyncCode";
089    private static final Pattern __PROGRAM_CODE_PATTERN = Pattern.compile("^[A-Z0-9\\-]{3,25}(/[1-9][0-9]*)?$");
090    private static final Pattern __DEFAULT_CODE_PATTERN = Pattern.compile("^[A-Z0-9\\-]{3,25}$");
091    private static final String __CODE_FORMATION_DIPLOMANTE = "0";
092    private static final Map<String, Set<String>> __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE = new HashMap<>();
093    static 
094    {
095        __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put(
096                ProgramFactory.PROGRAM_CONTENT_TYPE,
097                Set.of(
098                        AbstractProgram.DOMAIN,
099                        AbstractProgram.EDUCATION_KIND,
100                        AbstractProgram.DEGREE,
101                        AbstractProgram.LEVEL,
102                        AbstractProgram.RNCP_LEVEL
103                )
104        );
105        __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put(
106                CourseFactory.COURSE_CONTENT_TYPE,
107                Set.of(Course.COURSE_TYPE)
108        );
109        __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put(
110                OdfReferenceTableHelper.DEGREE,
111                Set.of(
112                        "degreeNature",
113                        "cursus"
114                )
115        );
116    }
117    
118    /* Components */
119    private PegaseApiManager _pegaseApiManager;
120    private ODFHelper _odfHelper;
121    private RichTextHelper _richTextHelper;
122
123    /* Pégase configuration */
124    private boolean _isActive;
125    private String _structureCode;
126    private boolean _trustAmetys;
127    
128    @Override
129    public void service(ServiceManager manager) throws ServiceException
130    {
131        super.service(manager);
132        
133        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
134        _richTextHelper = (RichTextHelper) manager.lookup(RichTextHelper.ROLE);
135        _pegaseApiManager = (PegaseApiManager) manager.lookup(PegaseApiManager.ROLE);
136    }
137    
138    public void initialize() throws Exception
139    {
140        _isActive = Config.getInstance().getValue("pegase.activate", true, false);
141        if (_isActive)
142        {
143            _structureCode = Config.getInstance().getValue("pegase.structure.code");
144            _trustAmetys = Config.getInstance().getValue("pegase.trust", true, false);
145        }
146    }
147    
148    private String _getPegaseCodeOrCode(Content content)
149    {
150        String code = content.getValue(OdfReferenceTableEntry.CODE_PEGASE);
151        if (StringUtils.isEmpty(code))
152        {
153            code = content.getValue(OdfReferenceTableEntry.CODE);
154        }
155        return code;
156    }
157
158    private String _getPegaseCodeOrCodeOfAttribute(Content content, String attributeName)
159    {
160        return _getContentValue(content, attributeName)
161            .map(this::_getPegaseCodeOrCode)
162            .orElse(null);
163    }
164
165    private void _addMandatoryDataPathAndReport(Content content, String dataPath, ExportReport report)
166    {
167        I18nizableText invalidMessage = new I18nizableText(
168            "plugin.odf-sync",
169            "PLUGINS_ODF_SYNC_EXPORT_PEGASE_MANDATORY_FIELD",
170            Map.of("fieldName", content.getDefinition(dataPath).getLabel())
171        );
172        report.addInvalidDataPath(content, invalidMessage);
173    }
174    
175    private Optional<Content> _getContentValue(Content content, String attributeName)
176    {
177        return Optional.of(attributeName)
178            .map(content::<ContentValue>getValue)
179            .flatMap(ContentValue::getContentIfExists);
180    }
181    
182    private Stream<Content> _getContentValues(Content content, String attributeName)
183    {
184        return Optional.of(attributeName)
185            .map(content::<ContentValue[]>getValue)
186            .map(Stream::of)
187            .orElseGet(Stream::of)
188            .map(ContentValue::getContentIfExists)
189            .filter(Optional::isPresent)
190            .map(Optional::get);
191    }
192    
193    /**
194     * Checks if the program has all the required fields, their Pegase correspondence and that the program has a valid structure
195     * @param program the program
196     * @param report the Pegase export report
197     */
198    @Override
199    public void checkProgram(Program program, ExportReport report)
200    {
201        if (!_isActive)
202        {
203            throw new UnsupportedOperationException("Pégase is not active in the configuration, you cannot check the program for the export.");
204        }
205        
206        try
207        {
208            _checkAttributes(program, report);
209            
210            if (program.hasValue(__PEGASE_SYNC_CODE))
211            {
212                String codeAndVersion = program.getValue(__PEGASE_SYNC_CODE);
213                Map<String, Object> dataCodeAndVersion = _getCodeAndVersion(codeAndVersion);
214                String code = (String) dataCodeAndVersion.get("code");
215                String versionString = (String) dataCodeAndVersion.get("version");
216                
217                // If the code is not missing, check if it is valid
218                if (StringUtils.isNotBlank(versionString))
219                {
220                    BigDecimal version = new BigDecimal(versionString);
221                    _checkProgramVersionCoherence(report, code, version);
222                }
223            }
224            
225            // If the education kind has a Pegase correspondence and is of code "0" (diplomante)
226            if (__CODE_FORMATION_DIPLOMANTE.equals(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.EDUCATION_KIND)))
227            {
228                _checkProgramDiplomanteField(program, report);
229            }
230            
231            _checkChildren(program, report);
232            
233        }
234        catch (IOException e)
235        {
236            report.updateStatus(ExportStatus.ERROR);
237            getLogger().error("Le jeton d'authentification à Pégase n'a pas pu être récupéré", e);
238        }
239    }
240    
241    private void _checkAttributes(Content content, ExportReport report)
242    {
243        if (content instanceof ProgramItem)
244        {
245            if (!content.hasValue(__PEGASE_SYNC_CODE))
246            {
247                _addMandatoryDataPathAndReport(content, __PEGASE_SYNC_CODE, report);
248            }
249            else if (content instanceof Program)
250            {
251                if (!_matches(__PROGRAM_CODE_PATTERN, content, __PEGASE_SYNC_CODE))
252                {
253                    report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_PEGASE_INVALID_CODE_PROGRAM"));
254                }
255            }
256            else if (!_matches(__DEFAULT_CODE_PATTERN, content, __PEGASE_SYNC_CODE))
257            {
258                report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_PEGASE_INVALID_CODE_DEFAULT"));
259            }
260        }
261        
262        String contentType = content.getTypes()[0];
263        Set<String> mandatoryAttributes = __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.getOrDefault(contentType, Set.of());
264        for (String attribute : mandatoryAttributes)
265        {
266            if (!content.hasValue(attribute))
267            {
268                _addMandatoryDataPathAndReport(content, attribute, report);
269            }
270        }
271    }
272    
273    private boolean _matches(Pattern regex, Content content, String attributeName)
274    {
275        return regex.matcher(content.getValue(attributeName)).matches();
276    }
277    
278    private void _checkProgramVersionCoherence(ExportReport report, String code, BigDecimal version) throws IOException
279    {
280        // If the Program already exist in this version
281        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code, version);
282        
283        Long versionLong = version.longValue();
284        if (objetMaquette != null)
285        {
286            // If it is not of type "FORMATION" or if it is not editable
287            if (!objetMaquette.getType().getCode().equals("FORMATION") || !objetMaquette.getModifiable())
288            {
289                report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS);
290            }
291        }
292        
293        // If nothing was found for this code and version, and the version is not null
294        else
295        {
296            // Get the element for this code without requesting a specific version
297            objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
298            
299            // If something was found and it's a Program
300            if (objetMaquette != null && objetMaquette.getType().getCode().equals("FORMATION"))
301            {
302                // Check if the version is compatible (versionFound == versionRequested or versionFound == versionRequested - 1)
303                Long versionFound = Long.valueOf(objetMaquette.getDetail().getFormation().getVersion());
304                if (versionLong != versionFound && versionLong != (versionFound + 1))
305                {
306                    report.setStatus(ExportStatus.PROGRAM_IMPOSSIBLE_VERSION);
307                }
308            }
309            
310            // If something was found but it is not a Program
311            else if (objetMaquette != null && !objetMaquette.getType().getCode().equals("FORMATION"))
312            {
313                report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS);
314            }
315            
316            // If nothing was found, check if the version requested is one
317            else if (versionLong != 1)
318            {
319                report.setStatus(ExportStatus.PROGRAM_IMPOSSIBLE_VERSION);
320            }
321        }
322    }
323    
324    private void _checkProgramDiplomanteField(Program program, ExportReport report)
325    {
326        // dataDegree which contains degree, cursus and degreeNature
327        _getContentValue(program, AbstractProgram.DEGREE)
328                .ifPresent(degree -> _checkAttributes(degree, report));
329    }
330    
331    private void _checkChildren(ProgramItem programItem, ExportReport report)
332    {
333        for (ProgramItem child : _odfHelper.getChildProgramItems(programItem))
334        {
335            _checkProgramItem(child, report);
336        }
337    }
338    
339    private void _checkChildrenOfYear(Container container, ExportReport report)
340    {
341        container.getProgramPartChildren()
342            .stream()
343            .filter(Container.class::isInstance)
344            .map(Container.class::cast)
345            .filter(c -> "annee".equals(getContainerNatureCode(c)))
346            .forEach(
347                child ->
348                {
349                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
350                    getLogger().error("L'élément {} (Année) ne peut avoir comme enfant une année : {}", container.getTitle(), child.getTitle());
351                }
352            );
353    }
354    
355    private void _checkChildrenOfSemester(Container container, ExportReport report)
356    {
357        container.getProgramPartChildren()
358            .stream()
359            .filter(Container.class::isInstance)
360            .map(Container.class::cast)
361            .filter(c ->
362            {
363                String nature = getContainerNatureCode(c);
364                return "annee".equals(nature) || "semestre".equals(nature);
365            })
366            .forEach(
367                child ->
368                {
369                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
370                    getLogger().error("L'élément {} (Semestre) ne peut avoir comme enfant une année ou un semestre : {}", container.getTitle(), child.getTitle());
371                }
372            );
373    }
374    
375    private void _checkChildrenOfSubProgram(SubProgram subProgram, ExportReport report)
376    {
377        for (ProgramItem child : _odfHelper.getChildProgramItems(subProgram))
378        {
379            // If the child is a SubProgram, then it is not compatible with the parent : SubProgram
380            if (child instanceof SubProgram childSubProgram)
381            {
382                report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
383                getLogger().error("L'élément {} (Parcours) ne peut avoir comme enfant un parcours : {}", subProgram.getTitle(), childSubProgram.getTitle());
384            }
385            
386            _checkProgramItem(child, report);
387        }
388    }
389    
390    private void _checkProgramItem(ProgramItem programItem, ExportReport report)
391    {
392        _checkAttributes((Content) programItem, report);
393        
394        if (programItem instanceof CourseList couresList)
395        {
396            // Check if the courseList has children, because a Pegase group has to have at least one child
397            if (!couresList.hasCourses())
398            {
399                getLogger().error("L'élément {} n'a pas d'enfant alors qu'il aurait dû être exporté en tant que groupement", couresList.getTitle());
400                report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
401            }
402        }
403        else if (programItem instanceof Container container)
404        {
405            String childNatureCode = getContainerNatureCode(container);
406            
407            if ("annee".equals(childNatureCode))
408            {
409                _checkChildrenOfYear(container, report);
410            }
411            else if ("semestre".equals(childNatureCode))
412            {
413                _checkChildrenOfSemester(container, report);
414            }
415            else
416            {
417                // Check if the container (without a nature) has children, because a Pegase group has to have at least one child
418                if (!container.hasProgramPartChildren())
419                {
420                    getLogger().error("L'élément {} n'a pas d'enfant alors qu'il aurait dû être exporté en tant que groupement", container.getTitle());
421                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
422                }
423            }
424        }
425        
426        else if (programItem instanceof SubProgram subProgram)
427        {
428            _checkChildrenOfSubProgram(subProgram, report);
429        }
430        
431        for (ProgramItem child : _odfHelper.getChildProgramItems(programItem))
432        {
433            _checkProgramItem(child, report);
434        }
435    }
436    
437    private ObjetMaquette _getObjetMaquetteIfAlreadyExists(String code) throws IOException
438    {
439        return _getObjetMaquetteIfAlreadyExists(code, null);
440    }
441    
442    /**
443     * Get the latest version of an objetMaquette if it exists
444     * @param code The code wanted
445     * @param version The version wanted
446     * @return The ObjetMaquette wanted or null if it was not found
447     * @throws IOException If an error occurs while retrieving the token
448     */
449    private ObjetMaquette _getObjetMaquetteIfAlreadyExists(String code, BigDecimal version) throws IOException
450    {
451        Pageable pageable = new Pageable();
452        pageable.setPage(0);
453        pageable.setTaille(10);
454        pageable.setTri(List.of("version,desc"));
455        
456        PagedObjetMaquette pagedObjetMaquette;
457        try
458        {
459            // Get the objetMaquette for the code and the version
460            pagedObjetMaquette = _pegaseApiManager.getObjetsMaquetteApi().lireListeObjetsMaquette(_structureCode, null, null, null, code, null, null, null, null, null, null, version, null, null, null, null, null, null, pageable);
461            List<ObjetMaquette> objetsMaquette = pagedObjetMaquette.getItems();
462            
463            // If it was found, return it
464            if (objetsMaquette.size() >= 1)
465            {
466                return objetsMaquette.get(0);
467            }
468        }
469        catch (ApiException e)
470        {
471            getLogger().warn("Une erreur est survenue lors la recherche de l'élément de code de synchronization Pégase {} pour vérifier son existance préalable dans Pégase", code, e);
472        }
473        
474        return null;
475    }
476    
477    private Map<String, Object> _getCodeAndVersion(String codeAndVersion)
478    {
479        Map<String, Object> result = new HashMap<>();
480        
481        String code = codeAndVersion;
482        String version = null;
483        
484        if (codeAndVersion.contains("/"))
485        {
486            String[] codeAndVersionTab = codeAndVersion.split("/");
487            if (codeAndVersionTab.length == 2)
488            {
489                code = codeAndVersionTab[0];
490                version = codeAndVersionTab[1];
491            } 
492        }
493        
494        result.put("code", code);
495        result.put("version", version);
496        
497        return result;
498    }
499    
500    private boolean _isModifiable(ObjetMaquette objetMaquette, String code, ExportReport report) throws IOException
501    {
502        // If the object is not editable, return false
503        if (!objetMaquette.getModifiable())
504        {
505            return false;
506        }
507        
508        // The object is editable, now we check if it is linked to this Program
509        List<FormationRef> parentPrograms = objetMaquette.getFormationsParentes();
510        if (parentPrograms != null && parentPrograms.size() >= 1)
511        {
512            FormationRef parentProgram = parentPrograms.get(0);
513            
514            // If it is already linked to the Program, return true
515            if (report.getCodeParentProgram().equals(parentProgram.getCode()) && report.getVersionParentProgram() == parentProgram.getVersion())
516            {
517                return true;
518            }
519        }
520        
521        // If the object is "mutualise" or orphan in Pégase then it can be linked to the Program
522        if (objetMaquette.getMutualise() || _isOrphan(code))
523        {
524            return true;
525        }
526        
527        return false;
528    }
529    
530    private boolean _isOrphan(String code) throws IOException
531    {
532        PagedObjetMaquette pagedObjetMaquette;
533        try
534        {
535            // Try to get the objetMaquette for the code wanted and that are isolated
536            pagedObjetMaquette = _pegaseApiManager.getObjetsMaquetteApi().lireListeObjetsMaquette(_structureCode, null, null, null, code, null, null, null, true, null, null, null, null, null, null, null, null, null, null);
537            
538            List<ObjetMaquette> objetsMaquette = pagedObjetMaquette.getItems();
539
540            return objetsMaquette.size() >= 1;
541        }
542        catch (ApiException e)
543        {
544            getLogger().info("L'élément de code de synchronisation Pégase {} n'a pas pu être trouvé dans Pégase, nous ne pouvons donc pas vérifier si l'élément est isolé.", e);
545        }
546        
547        return false;
548    }
549    
550    private Enfant _createPegaseChild(Content child, String pegaseId) 
551    {
552        Enfant pegaseChild = new Enfant();
553        
554        pegaseChild.setDetails(
555            new EnfantDetails()
556                .libelle(child.getTitle())
557        );
558        
559        pegaseChild.setRef(
560            new Ref()
561                .id(pegaseId)
562                .code(child.getValue(__PEGASE_SYNC_CODE))
563        );
564        
565        return pegaseChild;
566    }
567    
568    private Enfant _createChild(Content content, ExportReport report)
569    {
570        boolean success = true;
571        try
572        {
573            String childPegaseId = _createPegaseInstance(content, report);
574            return _createPegaseChild(content, childPegaseId);
575        }
576        catch (Exception ex)
577        {
578            success = false;
579            getLogger().error("Erreur lors de l'export de l'élément {}", content.getTitle(), ex);
580            return null;
581        }
582        finally
583        {
584            if (success)
585            {
586                report.addElementExported(content);
587            }
588        }
589    }
590    
591    private String _createPegaseInstance(Content content, ExportReport report) throws PegaseExportException, IOException
592    {
593        // Conteneur : semestre, année -> OTT ou Groupement
594        if (content instanceof Container)
595        {
596            return _createOTTOrGroupFromContainer((Container) content, report);
597        }
598        
599        // Parcours -> OO
600        if (content instanceof SubProgram)
601        {
602            return _createOOFromSubProgram((SubProgram) content, report);
603        }
604        
605        // ELP -> OP
606        if (content instanceof Course)
607        {
608            return _createOPFromCourse((Course) content, report);
609        }
610        
611        // liste d'ELP -> Groupement
612        if (content instanceof CourseList)
613        {
614            return _createGroupFromCourseList((CourseList) content, report);
615        }
616        
617        return null;
618    }
619    
620    private Map<String, Enfant> _createAllChildren(Map<String, Enfant> children, List<ProgramItem> programItem, ExportReport report)
621    {
622        for (ProgramItem childContent : programItem) 
623        {
624            Enfant containerChild = _createChild((Content) childContent, report);
625            
626            // If the child was created, add it to the list of children to attach
627            if (containerChild != null)
628            {
629                children.put(containerChild.getRef().getId(), containerChild);
630            }
631        }
632        
633        return children;
634    }
635
636    private void _attachAllChildren(Content content, String pegaseId, Map<String, Enfant> children, ExportReport report) throws IOException
637    {
638        List<Enfant> childrenAlreadyAttached;
639        try
640        {
641            // Get the children already attached to the programItem
642            childrenAlreadyAttached = _pegaseApiManager.getObjetsMaquetteApi().lireEnfants(_structureCode, pegaseId);
643            
644            // If there are children already attached and the option trustAmetys is checked
645            if (childrenAlreadyAttached != null && _trustAmetys)
646            {
647                // For every child already attached
648                for (Enfant child : childrenAlreadyAttached)
649                {
650                    // Try to detach it
651                    try
652                    {
653                        _pegaseApiManager.getObjetsMaquetteApi().detacherEnfant(_structureCode, pegaseId, child.getRef().getId());
654                    }
655                    catch (ApiException e)
656                    {
657                        report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR, content);
658                        getLogger().warn("L'enfant de code Pégase {} de l'élément {} n'a pas pu être détaché dans Pégase", child.getRef().getCode(), content.getTitle(), e);
659                    }
660                }
661            }
662            
663            // If the option trustAmetys is not checked
664            else if (childrenAlreadyAttached != null)
665            {
666                // For every child already attached
667                for (Enfant childAlreadyAttached : childrenAlreadyAttached)
668                {
669                    // Remove it from the list of children to attach
670                    children.remove(childAlreadyAttached.getRef().getId());
671                }
672            }
673            
674            List<Enfant> childrenToAttach = List.copyOf(children.values());
675            
676            // If there are children that need to be attached
677            if (!childrenToAttach.isEmpty())
678            {
679                // Try to attach them
680                try 
681                {
682                    _pegaseApiManager.getObjetsMaquetteApi().attacherEnfant(_structureCode, pegaseId, childrenToAttach);
683                }
684                catch (ApiException ex)
685                {
686                    report.updateExportReport(ExportStatus.WARN, ProblemTypes.LINKS_MISSING, content);
687                    
688                    String childrenFailedToAttach = "";
689                    
690                    for (Enfant child : childrenToAttach)
691                    {
692                        childrenFailedToAttach += child.getRef().getCode() + ",";
693                    }
694                    
695                    getLogger().warn("Une erreur est survenue lors de l'attachement des enfants ({}) à l'élément ({}) dans Pégase", childrenFailedToAttach, content.getTitle(), ex);
696                }
697            }
698        }
699        catch (ApiException e)
700        {
701            report.updateExportReport(ExportStatus.WARN, ProblemTypes.API_ERROR, content);
702            
703            getLogger().warn("Les liens avec les enfants de l'élément {} n'ont pas pu être traités car les enfants depuis Pégase n'ont pas pu être récupérés.", content.getTitle(), e);
704        }
705    }
706    
707    private void _attachAllChildrenGroupementCase(Content content, String pegaseId, Map<String, Enfant> children, ExportReport report) throws IOException
708    {
709        // If the list of children that need to be attached is empty, throw an Exception
710        if (children.isEmpty())
711        {
712            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.GROUPEMENT_WITHOUT_CHILDREN, content);
713            
714            getLogger().warn("Aucuns des enfants de la liste d'elp ou le conteneur {}, devant être exporté en groupement, n'ont pu être exportés, les enfants de ce groupement resterons donc inchangés ", content.getTitle());
715        }
716        else
717        {
718            List<Enfant> childenAlreadyAttached;
719            try
720            {
721                // Get the children already attached
722                childenAlreadyAttached = _pegaseApiManager.getObjetsMaquetteApi().lireEnfants(_structureCode, pegaseId);
723                List<String> childrenAttachedInTheEnd = List.copyOf(children.keySet());
724                
725                // If there are children already attached
726                if (childenAlreadyAttached != null)
727                {
728                    // For every child already attached
729                    for (Enfant childAlreadyAttached : childenAlreadyAttached)
730                    {
731                        // Remove it from the list of children to attach
732                        children.remove(childAlreadyAttached.getRef().getId());
733                    }
734                }
735                
736                List<Enfant> childrenToAttach = List.copyOf(children.values());
737                
738                // If there are children to attach, try to attach them
739                if (!childrenToAttach.isEmpty())
740                {
741                    try 
742                    {
743                        _pegaseApiManager.getObjetsMaquetteApi().attacherEnfant(_structureCode, pegaseId, childrenToAttach);
744                    }
745                    catch (ApiException ex)
746                    {
747                        report.updateExportReport(ExportStatus.WARN, ProblemTypes.LINKS_MISSING, content);
748                        
749                        if (getLogger().isWarnEnabled())
750                        {
751                            getLogger().warn("Une erreur est survenue lors de l'attachement des enfants ({}) à l'élément ({}) dans Pégase",
752                                childrenToAttach.stream()
753                                    .map(Enfant::getRef)
754                                    .map(Ref::getCode)
755                                    .distinct()
756                                    .collect(Collectors.joining(", ")),
757                                    content.getTitle(),
758                                ex
759                            );
760                        }
761                    }
762                }
763                
764                // If the option trustAmetys is checked, try to detach the other children (the children that were already attached to the content
765                if (_trustAmetys && childenAlreadyAttached != null)
766                {
767                    // For every child that was already there
768                    for (Enfant child : childenAlreadyAttached)
769                    {
770                        String childId = child.getRef().getId();
771                        
772                        // If it was not part of the children that needed to be attached, try to detach it
773                        if (!childrenAttachedInTheEnd.contains(childId))
774                        {
775                            try
776                            {
777                                _pegaseApiManager.getObjetsMaquetteApi().detacherEnfant(_structureCode, pegaseId, child.getRef().getId());
778                            }
779                            catch (ApiException e)
780                            {
781                                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR, content);
782
783                                getLogger().warn("L'enfant de code Pégase {} de l'élément {} n'a pas pu être détaché dans Pégase", child.getRef().getCode(), content.getTitle(), e);
784                            }
785                        }
786                    }
787                }
788            }
789            catch (ApiException e)
790            {
791                report.updateExportReport(ExportStatus.WARN, ProblemTypes.LINKS_MISSING, content);
792
793                getLogger().warn("Les liens avec les enfants de l'élément ({}) n'ont pas pu être traités car les enfants depuis Pégase n'ont pas pu être récupérés.", content.getTitle(), e);
794            }
795        }
796    }
797    
798    private ObjetFormation _createObjetFormation(Content content, String type, ExportReport report) throws PegaseExportException, IOException
799    {
800        // Create the ObjetFormation
801        ObjetFormation objetFormation = new ObjetFormation();
802        
803        String code = content.getValue(__PEGASE_SYNC_CODE);
804        String label = content.getTitle();
805        
806        objetFormation.setCode(code);
807        
808        objetFormation.setLibelle(StringUtils.truncate(label, 50)); // le libellé court doit faire moins de 50 caractères
809        objetFormation.setLibelleLong(StringUtils.truncate(label, 150)); // le libellé long doit faire moins de 150 caractères
810        
811        ObjetFormationType ob = new ObjetFormationType();
812        ob.setCode(type);
813        objetFormation.setType(ob);
814
815        objetFormation.setMutualise(true);
816        
817        if (content.hasValue("ects"))
818        {
819            Double ects = null;
820            if (content instanceof AbstractProgram)
821            {
822                ects = _getContentValue(content, AbstractProgram.ECTS)
823                    .map(c -> c.<String>getValue(OdfReferenceTableEntry.CODE))
824                    .map(Double::parseDouble)
825                    .orElse(null);
826            }
827            else
828            {
829                ects = content.getValue("ects");
830            }
831            
832            if (ects != null)
833            {
834                objetFormation.putChampsAdditionnelsItem("ECTS", new ValeurNum().valeur(ects));
835            }
836        }
837        
838        Object desc = null;
839        if (content.hasValue("description"))
840        {
841            desc = content.getValue("description");
842        }
843        else if (content.hasValue("presentation"))
844        {
845            desc = content.getValue("presentation");
846        }
847
848        if (desc != null)
849        {
850            RichText descRichText = (RichText) desc;
851            String description = _richTextHelper.richTextToString(descRichText);
852            objetFormation.setDescription(StringUtils.truncate(description, 2000)); // la description doit faire moins de 2000 caractères
853        }
854
855        // Check if the object already exists
856        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
857        if (objetMaquette != null)
858        {
859            // If it already exist, check if it can be edited
860            if (_isModifiable(objetMaquette, code, report))
861            {
862                String id = objetMaquette.getId();
863                objetFormation.setId(id);
864                
865                // Edit the already existing object
866                try
867                {
868                    objetFormation = _pegaseApiManager.getObjetsFormationApi().modifierObjetFormation(_structureCode, id, objetFormation);
869                    return objetFormation;
870                }
871                catch (ApiException e)
872                {
873                    report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
874
875                    throw new PegaseExportException("L'élément " + content.getTitle() + " n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
876                }
877            }
878            
879            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_ALREADY_EXIST);
880
881            throw new PegaseExportException("Erreur lors de l'export de " + label + "Un élément avec le code " + code + " existe déjà et n'est pas mutualisable ou modifiable");
882        }
883        
884        // If it did not already exist, create the object
885        try
886        {
887            objetFormation = _pegaseApiManager.getObjetsFormationApi().creerObjetFormation(_structureCode, objetFormation);
888            
889            return objetFormation;
890        }
891        catch (ApiException e)
892        {
893            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
894
895            throw new PegaseExportException("L'élément " + content.getTitle() + " n'a pas pu être exporté dû à un problème rencontré avec Pégase", e);
896        }
897    }
898    
899    private Formation _createProgram(Program program, ExportReport report) throws PegaseExportException, IOException
900    {
901        Formation pegaseProgram = new Formation();
902        
903        String codeAndVersion = program.getValue(__PEGASE_SYNC_CODE);
904        
905        Map<String, Object> dataCodeAndVersion = _getCodeAndVersion(codeAndVersion);
906        String code = (String) dataCodeAndVersion.get("code");
907        BigDecimal version = new BigDecimal((String) dataCodeAndVersion.get("version"));
908        
909        pegaseProgram.setCode(code);
910
911        pegaseProgram.setLibelle(StringUtils.truncate(program.getTitle(), 50)); // le libellé court doit faire moins de 50 caractères
912        pegaseProgram.setLibelleLong(StringUtils.truncate(program.getTitle(), 150)); // le libellé court doit faire moins de 150 caractères
913
914        _getContentValue(program, AbstractProgram.ECTS)
915            .map(c -> c.<String>getValue(OdfReferenceTableEntry.CODE))
916            .map(Double::parseDouble)
917            .ifPresent(pegaseProgram::setEcts);
918
919        pegaseProgram.setTypeFormation(new FormationTypeFormation().code(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.EDUCATION_KIND)));
920        
921        // If the Program is of education kind "diplomante", add the additional fields
922        if (__CODE_FORMATION_DIPLOMANTE.equals(pegaseProgram.getTypeFormation().getCode()))
923        {
924            _fieldsIfDiplomante(program, pegaseProgram);
925        }
926        
927        return _createOrEditProgram(code, version, pegaseProgram, report);
928    }
929    
930    private void _fieldsIfDiplomante(Program program, Formation pegaseProgram)
931    {
932        Content degree = _getContentValue(program, AbstractProgram.DEGREE).orElse(null);
933        
934        // Degree -> typeDiplome
935        pegaseProgram.setTypeDiplome(_getPegaseCodeOrCode(degree));
936        
937        // degreeNature -> natureDiplome
938        Optional.ofNullable(degree)
939            .map(d -> d.<String>getValue("degreeNature"))
940            .ifPresent(pegaseProgram::setNatureDiplome);
941        
942        // Cursus
943        Optional.ofNullable(degree)
944            .map(d -> d.<String>getValue("cursus"))
945            .ifPresent(pegaseProgram::setCursus);
946        
947        // educationLevel -> niveauFormation
948        pegaseProgram.setNiveauFormation(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.LEVEL));
949        
950        // RncpLevel -> niveauDiplome
951        String rncpLevelValue = _getContentValues(program, AbstractProgram.RNCP_LEVEL)
952            .map(this::_getPegaseCodeOrCode)
953            .collect(Collectors.joining(","));
954        pegaseProgram.setNiveauDiplome(rncpLevelValue);
955        
956        // Domain
957        if (((ElementDefinition) program.getDefinition(AbstractProgram.DOMAIN)).isMultiple())
958        {
959            _getContentValues(program, AbstractProgram.DOMAIN)
960                .findFirst()
961                .map(this::_getPegaseCodeOrCode)
962                .ifPresent(pegaseProgram::setDomaineFormation);
963        }
964        else
965        {
966            pegaseProgram.setDomaineFormation(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.DOMAIN));
967        }
968        
969        // ProgramFields -> champFormation
970        String programFieldsValue = _getContentValues(program, AbstractProgram.PROGRAM_FIELD)
971            .map(this::_getPegaseCodeOrCode)
972            .collect(Collectors.joining(","));
973        if (StringUtils.isNotBlank(programFieldsValue))
974        {
975            pegaseProgram.setChampFormation(programFieldsValue);
976        }
977
978        pegaseProgram.setMention(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.MENTION));
979    }
980    
981    private Formation _createOrEditProgram(String code, BigDecimal version, Formation pegaseProgram, ExportReport report) throws PegaseExportException, IOException
982    {
983        // Check if the Program already exist in this version
984        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code, version);
985        
986        // If no version is requested
987        if (version == null)
988        {
989            // If no program with this code was found in Pegase, create a new Program
990            if (objetMaquette == null)
991            {
992                try
993                {
994                    return _pegaseApiManager.getFormationsApi().creerFormation(_structureCode, pegaseProgram);
995                }
996                catch (ApiException e)
997                {
998                    report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
999
1000                    throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1001                }
1002            }
1003            
1004            // If a program was found, try to edit it
1005            String id = objetMaquette.getId();
1006            pegaseProgram.setId(id);
1007          
1008            try
1009            {
1010                return _pegaseApiManager.getFormationsApi().modifierFormation(_structureCode, id, pegaseProgram);
1011            }
1012            catch (ApiException e)
1013            {
1014                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1015                
1016                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1017            }
1018        }
1019        
1020        // If a version was requested 
1021        
1022        // If a program already exist in this version, try to edit it
1023        if (objetMaquette != null)
1024        {
1025            try
1026            {
1027                String id = objetMaquette.getId();
1028                
1029                pegaseProgram.setId(id);
1030                
1031                return _pegaseApiManager.getFormationsApi().modifierFormation(_structureCode, id, pegaseProgram);
1032            }
1033            catch (ApiException e)
1034            {
1035                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1036
1037                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1038            }
1039        }
1040        
1041        // If no program was found in Pegase with this code and version
1042        // Check if the program exist in another version
1043        objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
1044      
1045        // If the program exist in another version
1046        if (objetMaquette != null)
1047        {
1048            try
1049            {
1050                // Generate a new version of the Pegase Program
1051                Formation newVersionProgram = _pegaseApiManager.getFormationsApi().generer(_structureCode, objetMaquette.getId());
1052              
1053                String id = newVersionProgram.getId();
1054                pegaseProgram.setId(id);
1055                
1056                // Edit the new Program created
1057                return _pegaseApiManager.getFormationsApi().modifierFormation(_structureCode, id, pegaseProgram);
1058            }
1059            catch (ApiException e)
1060            {
1061                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1062                
1063                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1064            }
1065        }
1066        
1067        // If no program was found for this code at all
1068        else
1069        {
1070            try
1071            {
1072                // Create the program in Pegase
1073                return _pegaseApiManager.getFormationsApi().creerFormation(_structureCode, pegaseProgram);
1074            }
1075            catch (ApiException e)
1076            {
1077                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1078
1079                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1080            }
1081        }
1082    }
1083    
1084    private String _createOTTOrGroupFromContainer(Container container, ExportReport report) throws PegaseExportException, IOException
1085    {
1086        String type = null;
1087        
1088        String childNatureCode = getContainerNatureCode(container);
1089        boolean isYear = "annee".equals(childNatureCode);
1090        boolean isSemester = "semestre".equals(childNatureCode);
1091        
1092        // If it is a container of nature : semester of year, export it as an OTT of type semester or year
1093        if (isYear || isSemester)
1094        {
1095            type = isYear ? "ANNEE" : "SEMESTRE";
1096            
1097            ObjetFormation objetFormationCreated = _createObjetFormation(container, type, report);
1098            String pegaseId = objetFormationCreated.getId();
1099
1100            Map<String, Enfant> containerChildren = new HashMap<>();
1101
1102            // Create the children
1103            containerChildren = _createAllChildren(containerChildren, _odfHelper.getChildProgramItems(container), report);
1104            
1105            // Attach the children that were created
1106            _attachAllChildren(container, pegaseId, containerChildren, report);
1107            
1108            return pegaseId;
1109        }
1110        
1111        // If it is a container without a nature, export it as a group
1112        return _createGroupFromContainer(container, report);
1113    }
1114
1115    private String _createGroupFromContainer(Container container, ExportReport report) throws PegaseExportException, IOException
1116    {
1117        Map<String, Enfant> coursesChild = new HashMap<>();
1118        Groupement pegaseGroup = _createGroupFromContent(container);
1119
1120        // Create the children
1121        coursesChild = _createAllChildren(coursesChild, _odfHelper.getChildProgramItems(container), report);
1122        
1123        pegaseGroup.setEnfants(List.copyOf(coursesChild.keySet()));
1124        
1125        // Create the group
1126        Groupement groupementCreated = _createGroup(pegaseGroup, report);
1127        String pegaseId = groupementCreated.getId();
1128        
1129        // Attach the children to the group
1130        _attachAllChildrenGroupementCase(container, pegaseId, coursesChild, report);
1131        
1132        return pegaseId;
1133    }
1134    
1135    private Groupement _createGroup(Groupement pegaseGroup, ExportReport report) throws PegaseExportException, IOException
1136    {
1137        String code = pegaseGroup.getCode();
1138        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
1139        Groupement pegaseGroupCreated;
1140        
1141        // Check if the object already exists
1142        if (objetMaquette != null)
1143        {
1144            // If it already exist, check if it can be edited
1145            if (_isModifiable(objetMaquette, code, report))
1146            {
1147                String id = objetMaquette.getId();
1148                pegaseGroup.setId(id);
1149        
1150                // Edit the already existing object
1151                try
1152                {
1153                    return _pegaseApiManager.getGroupementsApi().modifierGroupement(_structureCode, id, pegaseGroup);
1154                }
1155                catch (ApiException e)
1156                {
1157                    report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
1158                    
1159                    throw new PegaseExportException("Le groupement " + pegaseGroup.getLibelle() + " n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1160                }
1161            }
1162            
1163            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_ALREADY_EXIST);
1164
1165            throw new PegaseExportException("Un élément avec ce code existe déjà et n'est pas mutualisable ou modifiable");
1166        }
1167        
1168        // If it did not already exist, create the object
1169        try
1170        {
1171            pegaseGroupCreated = _pegaseApiManager.getGroupementsApi().creerGroupement(_structureCode, pegaseGroup);
1172            
1173            if (pegaseGroupCreated != null)
1174            {
1175                return pegaseGroupCreated;
1176            }
1177        }
1178        catch (ApiException e)
1179        {
1180            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
1181            
1182            throw new PegaseExportException("Le groupement " + pegaseGroup.getLibelle() + " n'a pas pu être exporté dû à un problème rencontré avec Pégase", e);
1183        }
1184        
1185        return null;
1186    }
1187
1188    private String _createOOFromSubProgram(SubProgram subProgram, ExportReport report) throws PegaseExportException, IOException
1189    {
1190        ObjetFormation objetFormationCreated = _createObjetFormation(subProgram, "PARCOURS-TYPE", report);
1191        String pegaseId = objetFormationCreated.getId();
1192
1193        Map<String, Enfant> containerChildren = new HashMap<>();
1194
1195        // Create the children
1196        containerChildren = _createAllChildren(containerChildren, _odfHelper.getChildProgramItems(subProgram), report);
1197
1198        // Attach the children
1199        _attachAllChildren(subProgram, pegaseId, containerChildren, report);
1200        
1201        return pegaseId;
1202    }
1203    
1204    private String _createOPFromCourse(Course course, ExportReport report) throws PegaseExportException, IOException
1205    {
1206        // Get the nature of the course
1207        String type = _getPegaseCodeOrCodeOfAttribute(course, Course.COURSE_TYPE);
1208        
1209        // Create the course in Pegase
1210        ObjetFormation objetFormationCreated = _createObjetFormation(course, type, report);
1211        String pegaseId = objetFormationCreated.getId();
1212        Map<String, Enfant> containerChildren = new HashMap<>();
1213
1214        // Create the children
1215        containerChildren = _createAllChildren(containerChildren, _odfHelper.getChildProgramItems(course), report);
1216
1217        // Attach the children
1218        _attachAllChildren(course, pegaseId, containerChildren, report);
1219
1220        return pegaseId;
1221    }
1222    
1223    private Groupement _createGroupFromContent(Content content)
1224    {
1225        Groupement pegaseGroup = new Groupement();
1226        String code = content.getValue(__PEGASE_SYNC_CODE);
1227        
1228        pegaseGroup.setStructure(_structureCode);
1229        pegaseGroup.setCode(code);
1230        pegaseGroup.setLibelle(StringUtils.truncate(content.getTitle(), 50)); // le libellé est limité à 50 caractères
1231        pegaseGroup.setPlageDeChoix(false);
1232        pegaseGroup.setMutualise(true);
1233        pegaseGroup.setEnfants(List.of());
1234        
1235        return pegaseGroup;
1236    }
1237    
1238    private String _createGroupFromCourseList(CourseList courseList, ExportReport report) throws PegaseExportException, IOException
1239    {
1240        Map<String, Enfant> coursesChild = new HashMap<>();
1241        Groupement pegaseGroup = _createGroupFromContent(courseList);
1242        
1243        coursesChild = _createAllChildren(coursesChild, _odfHelper.getChildProgramItems(courseList), report);
1244        
1245        pegaseGroup.setEnfants(List.copyOf(coursesChild.keySet()));
1246        
1247        Groupement pegaseGroupCreated = _createGroup(pegaseGroup, report);
1248        
1249        String pegaseId = pegaseGroupCreated.getId();
1250
1251        _attachAllChildrenGroupementCase(courseList, pegaseId, coursesChild, report);
1252        
1253        return pegaseId;
1254    }
1255
1256    /**
1257     * Create a program in Pegase
1258     * @param program the program to export
1259     * @param report the Pegase export report
1260     */
1261    @Override
1262    public void createProgram(Program program, ExportReport report)
1263    {
1264        if (!_isActive)
1265        {
1266            throw new UnsupportedOperationException("Pégase is not active in the configuration, you cannot import or synchronize a program in Pégase.");
1267        }
1268        
1269        int nbTotal = _getEveryElements(program).size();
1270        
1271        report.setNbTotal(nbTotal);
1272        
1273        Formation pegaseProgramCreated;
1274        try
1275        {
1276            // Create the Program in Pegase
1277            pegaseProgramCreated = _createProgram(program, report);
1278            report.addElementExported(program);
1279            
1280            // Get the Id of the created formation from Pegase that is going to be used to attach the children to the program
1281            String pegaseProgramId = pegaseProgramCreated.getId();
1282            
1283            String codeParentProgram = pegaseProgramCreated.getCode();
1284            int versionParentProgram = 1;
1285            
1286            if (codeParentProgram.contains("/"))
1287            {
1288                String[] codeAndVersionTab = codeParentProgram.split("/");
1289                if (codeAndVersionTab.length == 2)
1290                {
1291                    codeParentProgram = codeAndVersionTab[0];
1292                    versionParentProgram = Integer.parseInt(codeAndVersionTab[1]);
1293                } 
1294            }
1295            
1296            report.setCodeAndVersion(codeParentProgram, versionParentProgram);
1297            
1298            // Get the children of the Ametys program
1299            List<ProgramPart> children = program.getProgramPartChildren();
1300            Map<String, Enfant> childrenToLink = new HashMap<>();
1301            
1302            for (ProgramPart child : children)
1303            {
1304                // Create the pegase instance of the child
1305                Enfant enfant = _createChild((Content) child, report);
1306                
1307                // Add the Enfant (created from the Pegase Id of the child) to the list of "Enfant" to link, if it is not already attached
1308                if (enfant != null)
1309                {
1310                    childrenToLink.put(enfant.getRef().getId(), enfant);
1311                }
1312            }
1313            
1314            _attachAllChildren(program, pegaseProgramId, childrenToLink, report);
1315            
1316        }
1317        catch (IOException e)
1318        {
1319            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1320            getLogger().error("Le jeton d'authentification à Pégase n'a pas pu être récupéré", e);
1321        }
1322        catch (PegaseExportException e)
1323        {
1324            report.updateStatus(ExportStatus.ERROR);
1325            getLogger().error("Une erreur est survenue lors de l'export de la formation '{}' ({}) dans Pégase", program.getTitle(), program.getId(), e);
1326        }
1327    }
1328    
1329    private Set<ProgramItem> _getEveryElements(ProgramItem program)
1330    {
1331        Set<ProgramItem> elementNotExported = new HashSet<>();
1332        elementNotExported.add(program);
1333        
1334        for (ProgramItem child : _odfHelper.getChildProgramItems(program))
1335        {
1336            elementNotExported.addAll(_getEveryElements(child));
1337        }
1338        
1339        return elementNotExported;
1340    }
1341}