Source: ressource/controllerImportEc.js


'use strict'

const request = require('request')
const flow = require('an-flow')
const elementtree = require('elementtree')
const config = require('./config')
const arbreCateg = config.constantes.categories.liste
const sjtObj = require('sesajstools/utils/object')

const Ref = require('../../constructors/Ref')

const xmls = ['cp', 'ce1', 'ce2', 'cm1', 'cm2', '6eme']

/**
 * Controleur /importEc/ pour importer les xml calculatice (appelé par le site ressources, après réplication des js calculatice)
 * @controller controllerImportEc
 */
module.exports = function (component) {
  component.controller('importEc', function ($ressourceRepository, $ressourceConverter, $ressourceControl, $accessControl, $json) {
    /**
     * Met à jour un arbre calculatice
     * @route GET /importEc/:xml
     * @param {Context} context
     * @param {string} xmlSuffifx Le suffixe du xml (cm2 pour ressources-cm2.xml)
     * @param {ressourceCallback} next
     */
    function getAndParseXml (context, xmlSuffix, next) {
      const arbre = getArbreDefaultValues(xmlSuffix)

      flow().seq(function () {
        $accessControl.isSesamathClient(context, this)
      }).seq(function (isSesamathClient) {
        if (isSesamathClient) this()
        else $json.denied(context, "Vous n'avez pas les droits suffisant pour accéder à cette commande")
      }).seq(function () {
        // on peut importer
        const nextStep = this
        const url = config.imports.ecBase + '/xml/ressources-' + xmlSuffix + '.xml'
        request.get(url, function (error, response, body) {
          if (error) {
            log.error(error)
            nextStep(new Error('impossible de récupérer ' + url))
          } else if (body) {
            nextStep(null, body)
          } else {
            log.error("Sur l'url " + url + ' on récupère', response)
            nextStep(new Error(url + ' renvoie une réponse vide'))
          }
        })
      }).seq(function (xmlString) {
        // log.debug('analyse de', xmlString)
        const arbreXml = elementtree.parse(xmlString)
        if (arbreXml._root) {
          if (!arbreXml._root._children || !arbreXml._root._children.length) this(new Error('xml ' + xmlSuffix + ' vide'))
          else if (arbreXml._root.tag !== 'niveau') this(new Error('xml ' + xmlSuffix + ' ne contient pas de tag niveau à la racine'))
          else if (arbreXml._root.attrib.id !== xmlSuffix) this(new Error('xml ' + xmlSuffix + ' ne contient pas le bon niveau (trouvé ' + arbreXml._root.attrib.id + ')'))
          else this(null, arbreXml._root._children)
        } else {
          this(new Error('xml ' + xmlSuffix + ' sans racine'))
        }
      }).seq(function (children) {
        // log.debug('obj xml', children, 'xml', {max:1000, indent:2})
        log.debug('parsing des enfants de ' + xmlSuffix)
        parseEnfants(children, this)
      }).seq(function (enfants) {
        arbre.enfants = enfants
        next(null, arbre)
      }).catch(function (error) {
        next(error)
      })
    } // getAndParseXml

    /**
     * Retourne les valeurs par défaut d'un arbre de ressources calculatice
     * @param xmlSuffix
     * @returns {object} {titre: string, type: string, origine: string, idOrigine: *, categories: *[], publie: boolean, restriction: number, enfants: Array}
     */
    function getArbreDefaultValues (xmlSuffix) {
      const classe = (xmlSuffix === '6eme') ? xmlSuffix : xmlSuffix.toUpperCase()
      let titre = 'Ressources Calcul@tice ' + classe
      if (xmlSuffix === 'all') {
        titre = 'Exercices de calcul mental Calcul@TICE'
        niveaux = [
          config.constantes.niveaux.cp,
          config.constantes.niveaux.ce1,
          config.constantes.niveaux.ce2,
          config.constantes.niveaux.cm1,
          config.constantes.niveaux.cm2,
          config.constantes.niveaux['6e']
        ]
      } else if (xmlSuffix === '6eme') {
        niveaux = [config.constantes.niveaux['6e']]
      } else {
        niveaux = [config.constantes.niveaux[xmlSuffix]]
      }
      return {
        titre: titre,
        type: 'arbre',
        origine: 'calculatice',
        idOrigine: xmlSuffix,
        categories: [arbreCateg],
        niveaux: niveaux,
        publie: true,
        restriction: 0,
        enfants: []
      }
    }

    /**
     * Retourne une ressource à partir d'un child exercice
     * @param child
     * @returns {Ressource}
     */
    function getEcRessource (child) {
      let ressource
      if (child.attrib.uid) {
        ressource = {
          titre: '???',
          origine: 'calculatice',
          idOrigine: child.attrib.uid,
          categories: [config.constantes.categories.exerciceInteractif],
          niveaux: niveaux,
          parametres: {}
        }
        let swf, js, options
        child._children.forEach(function (elt) {
          if (elt.tag === 'nom') ressource.titre = elt.text
          else if (elt.tag === 'fichier') swf = elt.text
          else if (elt.tag === 'fichierjs') js = elt.text
          else if (elt.tag === 'options') options = elt.text
          else log.debug("tag d'enfant d'exo ec inconnu", elt)
        })
        if (options && options !== 'default') {
          try {
            ressource.parametres.options = JSON.parse(options)
          } catch (error) {
            log.debug("parsing d'options HS", options)
            log.error(new Error("erreur sur le parsing des options de l'exercice calculatice " + ressource.idOrigine +
              ' : ' + error.toString() + '\navec\n' + options))
          }
        } else if (!options) {
          log.error(new Error('exercice calculatice ' + ressource.idOrigine + ' sans options'))
        }
        if (js) {
          ressource.type = 'ecjs'
          ressource.parametres.fichierjs = js
        } else if (swf) {
          ressource.type = 'ec2'
          ressource.parametres.fichier = swf
        }
      }

      return ressource
    } // getEcRessource

    /**
     * Passe à next les enfants d'un élément du xml
     * @param children
     * @param next callback(error, enfants)
     */
    function parseEnfants (children, next) {
      const enfants = []

      flow(children).seqEach(function (child) {
        const nextChild = this
        if (child.tag === 'exercice') {
          save(getEcRessource(child), function (error, ressource) {
            if (error) log.error(error)
            else enfants.push(new Ref(ressource))
            nextChild()
          })
        } else if (child._children.length) {
          const enfant = {}
          enfant.type = 'arbre'
          enfant.titre = getNom(child._children)
          parseEnfants(child._children, function (error, ptifils) {
            if (error) {
              nextChild(error)
            } else {
              enfant.enfants = ptifils
              enfants.push(enfant)
              nextChild()
            }
          })
        } else {
          if (child.tag !== 'nom') log.debug('child ignoré', child)
          nextChild()
        }
      }).seq(function () {
        next(null, enfants)
      }).catch(function (error) {
        log.error(error)
        next(error)
      })
    } // parseEnfants

    /**
     * Renvoie le text du premier tag nom trouvé dans les enfants passés en argument
     * @param {object[]} children
     */
    function getNom (children) {
      let i = 0
      let nom
      while (!nom && i < children.length) {
        if (children[i].tag === 'nom') {
          nom = children[i].text
        }
        i++
      }

      return nom || '???'
    }

    /**
     * Enregistre une ressource
     * @param {Ressource} ressource
     * @param next Appelé avec (error, entiteRessource)
     */
    function save (ressource, next) {
      if (ressource.idOrigine) {
        $ressourceRepository.loadByOrigin(ressource.origine, ressource.idOrigine, function (error, ressourceLoaded) {
          if (error) {
            log.error('pb au chargement : ' + error.toString(), ressource)
            next(new Error('Impossible de sauvegarder la ressource récupérée (probablement mal interprétée)'))
          } else {
            const ressourceNew = sjtObj.clone(ressourceLoaded) || {}
            sjtObj.update(ressourceNew, ressource)
            if (ressource.idOrigine == 1) { // eslint-disable-line eqeqeq
              log.debug('ressource 1 en bdd', ressourceLoaded.parametres)
              log.debug('ressource 1 passée', ressourceNew.parametres)
            }
            if (sjtObj.isEqual(ressourceLoaded, ressourceNew)) {
              next(null, ressourceNew)
            } else {
              log.debug('ressource calculatice/' + ressource.idOrigine + ' modifiée')
              $ressourceRepository.save(ressourceNew, next)
            }
          }
        })
      } else {
        log.debug('ressource incomplète', ressource)
        log.error(new Error('ressource sans idOrigine'))
        next(new Error('ressource incomplète'))
      }
    }

    /**
     * Enregistre la ressource et affiche la réponse
     * @param {Context}   context
     * @param {Ressource} ressource
     */
    function saveAndSendReponse (context, ressource) {
      save(ressource, function (error, ressource) {
        $json.send(context, error, new Ref(ressource))
      })
    }

    /**
     * Le controleur
     * @param context
     */
    function xmlController (context) {
      $accessControl.isSesamathClient(context, function (error, isSesamathClient) {
        if (error) {
          log.error(error)
          $json.denied(context, error.toString())
        } else if (isSesamathClient) {
          const xmlSuffix = context.arguments.xml
          if (!xmlSuffix) throw new Error('Il manque un argument') // devrait jamais arriver

          if (xmlSuffix === 'all') {
            const arbreAll = getArbreDefaultValues(xmlSuffix)

            flow(xmls).seqEach(function (suffix) {
              const nextXml = this
              log.debug('pour all on lance ' + suffix)
              getAndParseXml(context, suffix, function (error, arbre) {
                if (error) {
                  nextXml(error)
                } else {
                  save(arbre, function (error, arbre) {
                    if (error) {
                      nextXml(error)
                    } else {
                      arbreAll.enfants.push(new Ref(arbre))
                      nextXml()
                    }
                  })
                }
              })
            }).seq(function () {
              saveAndSendReponse(context, arbreAll)
            }).catch(function (error) {
              $json.send(context, error)
            })
          } else {
            getAndParseXml(context, xmlSuffix, function (error, arbre) {
              if (error) $json.send(context, error)
              else saveAndSendReponse(context, arbre)
            })
          }
        } else {
          $json.denied(context, "Vous n'avez pas les droits suffisant pour accéder à cette commande")
        }
      })
    }

    let niveaux // affecté dans getArbreDefaultValues et utilisé au save
    xmlController.timeout = 60000

    this.get(':xml', xmlController)
  })
}