Source: personne/servicePersonneControl.js


'use strict'

const flow = require('an-flow')
const { isInArray } = require('sesajstools')
const { addError, addWarning } = require('../lib/ressource')
const { isSameSimpleArray } = require('../lib/tools')

module.exports = function (component) {
  component.service('$personneControl', function (EntityPersonne, EntityGroupe, $personneRepository, $groupeRepository, $accessControl) {
    /**
     * Charge groupeNom pour voir s'il existe, sinon, et si on a précisé shouldBeNew on le crée,
     * et ensuite l'ajoute à la ressource si on en est membre ou qu'il était dans whiteList
     * @private
     * @param {Context} context
     * @param {Ressource} ressource
     * @param {string} groupeNom
     * @param {object} [options]
     * @param {boolean} [options.isGroupeAuteur] Ajouter le groupe à groupesAuteurs et pas groupes
     * @param {string[]} [options.whiteList] Une liste de noms de groupes que l'on peut ajouter sans vérifier si on est membre ou pas
     * @param {errorCallback} next
     */
    function addGroupe (context, ressource, groupeNom, options, next) {
      if (!ressource) throw new Error('pas de ressource')
      if (!groupeNom) throw new Error('pas de nom de groupe')
      if (arguments.length === 4) {
        next = options
        options = {}
      }
      const isGroupeAuteur = options.isGroupeAuteur === true
      const whiteList = options.whiteList || []
      if (!Array.isArray(ressource.groupes)) ressource.groupes = []
      if (!Array.isArray(ressource.groupesAuteurs)) ressource.groupesAuteurs = []
      $groupeRepository.loadByNom(groupeNom, function (error, groupe) {
        if (error) return next(error)
        if (!groupe) {
          addWarning(ressource, `Le groupe ${groupeNom} n’existe pas (ou plus)`)
          return next()
        }
        const { nom } = groupe

        if (whiteList.includes(nom)) {
          if (isGroupeAuteur) ressource.groupesAuteurs.push(nom)
          else ressource.groupes.push(nom)
          return next()
        }

        // faut les droits
        if (!$accessControl.isGroupeMembre(context, nom)) {
          addWarning(ressource, `Vous devez faire partie du groupe « ${nom} » pour y partager cette ressource`)
          return next()
        }

        if (isGroupeAuteur) {
          // il faut en plus les droits updateAuteurs
          if ($accessControl.hasPermission('updateAuteurs', context, ressource)) {
            ressource.groupesAuteurs.push(nom)
          } else {
            addWarning(ressource, 'Vous n’avez pas les droits suffirants pour modifier les groupes pouvant modifier cette ressource')
          }
        } else {
          ressource.groupes.push(nom)
        }
        return next()
      })
    }

    /**
     * Service de gestion des droits (donc demande le contexte en argument, parfois la ressource concernée)
     * à la jonction entre personne et ressource.
     * @service $personneControl
     */
    return {
      /**
       * Normalise la propriété groupes d'une ressource (en vérifiant les droits et que les groupes existent)
       * Un prof peut enlever/ajouter des groupes s'il est auteur (rien sinon)
       * @param {Context}           context
       * @param {Ressource}         ressourceOriginale
       * @param {Ressource}         ressourceNew
       * @param {ressourceCallback} next
       * @memberOf $personneControl
       */
      checkGroupes: function checkGroupes (context, ressourceOriginale, ressourceNew, next) {
        /**
         * ajoute l'erreur à la ressource et la passe à next
         * @private
         * @inner
         * @param {Error} error
         */
        function addErrorAndNext (error) {
          log.error(error)
          addError(ressourceNew, error.toString())
          next(null, ressourceNew)
        }

        if (!ressourceNew.groupes) ressourceNew.groupes = []
        if (!ressourceNew.groupesAuteurs) ressourceNew.groupesAuteurs = []
        if (!Array.isArray(ressourceNew.groupes)) {
          // on vide et on signale l'erreur sans aller plus loin
          log.error(new Error("groupes n'était pas un array"), ressourceNew.groupes)
          addError(ressourceNew, 'Groupes invalides')
          ressourceNew.groupes = []
          return next(null, ressourceNew)
        }
        if (!Array.isArray(ressourceNew.groupesAuteurs)) {
          log.error(new Error("groupesAuteurs n'était pas un array"), ressourceNew.groupesAuteurs)
          addError(ressourceNew, 'Groupes auteurs invalides')
          ressourceNew.groupesAuteurs = []
          return next(null, ressourceNew)
        }
        log.debug(`checkGroupes avec les groupes (${ressourceNew.groupes.join(',')}) groupesAuteurs (${ressourceNew.groupesAuteurs.join(',')})`)
        // les 2 cas où y'a rien à faire
        if (
          ressourceOriginale &&
          isSameSimpleArray(ressourceNew.groupes, ressourceOriginale.groupes) &&
          isSameSimpleArray(ressourceNew.groupesAuteurs, ressourceOriginale.groupesAuteurs)
        ) {
          // update sans modif de groupe
          return next(null, ressourceNew)
        }
        if (
          !ressourceOriginale &&
          !ressourceNew.groupes.length &&
          !ressourceNew.groupesAuteurs.length
        ) {
          // création de ressource sans groupes
          return next(null, ressourceNew)
        }

        // y'a des trucs à vérifier, les droits sur les groupes, seulement si la ressource existait
        // (sinon on suppose que si on est appelé c'est que le user a les droits de création de ressource donc d'updateGroupes dessus)
        if (ressourceOriginale && !$accessControl.hasPermission('updateGroupes', context, ressourceOriginale)) {
          // on remet comme c'était et on s'arrête là
          ressourceNew.groupes = ressourceOriginale.groupes
          ressourceNew.groupesAuteurs = ressourceOriginale.groupesAuteurs
          addError(ressourceNew, "Vous n'avez pas les droits suffisants pour modifier les groupes de cette ressource")
          return next(null, ressourceNew)
        }

        // on a les droits, on mémorise ce qui est demandé et ajoute les groupes sup s'il y en a
        const groupesVoulus = ressourceNew.groupes
        const groupesAuteursVoulus = ressourceNew.groupesAuteurs
        ressourceNew.groupes = []
        ressourceNew.groupesAuteurs = []
        // groupesVoulus contient tous ceux que l'on veut mettre, on vérifie s'ils y étaient déjà
        // et sinon qu'ils existent et que l'utilisateur peut les ajouter
        flow(groupesVoulus).seqEach(function (groupeNom) {
          const options = {
            whiteList: (ressourceOriginale && ressourceOriginale.groupes) || []
          }
          addGroupe(context, ressourceNew, groupeNom, options, this)
          // @todo faudrait vérifier que l'on a pas viré de groupe auxquel on appartient pas, et si c'était le cas cloner ?
        }).set(groupesAuteursVoulus).seqEach(function (groupeNom) {
          const options = {
            whiteList: (ressourceOriginale && ressourceOriginale.groupesAuteurs) || [],
            isGroupeAuteur: true
          }
          addGroupe(context, ressourceNew, groupeNom, options, this)
        }).seq(function () {
          next(null, ressourceNew)
        }).catch(addErrorAndNext)
      },

      /**
         * Normalise les propriétés auteurs et contributeurs (en vérifiant qu'ils existent)
         * Vérifie que le user courant peut modifier les auteurs
         * @param {Context}       context
         * @param {Ressource}     ressourceOriginale
         * @param {Ressource}     ressourceNew
         * @param {ressourceCallback} next
         */
      checkPersonnes: function checkPersonnes (context, ressourceOriginale, ressourceNew, next) {
        // log.debug('checkPersonnes avec les auteurs initiaux', ressourceOriginale && ressourceOriginale.auteurs)
        // log.debug('les nouveaux auteurs', ressourceNew.auteurs)
        // log.debug('et les auteurs à ajouter', ressourceNew._auteursAdd)
        const pid = $accessControl.getCurrentUserPid(context)
        // les cas où on a rien à faire
        if (
          ressourceOriginale &&
            isSameSimpleArray(ressourceNew.auteurs, ressourceOriginale.auteurs) &&
            isSameSimpleArray(ressourceNew.contributeurs, ressourceOriginale.contributeurs) &&
            !ressourceNew._auteursAdd &&
            !ressourceNew._contributeursAdd
        ) {
          // y'avait une ressource et on a rien changé
          next(null, ressourceNew)
        } else if ($accessControl.hasPermission('updateAuteurs', context, ressourceOriginale)) {
          // on a tous les droits sur les auteurs et qqchose a changé

          // les pids que l'on veut mettre (case à cocher, parmi les précédents)
          const auteurs = []
          ressourceNew.auteurs.forEach((pid) => {
            if (!isInArray(auteurs, pid)) auteurs.push(pid)
          })
          // aj auteurs sup
          if (ressourceNew._auteursAdd) {
            const tmp = ressourceNew._auteursAdd.split(',')
            delete ressourceNew._auteursAdd
            tmp.forEach(function (pid) {
              pid = pid.trim() // vire d'éventuels espaces
              if (pid && !isInArray(auteurs, pid)) {
                auteurs.push(pid)
              }
            })
          }
          // idem contributeurs
          const contributeurs = []
          ressourceNew.contributeurs.forEach((pid) => {
            if (!isInArray(auteurs, pid) && !isInArray(contributeurs, pid)) contributeurs.push(pid)
          })
          // aj contributeurs sup
          if (ressourceNew._contributeursAdd) {
            const tmp = ressourceNew._contributeursAdd.split(',')
            delete ressourceNew._contributeursAdd
            tmp.forEach(function (pid) {
              if (pid && !isInArray(contributeurs, pid) && !isInArray(auteurs, pid)) contributeurs.push(pid)
            })
          }

          // puis reset des personnes sur la nouvelle ressource
          ressourceNew.auteurs = []
          ressourceNew.contributeurs = []

          flow().seq(function () {
            // auteurs voulus
            const nextStep = this
            flow(auteurs).seqEach(function (pid) {
              const nextAuteur = this
              $personneRepository.load(pid, function (error, personne) {
                if (error) {
                  addError(ressourceNew, error.toString())
                } else if (personne) {
                  ressourceNew.auteurs.push(personne.pid)
                } else {
                  addWarning(ressourceNew, 'L’auteur d’identifiant ' + pid + ' n’existe pas')
                }
                nextAuteur()
              })
            }).done(nextStep)
          }).seq(function () {
            // contributeurs voulus
            const nextStep = this
            // flow contributeurs demandés
            flow(contributeurs).seqEach(function (pid) {
              const nextContributeur = this
              $personneRepository.load(pid, function (error, personne) {
                if (error) {
                  addError(ressourceNew, error.toString())
                } else if (personne) {
                  ressourceNew.contributeurs.push(personne.pid)
                } else {
                  addWarning(ressourceNew, 'Le contributeur d’identifiant ' + pid + ' n’existe pas')
                }
                nextContributeur()
              })
            }).done(nextStep)
          }).seq(function () {
            // terminé, mais si y'a pas d'auteurs on met au moins le user courant, si y'en a un
            if (pid && !ressourceNew.auteurs.length) ressourceNew.auteurs.push(pid)
            next(null, ressourceNew)
          }).catch(next)
        } else {
          // pas tous les droits sur les auteurs, on ajoute le user courant en auteur (nouvelle ressource)
          // ou contributeur (modif existante) sans toucher au reste
          if (ressourceOriginale) {
            ressourceNew.auteurs = ressourceOriginale.auteurs || []
            ressourceNew.contributeurs = ressourceOriginale.contributeurs || []
          }
          // le mettre en auteur ?
          if (ressourceNew.auteurs.length) {
            // y'a des auteurs
            if (ressourceNew.auteurs.indexOf(pid) === -1 && ressourceNew.contributeurs.indexOf(pid) === -1
            ) {
              // et il est pas mentionné, on le met en contributeur
              ressourceNew.contributeurs.push(pid)
            }
          } else {
            // si y'a pas d'auteurs on l'y ajoute
            ressourceNew.auteurs = [pid]
            // mais si y'a une ressource originale c'est pas normal
            if (ressourceOriginale) log.error(new Error('Il y avait une ressource sans auteurs ' + ressourceOriginale.oid))
          }
          next(null, ressourceNew)
        }
      },
      /**
       * Change les champs auteurs, contributeurs, auteursParents, contributeursParents au clonage
       * @param {Ref|Ressource} ressource
       * @param {string} pid pid de la personne qui clone
       */
      changePidsOnClone: function changePidsOnClone (ressource, pid) {
        if ((ressource.auteurs || []).includes(pid)) return // on ne change rien
        // concat & dedup, sans pid qui va se retrouver en auteurs
        const parents = ressource.auteursParents || []
        const auteurs = ressource.auteurs || []
        const contributeurs = ressource.contributeurs || []
        ressource.auteursParents = Array.from(new Set(parents.concat(auteurs).filter(p => p !== pid)))
        ressource.auteurs = [pid]
        ressource.contributeurs = contributeurs.filter(p => p !== pid)
      }
    }
  })
}