define(function () {
  var homeFactory = function ($q, $http, $rootScope, $filter, $timeout, ApplicationFactory,
      ConfigFactory, PortalsFactory, gaJsUtils, gaDomUtils) {
    const kis_home_app = 'kis_home';
    const resources = {homepages: [], images: {groups: [], links: [], other: []}};
    const vAlignPositions = ['start', 'center', 'end'];
    const hAlignPositions = ['left', 'center', 'right'];
    const wheelDirections = ['left', 'right'];

    /**
     * Ouvre un nouvel onglet du navigateur web sur la page de l'application.<br>
     * Reformate fonction entre homeAppSwitcher, homeAppCarousel, homepageNews et mapLeftMenu
     * Evite de déclarer homeFactory dans PortalsFactory
     * @param portalid uid identifiant du portail
     * @param app application kis
     * @param distinctAppName (optionnel) false ds mapleftMenu qd scope.currentAppName === app.name
     */
    const gotoApp = (portalid, app, distinctAppName = true) => {
      let main = document.getElementById('xgos_main');
      if (main && main.children.length > 0 && main.children[0].style.height === '100%') {
        main.children[0].style.height = 'unset';
        main = null;
      }
      PortalsFactory.gotoApp(portalid,app,distinctAppName);
    };

    /**
     * Fonction factorisée depuis 1ère page d'accueil existante pour être
     * utilisable depuis les différentes pages d'accueil.
     * Lance la récupération des applications dans les resources du service
     * @return {Promise} configuration de l'application kis_home
     */
    const getApplicationThenGetConfiguration = () => {
      const defer = $q.defer();
      // get applications
      if (gaJsUtils.notNullAndDefined(ApplicationFactory.resources.applications)
          && ApplicationFactory.resources.applications.length > 0) {
        getConfig(defer);
      } else {
        ApplicationFactory.get(false).then(
            () => {
              getConfig(defer);
            },
            (errApps) => {
              console.error(errApps.data && errApps.data.message ? errApps.data.message :
                  $filter('translate')('portals.homepage.getAppsError'));
              defer.resolve(null);
            });
      }
      return defer.promise;
    };

    /**
     * Récupère la configuration de kis_home
     * @param {object} defer instance de deferred
     */
    const getConfig = (defer) => {
      // retrieve the home configuration
      ConfigFactory.get('home', 'config', kis_home_app).then(
          (conf) => {
            defer.resolve(conf.data);
          },
          (errConf) => {
            console.error(errConf.data && errConf.data.message ? errConf.data.message :
                $filter('translate')('portals.homepage.getConfigError'));
            defer.resolve(null);
          }
      );
    };

    /**
     * Récupère la liste des pages d'accueil du portail
     * soit le tableau "homepages" du fichier homepages.json se trouvant dans le dossier "CSS-HTML" du repo
     * @param {string} portalid uid identifiant du portail dont on récupère les pages d'accueil
     * @return {Promise} liste des pages d'accueil. Tableau de dto de type Homepage
     */
    const get = (portalid) => {
      let defer = $q.defer();
      $http.get('/services/{portalid}/homepage/get?f=json&').then(
          (res) => {
            if (res.data && Array.isArray(res.data)) {
              const homepages = addHomepages(res.data);
              resources.homepages = homepages;
              defer.resolve(homepages);
            } else {
              defer.resolve([]);
              console.error($filter('translate')('portals.homepage.getHomepagesError') + portalid)
            }
          },
          (err) => {
            if (Array.isArray(err.data)) {
              for (const error of err.data) {
                require('toastr').error(error);
              }
            }
            defer.resolve([]);
          }
      )
      return defer.promise;
    }

    /**
     * Sauvegarde ou met à jour une page d'accueil
     * @param {object} homepage page d'accueil, objet ayant la structure d'un dto de type Homepage
     * @param {string} header en-tête personnalisé de la page d'accueil
     * @return {Promise} page d'accueil savegardée
     */
    const save = (homepage, header) => {
      gaDomUtils.showGlobalLoader();
      let defer = $q.defer();
      if (!homepage) {
        defer.resolve();
      }
      const data = {homepage: homepage};
      if (header) {
        data.header = header;
      }
      $http.post('/services/{portalid}/homepage/save?f=json&', data).then(
          (res) => {
            if (res.data && res.data.hasOwnProperty('id') && Number.isInteger(res.data.id)) {
              const homepage = addHomepages(res.data);
              defer.resolve(homepage);
            } else {
              require('toastr').error($filter('translate')('portals.homepage.nok'));
              defer.resolve(null);
            }
            gaDomUtils.hideGlobalLoader();
          },
          err => {
            require('toastr').error($filter('translate')('portals.homepage.nok'));
            console.error(err.data && err.data.message ? err.data.message : '');
            gaDomUtils.hideGlobalLoader();
          }
      );
      handleHomepageNewsErrors(defer.promise);
      return defer.promise;
    };

    /**
     * Récupère la page d'accueil ayant un id égal au nombre entier fourni en paramètre
     * @param {number} id entier id de la page d'accueil homepage.id
     * @return {*} page d'accueil, dto de type Homepage
     */
    const getById = (id) => {
      let defer = $q.defer();
      if (id) {
        let homePageInService = resources.homepage.find(f => f.id === id);
        if (!homePageInService) {
          $http.get('/services/{portalid}/homepage/getById?f=json'
              + '&id=' + id
          ).then(
              res => {
                if (res.data && !(typeof res.data === 'string' && res.data === '')
                    && res.data.hasOwnProperty('id')) {
                  const homepage = addHomepages(res.data);
                  defer.resolve(homepage);
                } else {
                  defer.resolve(null);
                }
              },
              (err) => {
                if (Array.isArray(err.data)) {
                  for (const error of err.data) {
                    require('toastr').error(error);
                  }
                }
                defer.resolve(null);
              }
          );
        } else {
          console.log('HomeFactory.getById(' + id + ') - homepage trouvée dans la factory');
          defer.resolve(homePageInService);
        }
      } else {
        console.log('HomeFactory.getById() - le paramètre id est null');
        defer.resolve(null);
      }
      return defer.promise;
    };

    /**
     * Récupère le nom de la page d'accueil active du portail courant.
     * Si xgos.portal est null et si portalid existe, on vérifie dans les portails de la factory.
     * Si besoin, on initialise les portails de la factory.
     * @param {string} portalid uid identifiant du portail (optionnel)
     * @return {Promise} contient le nom de la page d'accueil personnalisée active ou 'default'
     */
    const getPortalHomepageName = (portalid) => {
      const defer = $q.defer();
      let currentPortal = null;

      // récupère le nom de la page d'accueil actif dans les paramètres du portail
      const getPortalActiveHomepageName = (def, portal) => {
        if (portal !== null && portal !== undefined
            && gaJsUtils.notNullAndDefined(portal, 'parameters.homepage')
            && typeof portal.parameters.homepage.activename == 'string'
            && portal.parameters.homepage.activename.length > 0) {
          // renvoie le nom de la page d'accueil actif du portail courant
          // si le portail et le paramètre existent
          def.resolve(portal.parameters.homepage.activename);
        } else {
          // renvoie 'defaut' (pas de page d'accueil)
          // si le portail n'existe pas ou si le paramètre n'existe pas
          def.resolve('default');
        }
      };

      // on récupère le portail
      // d'abord on vérifie l'existence du portail de xgos
      if (gaJsUtils.notNullAndDefined($rootScope.xgos, 'portal')) {
        currentPortal = $rootScope.xgos.portal;
        // renvoie le nom de la page d'accueil active du portail de xgos
        getPortalActiveHomepageName(defer, currentPortal);
      }
      // sinon on utilise le paramètre 'portalid' de la méthode s'il a été fourni
      else if (portalid !== null && portalid !== undefined
          && typeof portalid === 'string' && portalid.length > 0) {

        // on recherche dans les portails des resources de la factory si elle en contient
        if (PortalsFactory.resources.portals && PortalsFactory.resources.portals.length > 0) {
          currentPortal = PortalsFactory.resources.portals.find(p => p.uid === portalid);
          // renvoie le nom de la page d'accueil active du portail de la factory
          getPortalActiveHomepageName(defer, currentPortal);
        } else {
          // sinon on récupère les portails
          PortalsFactory.get().then(
              () => {
                currentPortal = PortalsFactory.resources.portals.find(p => p.uid === portalid);
                // renvoie le nom de la page d'accueil active du portail de la factory
                getPortalActiveHomepageName(defer, currentPortal);
              },
              () => {
                // renvoie 'default'
                getPortalActiveHomepageName(defer);
                console.error($filter('translate')('portals.homepage.getPortalsError'));
              }
          )
        }
      } else {
        // sans portail trouvé et sans paramètre 'portalid'
        // retourne 'default'
        defer.resolve('default')
      }
      return defer.promise;
    };

    /**
     * Préfixe le nom de l'image du chemin d'accès au fichier situé dans le dossier "CSS-HTML" du repo
     * @param {string} portalid uid identifiant du portail
     * @param {string} image nom de l'image
     * @return {string} source de l'image vers le fichier situé dans le dossier "CSS-HTML" du repo
     */
    const prefixImageName = (portalid, image) => {
      return '/services/' + portalid + '/stylesheet/downloadfile?fileName=' + image;
    };

    /**
     * Récupère les images des groupes ou des liens
     * @return {map} objet répartissant dans des tableaux les images disponibles dans le dossier CSS-HTML du repo
     * et utilisable pour des groupes, des liens, le logo ou des arrières-plan.
     */
    const getImages = () => {
      let defer = $q.defer();
      let serviceHasImage = false;
      // teste si au moins 1 image existe dans le service
      if (resources.images && Object.values(resources.images).length > 0) {
        for (const imagesByType of Object.values(resources.images)) {
          if (Array.isArray(imagesByType) && imagesByType.length > 0) {
            for (const image of imagesByType) {
              if (typeof image == 'string' && image.length > 0) {
                serviceHasImage = true;
                break;
              }
            }
          }
        }
      }
      if (serviceHasImage) {
        const imagesAsObject = transformImage(resources.images);
        defer.resolve(imagesAsObject);
      } else {
        $http.get('/services/{portalid}/homepage/getImages?f=json').then(
            (map) => {
              if (map.data) {
                if (map.data.other && map.data.groups && map.data.links) {
                  // si le format du retour est correct
                  resources.images = map.data;
                  const imagesAsObject = transformImage(map.data);
                  defer.resolve(imagesAsObject);
                } else {
                  console.error('homepage/getImages renvoie null');
                  defer.resolve();
                }
              }
            },
            (err) => {
              console.error(err && err.data && err.data.message ? err.data.message : '');
              require('toastr').error($filter('translate')('portals.homepage.getSvgError'));
              defer.resolve();
            });
      }
      return defer.promise;
    };

    /**
     * Copie le fichier dont le nom est fourni en paramètre du dossier "UPLOAD"
     * au dossier "CSS-HTML" d'un repo
     * @param {string} portalid uid identifiant du portail
     * @param {string} uploadProcessId uid identifiant du process d'upload (cf. dzMemShare.uploadProcessID)
     * @param {string} filename string du nom du fichier à copier
     * @param {boolean} getBase64 true si on veut récupérer l'image en basse64 plutot que le nom du fichier
     * @return {Promise} le nom du fichier si la copie a réussi ou bien une string vide
     */
    const copyUploadedImgToCssRepo = (portalid, uploadProcessId, filename, getBase64 = false) => {
      return $http.get('/services/{portalid}/homepage/copyLogoOrImage?f=json'
          + '&portalid=' + portalid
          + '&processid=' + uploadProcessId
          + '&filename=' + filename
          + '&base64=' + getBase64);
    };

    /**
     * Lance l'appel pour sauvegarder l'en-tête personnalisé dans la page d'accueil
     * @param {number} homepageid propriété id de l'objet homepage lié à utiliser dans le nom du fichier à créer
     * @param {string} headerAsHtml <code>document.getElementById('kis-home-news').innerHTML</code>
     * @return {Promise} true si le fichier "<code>header-{id}.html</code>" a bien été créé
     */
    const saveHomepageHeader = (homepageid, headerAsHtml) => {
      return $http.post('/services/{portalid}/homepage/saveHomepageHeader?f=json'
          + '&homepageid=' + homepageid,
          headerAsHtml);
    };

    /**
     * Supprime un fichier image dans le dossier CSS-HTML/homepage du repo
     * @param {string} portalid uid identifiant du portail
     * @param {string} imageName nom du fichier image à supprimer dans le repo
     */
    const deleteImageByName = (portalid, imageName) => {
      return $http.get('/services/' + portalid + '/homepage/deleteImageByName?f=json'
          + '&imageName=' + imageName
      );
    };

    /**
     * supprime une image de la bibliotheque (pour le logo et le background):<ul><li>
     *  - supprime le fichier dans le repo</li><li>
     *  - supprime l'image dans l'interface front-end</li></ul>
     * @param {string} portalId portalid
     * @param {object[]} images bibliothèque d'images
     * @param {number} indexToDelete index de l'image dans la bibliothèque
     */
    const deleteImage = (portalId, images, indexToDelete) => {
      let imageDeleted = undefined;
      // remove the correct image in scope.images.xxx
      if (Array.isArray(images) && images.length > indexToDelete) {
        imageDeleted = images.splice(indexToDelete, 1)[0];
      }
      if (imageDeleted) {
        deleteImageByName(portalId, imageDeleted.src).catch(err => {
          console.error(err.data);
        });
      }
    };

    /**
     * ouvre une popup pour demander la confirmation de la suppression
     * @param {string} portalId portalid
     * @param {string[]} images bibliothèque d'images
     * @param {int} indexToDelete index de l'image dans la bibliothèque
     */
    const ShowDialogToDeleteImage = (portalId, images, indexToDelete) => {
      swal(
        {
          title: $filter('translate')('portals.homepage.v2.deleteImage'),
          text: $filter('translate')('portals.homepage.v2.definitiveAction'),
          type: 'warning',
          showCancelButton: true,
          confirmButtonColor: '#DD6B55',
          confirmButtonText: $filter('translate')('common.delete'),
          cancelButtonText: $filter('translate')('common.cancel'),
          closeOnConfirm: true,
        },
        function(isConfirm) {
          if (isConfirm) {
            deleteImage(portalId, images, indexToDelete);
          }
        }
      );
    };

    /**
     * Récupère la page d'accueil portant le nom en paramètre et renvoie
     * la page d'accueil et le header HTML (s'il en existe un).<br>
     * Ajoute la page d'accueil aux resources du service.<br>
     * La liste des applications KIS est nécessaire pour
     * filtrer les liens internes par rôle utilisateur
     * @param {string} name propriété 'name' de la homepage
     * @param {boolean} bypassRights true quand on ne filtre pas les liens selon le rôle de l'utilisateur
     * @return {Promise} objet contenant une propriété 'homepage'
     * et éventuellement une propriété 'header'
     */
    const getByNameAndHeader = (name, bypassRights = false) => {
      let defer = $q.defer();
      $http.get('/services/{portalid}/homepage/getByNameAndHeader?f=json'
          + '&name=' + name
      ).then(
          (res) => {
            // affiche dans un toastr les éventuelles erreurs
            // levées à la lecture de la bd pour récupérer les news de la page d'accueil
            handleHomepageErrors(res.data);

            const setHomepageAndHeader = (data) => {
              const container = {};
              if (data) {
                if (data.homepage && Object.keys(data.homepage).length > 0) {

                  // ajoute les propriétés "datestring" et "time" aux actualités:
                  if (data.homepage.news && Array.isArray(data.homepage.news.events) && data.homepage.news.events.length > 0) {
                    addNewsEventsDatestring(data.homepage.news.events);
                    addNewsEventsTime(data.homepage.news.events);
                  }

                  // addHomepages filtre les items par rôle
                  container.homepage = addHomepages(data.homepage, bypassRights);
                  if (data.header && data.header.length > 0) {
                    container.header = data.header;
                  }
                }
              }
              return container;
            };

            if (ApplicationFactory.resources.applications.length ===0) {
              ApplicationFactory.get(false).then(
                  ()=>{
                    const result = setHomepageAndHeader(res.data);
                    defer.resolve(result);
                  },
                  (err)=>{
                    console.error($filter('translate')('portals.homepage.getAppsError')
                    + err && err.data && err.data.message ? err.data.message : '');
                    const result = setHomepageAndHeader(res.data);
                    defer.resolve(result);
                  });
            } else {
              const result = setHomepageAndHeader(res.data);
              defer.resolve(result);
            }
          },
          (err) => {
            console.log(err && err.data && err.data.message ? err.data.message : '');
            require('toastr').error($filter('translate')('portals.homepage.getHeaderError'));
            defer.resolve(null);
          }
      );
      return defer.promise;
    };

    /**
     * Récupère les applications depuis le service ou depuis le repo
     * @return {object[]} tableau des applications KIS disponibles
     */
    const getApplications = () => {
      let defer = $q.defer();
      let userApps = [];
      let apps = ApplicationFactory.resources.applications;
      if (Array.isArray(apps) && apps.length > 0) {
        userApps = ApplicationFactory.resources.applications;
        defer.resolve(userApps);
      } else {
        ApplicationFactory.get(false).then(
            () => {
              userApps = ApplicationFactory.resources.applications;
            },
            err => {
              console.error($filter('translate')('portals.homepage.getAppsError')
              + err && err.data && err.data.message ? err.data.message : '');
            }
        ).finally(() => defer.resolve(userApps));
      }
      return defer.promise;
    };

    /**
     * Filtre les liens et les groupes en fonction du rôle de l'utilisateur connecté :<ul><li>
     * Enlève les liens qui ouvrent des applications pour lesquelles l'utilisateur n'a pas les droits d'accès</li><li>
     * Enlève les groupes vides</li></ul>
     * @param {object} homepage objet de page d'accueil
     * @see addHomepages
     */
    const filterHomepageByRole = (homepage) => {
      const grantedLinks = [];
      const grantedGroups = [];
      const homepageHasLinks = !!homepage && gaJsUtils.notNullAndDefined(homepage.links)
          && Array.isArray(homepage.links) && homepage.links.length > 0;
      const homepageHasGroups = !!homepage && gaJsUtils.notNullAndDefined(homepage.groups)
          && Array.isArray(homepage.groups) && homepage.groups.length > 0;
      // on utilise ce groupe intermédiaire plutôt que de se baser sur les index des groupes
      const groupsNeeded = [];

      if (homepageHasLinks && gaJsUtils.notNullAndDefined($rootScope.xgos.user)) {
        // si la page d'accueil a une liste de liens non vide. Sinon links,groups = []
        // s'il existe xgos.user et des liens dans la page d'accueil.
        // sinon links,groups = []
        for (const link of homepage.links) {

          // si l'application n'a pas de roles (lien interne vers une application kis)
          // si le lien n'a pas de rôles (lien externe vers une page web)
          // alors le lien est accessible
          let isGrantedLink = true;

          if (link.hasOwnProperty('appuid') && typeof link.appuid == 'string'
              && link.appuid.length > 0) {
            // si le lien est type à renvoyer vers un lien interne (i.e appname != null)
            if (Array.isArray(ApplicationFactory.resources.applications)
                && ApplicationFactory.resources.applications.length > 0) {
              // retrouve l'objet application référencé par le lien
              const app = ApplicationFactory.resources.applications.find(
                  a => a.uid === link.appuid);

              if (!!app && app.hasOwnProperty('roles')
                  && Array.isArray(app.roles) && app.roles.length > 0) {
                // si l'application du lien interne a des roles, alors l'utilisateur doit posséder
                // un de ces rôles pour avoir accès au lien
                isGrantedLink = false;
                for (const role of app.roles) {
                  // compare les rôles de l'application avec les rôles de xgos.user
                  isGrantedLink = gaJsUtils.userHasRole($rootScope.xgos.user, role.name);
                  if (isGrantedLink) {
                    break;
                  }
                }
              }
            } else {
              console.log("HomepageFactory#filterHomepageByRole - Impossible de filtrer " +
                  "les liens internes de la page d'accueil : " +
                  "ApplicationFactory.resources.length === 0");
            }
          } else if (!link.roles || (link.roles && Array.isArray(link.roles) && link.roles.length === 0)) {
            // si pas de roles -> on donne l'acces
            isGrantedLink = true;
          } else if (link.roles && Array.isArray(link.roles) && link.roles.length > 0) {
            // si le lien externe a des roles, alors l'utilisateur doit posséder
            // un de ces rôles pour avoir accès au lien
            isGrantedLink = false;
            for (const role of link.roles) {
              // compare les rôles du lien avec les rôles de xgos.user
              isGrantedLink = gaJsUtils.userHasRole($rootScope.xgos.user, role);
              if (isGrantedLink) {
                break;
              }
            }
          }
          if (isGrantedLink) {
            // ajoute le lien dans le tableau de la variable locale des liens autorisés
            grantedLinks.push(link);

            // si le lien est attaché à un groupe
            if (link.hasOwnProperty('groupid') && Number.isInteger(link.groupid)) {
              // si le groupe existe bel et bien dans la page d'accueil actuelle
              const groupExist = homepage.groups.find(g => g.id === link.groupid);
              // et si le groupe n'a pas déjà été rencontré
              const isAlreadyIn = groupsNeeded.indexOf(link.groupid) > -1;
              if (groupExist && !isAlreadyIn) {
                // ajoute le groupe dans le tableau de la variale locale des groupes autorisés
                groupsNeeded.push(link.groupid);
              }
            }
          }
        }
        //ajouter les groupes nécessaires dans l'ordre
        // on utilise l'ordre naturel plutôt que d'utiliser la propriété index des groupes
        for (let group of homepage.groups) {
          if (groupsNeeded.indexOf(group.id) > -1) {
            grantedGroups.push(group);
          }
        }

        // copie la variable locale des liens autorisés dans la propriété de la page d'accueil
        homepage.links = Object.assign([], grantedLinks);

        // copie la variable locale des groupes autorisés dans la propriété de la page d'accueil
        homepage.groups = Object.assign([], grantedGroups);
      }
    };

    /**
     * Filtre les liens/thèmes avant d'ajouter une page d'accueil ou un tableau de pages d'accueil
     * dans l'objet resources du service.<br>
     * Méthode utilisée sur le retour de get(), save(), getById() et getByName()
     * @param {object} homepage page d'accueil ou tableau de page d'accueil à ajouter
     * @param {boolean} bypassRights est true si on veut éviter de filtrer les liens par rôle
     * @return {object} renvoie un objet page d'accueil ou un tableau de pages d'accueil
     */
    const addHomepages = (homepage, bypassRights = false) => {
      if (!gaJsUtils.notNullAndDefined(resources.homepages)) {
        resources.homepages = [];
      }
      const addHomepage = (page) => {
        enumsToLowercase(page);
        if (!bypassRights) {
          filterHomepageByRole(page);
        }
        const index = resources.homepages.findIndex(h => h.id === page.id);
        if (index > -1) {
          resources.homepages[index] = page;
        } else {
          resources.homepages.push(page);
        }
      };
      if (Array.isArray(homepage)) {
        for (const page of homepage) {
          addHomepage(page);
        }
      } else {
        addHomepage(homepage);
      }
      return homepage;
    };

    /**
     * Vérifie si l'utilisateur xgos.user existe et est admin
     * @return {boolean} true si l'utilisateur a pour rôle 'sRootUser' ou 'rootUser'
     */
    const isUserAdmin = () => {
      return gaJsUtils.notNullAndDefined($rootScope.xgos) && ($rootScope.xgos.isroot)
          || (gaJsUtils.notNullAndDefined($rootScope.xgos, 'user')
              && (
                  gaJsUtils.userHasRole($rootScope.xgos.user, 'sRootUser')
                  || gaJsUtils.userHasRole($rootScope.xgos.user, 'rootUser')
              )
          );
    };

    /**
     * Renvoie un objet homepage pour un affichage conforme à la maquette
     * @param {string} portalid uid identifiant du portail
     * @return {object} objet ayant la structure d'un dto Homepage
     */
    const getDefaultHomepage = (portalid) => {
      return {
        id: null,
        name: 'v2',
        logo: 'logo.svg',
        active: true,
        portalid: portalid ? portalid : null,
        image: 'FondPageAccueil3.jpg',
        shownewscfg: true,
        shownews: true,
        customheader: false,
        published: false,
        title: {
          text: 'SUIVI DES SCHEMAS DIRECTEURS RESEAUX HUMIDES',
          color: '#B1CC1E',
          weight: 700,
          size: 24,
          halign: 'center'
        },
        subtitle: {
          text: '',
          color: '#4C4A49',
          weight: 300,
          size: 14,
          halign: 'left'
        },
        group: {
          image: '',
          title: {
            text: 'Je choisi mon métier',
            color: '#FFFFFF',
            bgcolor: '#00497B',
            weight: 700,
            size: 16,
            halign: 'left'
          }
        },
        link: {
          image: '',
          title: {
            text: 'Je choisi mon thème',
            color: '#FFFFFF',
            bgcolor: '#00497B',
            weight: 700,
            size: 16,
            halign: 'left'
          }
        },
        admin: getAdminGroup(2),
        news: {
          title: 'Fil d\'actualité',
          separator: '-',
          maxitem: 3,
          color: '#FFFFFF',
          bgcolor: '#00497B',
          events: [
            {
              fid: 0,
              date: '2021-11-23',
              text: 'Nouvelle du jour',
              published: true,
              homepage: 'v2'
            },
            {
              fid: 1,
              date: '2021-07-12',
              text: 'Bonjour',
              published: true,
              homepage: 'v2'
            },
            {
              fid: 2,
              date: '2021-01-29',
              text: 'Aujourd\'hui c\'est jour de chance',
              published: true,
              homepage: 'v2'
            },
            {
              fid: 3,
              date: '2020-11-24',
              text: 'Quatrième nouvelle masquée',
              published: true,
              homepage: 'v2'
            },
            {
              fid: 4,
              date: '2020-09-28',
              text: 'Cinquième nouvelle masquée',
              published: true,
              homepage: 'v2'
            }
          ]
        },
        groups: [
          {
            id: 0,
            index: 0,
            title: {
              text: 'EAU',
              color: '#FFFFFF',
              weight: 700,
              size: 14,
              valign: 'start',
              halign: 'center'
            },
            shape: 'circle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            homepageid: 0,
            active: false,
            image: 'water-group-circle-top.svg'
          },
          {
            id: 1,
            index: 1,
            title: {
              text: 'ASS',
              color: '#FFFFFF',
              weight: 700,
              size: 14,
              valign: 'end',
              halign: 'center'
            },
            shape: 'circle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            homepageid: 0,
            active: false,
            image: 'water-group-circle-bottom.svg'
          }
        ],
        links: [
          {
            id: 0,
            index: 0,
            title: {
              text: 'Entrée géographique',
              color: '#4C4A49',
              weight: 700,
              size: 14,
              valign: 'start',
              halign: 'left'
            },
            desc: {
              text: 'description de l\'élement du lien',
              color: '#4C4A49',
              weight: 300,
              size: 14,
              halign: 'left'
            },
            shape: 'rectangle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            active: false,
            image: 'marker-item-rectangle-bottom.svg',
            groupid: 0,
            appuid: null,
            url: null,
            roles: []
          },
          {
            id: 1,
            index: 1,
            title: {
              text: 'Catalogue descriptif',
              color: '#4C4A49',
              weight: 700,
              size: 14,
              valign: 'end',
              halign: 'left'
            },
            desc: {
              text: 'Fiches des ouvrages \n Fiches de synthèse des systènes',
              color: '#4C4A49',
              weight: 300,
              size: 14,
              halign: 'left'
            },
            shape: 'rectangle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            active: false,
            image: 'folders-item-rectangle-top.svg',
            groupid: 0,
            appuid: null,
            url: null,
            roles: []
          },
          {
            id: 2,
            index: 2,
            title: {
              text: 'Suivi des schémas directeurs',
              color: '#4C4A49',
              weight: 700,
              size: 14,
              valign: 'end',
              halign: 'left'
            },
            desc: {
              text: 'Cartes thématiques de synthèse \n Actions en cours \n Planification',
              color: '#4C4A49',
              weight: 300,
              size: 14,
              halign: 'left'
            },
            shape: 'rectangle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            active: false,
            image: 'screen-item-rectangle-top.svg',
            groupid: 0,
            appuid: null,
            url: 'https://www.legifrance.gouv.fr/',
            roles: []
          },
          {
            id: 3,
            index: 3,
            title: {
              text: 'Tableau de bord',
              color: '#4C4A49',
              weight: 700,
              size: 14,
              valign: 'start',
              halign: 'left'
            },
            desc: {
              text: 'description de l\'élement du lien',
              color: '#4C4A49',
              weight: 300,
              size: 14,
              halign: 'left'
            },
            shape: 'rectangle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            active: false,
            image: 'compass-item-rectangle-bottom.svg',
            groupid: 0,
            appuid: null,
            url: null,
            roles: []
          },
          {
            id: 4,
            index: 4,
            title: {
              text: 'Réglement des sols',
              color: '#4C4A49',
              weight: 700,
              size: 14,
              valign: 'end',
              halign: 'left'
            },
            desc: {
              text: 'lien internet',
              color: '#4C4A49',
              weight: 300,
              size: 14,
              halign: 'left'
            },
            active: false,
            groupid: 0,
            shape: 'rectangle',
            image: null,
            bgcolor: '#FFFFFF',
            color: '#4C4A49',
            bordercolor: '#ED3475',
            appuid: null,
            url: 'https://www.legifrance.gouv.fr/',
            roles: []
          },
          {
            id: 5,
            index: 5,
            title: {
              text: 'Lien applicatif',
              color: '#4C4A49',
              weight: 700,
              size: 14,
              valign: 'end',
              halign: 'left'
            },
            desc: {
              text: 'vers application',
              color: '#4C4A49',
              weight: 300,
              size: 14,
              halign: 'left'
            },
            shape: 'rectangle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            active: false,
            image: null,
            groupid: 1,
            appuid: null,
            url: null,
            roles: []
          },
          {
            id: 6,
            index: 6,
            title: {
              text: 'Thème d\'application',
              color: '#4C4A49',
              weight: 700,
              size: 14,
              valign: 'end',
              halign: 'left'
            },
            desc: {
              text: 'description',
              color: '#4C4A49',
              weight: 300,
              size: 14,
              halign: 'left'
            },
            shape: 'rectangle',
            bgcolor: '#FFFFFF',
            bordercolor: '#ED3475',
            active: false,
            image: null,
            groupid: 1,
            appuid: null,
            url: null,
            roles: []
          }
        ]
      };
    };

    /**
     * Construit le groupe particulier "ADMIN" de page d'accueil
     * particularité: objet ayant la structure d'un HomepageItem dans un tableau de HomepageGroup
     * @param {number} index dernière position dans le tableau des groupes
     * @return {object} objet ayant la structure d'un dto HomepageItem pour accéder à la page admin
     */
    const getAdminGroup = (index) => {
      return {
        id: null,
        index: index,
        title: {
          text: 'Admin',
          color: '#4C4A49',
          weight: 700,
          size: 14,
          valign: 'center',
          halign: 'center'
        },
        isadmin: true,
        active: false,
        shape: 'rectangle',
        image: null,
        uri: null,
        appuid: 'admin',
        bgcolor: '#FFFFFF',
        bordercolor: '#ED3475',
      };
    };


    /**
     * Construit un groupe vierge de page d'accueil
     * @param {number} newIndex position du nouvel objet dans le tableau de groupes
     * @return {*} objet avec la structure d'un dto HomepageGroup
     */
    const getEmptyGroup = (newIndex) => {
      return {
        id: null,
        index: newIndex,
        title: {
          text: '',
          color: '#4C4A49',
          weight: 300,
          size: 14,
          valign: 'center',
          halign: 'center'
        },
        shape: 'circle',
        bgcolor: '#FFFFFF',
        bordercolor: '#ED3475',
        active: false,
        image: null
      };
    };

    /**
     * Construit un lien vierge de page d'accueil
     * @param {number} newIndex position du nouvel objet dans le tableau de liens
     * @param {number} groupid id du groupe auquel appartient le lien
     * @return {*} objet avec la structure d'un dto HomepageItem
     */
    const getEmptyLink = (newIndex, groupid) => {
      const link = getEmptyGroup(newIndex);
      link.groupid = groupid;
      link.roles = [];
      link.desc = {
        text: '',
        weight: 300,
        color: '#323232',
        size: 12,
        halign: 'left'
      };
      link.title.size = 12;
      link.title.weight = 700;
      link.title.halign = 'left';
      link.shape = 'rectangle';
      link.uri = null;
      link.appuid = null;
      return link;
    };

    /**
     * Active l'objet image au rang index dans un tableau d'objets image
     * @param {object[]} images tableau d'objets images
     * @param {number} index rang de l'image à activer/désactiver dans le tableau d'objets image
     */
    const activeImageByIndex = (images, index) => {
      // désactive une éventuelle autre image déjà active
      for (let i = 0; i < images.length; i++) {
        if (images[i].active && index !== i) {
          images[i].active = false;
        }
      }
      // active/désactive l'image au rang index dans la liste d'images
      if (Number.isInteger(index) && index > -1 && index < images.length) {
        images[index].active = !images[index].active;
      }
    };

    /**
     * Initialise les boutons "Police en gras" du titre, sous-titre et des titres de bloc
     * @param {object} homepage objet page d'accueil reçu du back
     */
    const addTitlesIsBoldProperty = (homepage) => {
      const setIsBoldByWeight = (title) => {
        if (title) {
          if (!title.weight || (Number.isInteger(title.weight) && title.weight <= 400)) {
            title.weight = 400;
            title.isBold = false;
          } else {
            title.weight = 700;
            title.isBold = true;
          }
        }
      };
      // ajoute la propriété 'isBold' aux titres
      if (gaJsUtils.notNullAndDefined(homepage.title)) {
        setIsBoldByWeight(homepage.title);
      }
      if (gaJsUtils.notNullAndDefined(homepage.subtitle)) {
        setIsBoldByWeight(homepage.subtitle);
      }
      if (gaJsUtils.notNullAndDefined(homepage, 'group.title')) {
        setIsBoldByWeight(homepage.group.title);
      }
      if (gaJsUtils.notNullAndDefined(homepage, 'link.title')) {
        setIsBoldByWeight(homepage.link.title);
      }

    };

    /**
     * Change la propriété weight du titre et
     * modifie la propriété temporaire "isBold" pour répercuter l'état sur le bouton
     * @param {object} title objet titre de homepage (title, subtitle, group ou link)
     */
    const toggleBoldByWeight = (title) => {
      if (title) {
        if (!Number.isInteger(title.weight) || title.weight > 400) {
          title.weight = 400;
          title.isBold = false;
        } else {
          title.weight = 700;
          title.isBold = true;
        }
      }
    };

    /**
     * Insère chaque nom d'image de list dans un objet {nom d'image,image en base64,false}
     * et renvoie un tableau des objets constitués
     * @param {object[]} imageList liste des noms de fichiers image
     * @return {*} objet contenant des tableaux d'images ayant une propriété 'active' = false
     */
    const transformImage = (imageList) => {
      // sous-méthode
      const addActivePropertyToArray = (groupImagesMap) => {
        let imageAsObjects = [];
        if (groupImagesMap && Object.keys(groupImagesMap)) {
          for (const [filename, image64] of Object.entries(groupImagesMap)) {
            if(image64 !== 'default') {
              const extension = filename.split('.').pop();
              const prefix = 'data:image/' + (extension === 'svg' ? extension + '+xml' : extension)
                  + ';base64,';
              imageAsObjects.push({
                src: filename,
                base: prefix + image64,
                active: false
              });
            } else {
              imageAsObjects.push({
                src: filename,
                isDefault: true,
                active: false
              });
            }
          }
        }
        return imageAsObjects;
      };
      if (imageList && Object.keys(imageList).length > 0 && imageList.hasOwnProperty('groups')
          && imageList.hasOwnProperty('links') && imageList.hasOwnProperty('other')) {
        // si l'argument est un objet images
        const images = {};
        for (let [key, inputImageObject] of Object.entries(imageList)) {
          images[key] = addActivePropertyToArray(inputImageObject);
        }
        return images;
      } else {
        // si l'argument est un tableau
        return addActivePropertyToArray(imageList);
      }
    };

    /**
     * Trie un tableau suivant la propriété "index" des élements
     * @param {object[]} list tableau dont les éléments contiennent une propriété index
     */
    const sortByIndex = (list) => {
      list.sort(
          (a, b) => {
            if (a && b) {
              if (a.index && b.index) {
                return (a.index < b.index) ? -1 : (a.index > b.index ? 1 : 0);
              } else {
                return (!a.index && b.index) ? -1 : (a.index && !b.index ? 1 : 0);
              }
            } else {
              return !a && b ? -1 : (a && !b ? 1 : 0);
            }
          });
    };

    /**
     * Récupère l'élément HTML correspondant au carousel conteneur des groupes ou liens
     * @param {string} itemType 'group' ou 'link'
     * @return {HTMLElement | null} élément HTML du carousel des groupes ou des liens
     * ou null si document.getElementById a échoué
     */
    const getCarousel = (itemType) => {
      let element = null;
      const carouselId = getCarouselId(itemType);
      const container = document.getElementById(carouselId);
      if (container) {
        element = container;
      }
      return element;
    };

    /**
     * Centralise la géréation de l'attribut id
     * de l'élément HTML correspondant au carousel de groupes ou liens
     * @param {string} itemType 'group' ou 'link'
     * @return {string} string de l'attribut id de l'élément HTML du carousel de groupes ou liens
     */
    const getCarouselId = (itemType) => {
      return itemType + '-carousel';
    };

    /**
     * Incrémente l'alignement horizontal d'un titre de page d'accueil:
     * top > center > bottom ...
     * défini center par défaut
     * @param {object} title objet titre (ex. titre de page d'accueil, sous-titre ou texte de groupe/lien)
     */
    const setHalign = (title) => {
      // par défaut, l'alignement horizontal est défini au centre
      let nextHalign = hAlignPositions.find(pos => pos === 'center');
      const currentHalign = title.halign;
      if (typeof currentHalign == 'string' && currentHalign.length > 0) {
        // récupère l'index de l'alignement actuel dans le tableau des positions
        const curIndex = hAlignPositions.findIndex(pos => pos === currentHalign);
        if (curIndex > -1) {
          // défini l'index de l'alignement suivant
          let nextIndex = curIndex + 1;
          if (nextIndex > hAlignPositions.length -1) {
            // revient sur 'left' après 'right'
            nextHalign = hAlignPositions[0];
          } else {
            // bascule sur l'alignement suivant
            nextHalign = hAlignPositions[nextIndex];
          }
        }
      }
      title.halign = nextHalign;
    };

    /**
     * Défini l'index de chaque item d'un tableau en fonction de sa position dans le tableau
     * @param {object[]} items tableau d'items
     * @param {number} start index de départ (0 par défaut)
     */
    const resetIndex = (items, start= 0) => {
      for (let i = 0; i < items.length; i++) {
        items[i].index = start + i;
      }
    };

    /**
     * Transforme la casse des enums contenues dans un objet homepage (page d'accueil)
     * ou dans un tableau d'objets homepage.<br><ul>
     * Modifie<li><code>
     * homepage.groups[i].shape</code></li><li><code>
     * homepage.groups[i].title.valign</code></li><li><code>
     * homepage.links[i].shape</code></li><li><code>
     * homepage.links[i].title.valign</code></li><li><code>
     * homepage.admin.title.valign</code></li><li><code>
     * homepage.admin.title.shape</code></li></ul>
     * @param {object} homepage page d'accueil ou tableau de pages d'accueil
     */
    const enumsToLowercase = (homepage) => {
      if (Array.isArray(homepage)) {
        for (const elem of homepage) {
          enumsToLowercase(elem);
        }
      } else if (homepage) {
        if (homepage.groups && homepage.links) {
          const groupToLowercase = (list) => {
            if (list && Array.isArray(list) && list.length > 0) {
              for (const group of list) {
                if (group.title && group.title.valign && typeof group.title.valign == 'string') {
                  group.title.valign = group.title.valign.toLowerCase();
                }
                if (group.shape && typeof group.shape == 'string') {
                  group.shape = group.shape.toLowerCase();
                }
              }
            }
          };
          groupToLowercase(homepage.groups);
          groupToLowercase(homepage.links);
        }
        if (homepage.admin) {
          if (homepage.admin.title && homepage.admin.title.valign
              && typeof homepage.admin.title.valign == 'string') {
            homepage.admin.title.valign = homepage.admin.title.valign.toLowerCase();
          }
          if (homepage.admin.shape && typeof homepage.admin.shape == 'string') {
            homepage.admin.shape = homepage.admin.shape.toLowerCase();
          }
        }
      }
    };

    /**
     * Vérifie si l'utilisateur est habilité pour configurer la page d'accueil v2
     * is user allowed to configure homepage v2
     * @param {object} user objet correspondant à un User dans le back
     * @return true si l'utilisateur possède le rôle "kis_homepage" ou bien s'il est root
     */
    const isUserAllowedToConfigureHomepage = (user) => {
      return user && 
        (gaJsUtils.userHasRole(user, 'kis_homepage') || gaJsUtils.userHasRole(user, 'kis_homepage_actu') || user.login === 'root');
    };

    /**
     * Lors de la récupération de la page d'accueil,
     * affiche les erreurs levées en cas d'absence de datasource
     * ou bien lors de la récupération des news dans la base de données
     * @param {object} responseObject objet contenu dans la promise de retour du back (i.e res.data)
     */
    const handleHomepageErrors = (responseObject) => {
      if (responseObject && Array.isArray(responseObject.errors)) {
        for (const error of responseObject.errors) {
          require('toastr').error(error);
        }
      }
    };

    /**
     * Affiche les erreurs du back dans un toastr
     * @param {Promise} promise contenant la liste d'erreurs renvoyée du back
     */
    const handleHomepageNewsErrors = (promise) => {
      promise.then(
          res => {
            // affiche un message pour chaque erreur rencontrée lors de la création de la table 'kis_homepage_actualite'
            // En cas d'erreur le back renvoie une liste de string, sinon le back renvoie une liste de DTO
            if (Array.isArray(res.data) && res.data.length > 0 && typeof res.data[0] === 'string') {
              for (const error of res.data) {
                require('toastr').error(error);
              }
            }
          },
          error => {
            if (error.data && error.data.message) {
              console.log(error.data.message);
            }
          }
      );
    };

    /**
     * Ajoute une propriété "datestring" aux actualités.<br>
     * Cette propriété est nécessaire pour l'affichage dans editList
     * @param {object[]} events tableau d'actualités d'une page d'accueil (i.e homepage.news.events)
     */
    const addNewsEventsDatestring = (events) => {
      if (Array.isArray(events) && events.length > 0) {
        for (const event of events) {
          addNewsEventDatestring(event);
        }
      }
    };

    /**
     * Ajoute une propriété "datestring" à une actualité.<br>
     * Cette propriété est nécessaire pour l'affichage dans editList.
     * @param {object} event actualité d'une page d'accueil (i.e homepage.news.events[i])
     */
    const addNewsEventDatestring = (event) => {
      const date = timestampToDate(event.date);
      const days = date.getDate() > 9 ? date.getDate() : '0' + date.getDate();
      const months = (date.getMonth() + 1) > 9 ? (date.getMonth() + 1) : '0' + (date.getMonth() + 1);
      event.datestring = days + '/' + months + '/' + date.getFullYear();
    };

    /**
     * Créé la propriété "time" de chaque actualité d'un tableau à partir de la propriété "date" de chaque actualité
     * @param {object[]} events tableau d'actualités
     */
    const addNewsEventsTime = (events) => {
      if (Array.isArray(events) && events.length > 0) {
        for (const event of events) {
          event.time = getTimeFromTimestamp(event.date);
        }
      }
    };

    /**
     * Récupère les heures et les minutes d'une date fournie
     * @param {number} timestamp timestamp duquel on veut extraire le temps
     * @return {string} heures et minutes de la date avec secondes égales à zéro séparées par le caractère deux-points
     */
    const getTimeFromTimestamp = (timestamp) => {
      const date = timestampToDate(timestamp);
      const hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
      const minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
      return hours + ':' + minutes + ':000';
    };

    /**
     * Transforme un timestamp en date
     * @param {number} timestamp milisecondes depuis 01-01-1970
     * @return {Date|number} retourne l'objet Date JS ou bien le paramètre si celui-ci n'a pas pu être parsé
     */
    const timestampToDate = (timestamp) => {
      if (timestamp instanceof Date) {
        return timestamp;
      } else {
        try{
          return new Date(timestamp);
        }catch (e) {
          console.error(e);
          return timestamp;
        }
      }
    };

    /**
     * Assigne à la propriété date de l'actualité en paramètre un timestamp à partir de la date de publication de l'actualité en cours
     * à laquelle on fournit les heures et minutes à partir de la variable <code>time</code>.<br>
     * Copie la valeur de la variable <code>time</code> comme heure+minutes dans la variable <code>date</code>
     * @param {object} editedEvent actualité de page d'accueil en cours d'édition
     */
    const combineDateTime = (editedEvent) => {
      if (editedEvent && editedEvent.date && editedEvent.time) {
        const date = new Date(editedEvent.date);
        const time = editedEvent.time;
        const dateString = moment(date).format('YYYY-MM-DD');
        editedEvent.date = (new Date(dateString + ' ' + time)).getTime();
      }
    };

    /**
     * Insère ou met à jour une actualité de page d'accueil dans la table des actualités de pages d'accueil
     * @param {object} newsEvent string json de l'actualité à transformer en entité de la classe java HomepageNewsEvent
     * @return {object[]} liste des actualités de la page d'accueil dont le nom est fourni en paramètre
     */
    const saveHomepageNewsEvent = (newsEvent) => {
      const promise = $http.put('/services/{portalid}/homepage/newsEvent?f=json', newsEvent);
      updateResourcesAndHandleServerErrors(promise, newsEvent.homepage);
      return promise;
    };

    /**
     * Supprime l'actualité de page d'accueil dans la base de données contenant la table des actualités de pages d'accueil
     * @param {number} newsEventId nombre entier identifiant de l'actualité à supprimer
     * @param {string} homepageName nom de la page d'accueil dont on va retourner toutes les actualités si la suppression réussie
     * @return {object[]} liste des actualités de la page d'accueil dont le nom est fourni en paramètre
     */
    const deleteHomepageNewsEvent = (newsEventId, homepageName) => {
      const promise = $http.delete('/services/{portalid}/homepage/newsEvent?f=json'
          + '&homepage=' + homepageName + '&id=' + newsEventId);
      updateResourcesAndHandleServerErrors(promise, homepageName);
      return promise;
    };

    /**
     * Met à jour la liste d'actualités de la page d'accueil dans le tableau "resources" du service.
     * Affiche les erreurs levées dans le back durant les opérations d'enregistrement ou de suppression d'une actualité
     * @param {Promise} promise objet contenant le tableau des actualités de la page d'accueil renvoyé du back
     * @param {string} homepageName nom de la page d'accueil dont on met à jour la liste d'actualités
     */
    const updateResourcesAndHandleServerErrors = (promise, homepageName) => {
      promise.then(
          (res) => {
            if (Array.isArray(res.data)) {
              const homepageToUpdate = resources.homepages.find(page => page.name === homepageName);
              if (homepageToUpdate) {
                if (!homepageToUpdate.news) {
                  homepageToUpdate.news = {};
                }
                homepageToUpdate.news.events = res.data;
              }
            }
          },
          (err) => {
            if (err && Array.isArray(err.data)) {
              for (const error of err.data) {
                require('toastr').error(error);
              }
            }
          }
      );
    };

    /**
     * A l'ouverture de la popup d'édition d'une actualité de page d'accueil,
     * masque les compteurs de mots (wc) et de caractères (cc) de l'éditeur HTML.<br>
     * A cause du style inline on doit passer par du JS
     */
    const hideEditEventCcWcToolbar = () => {
      const countCharElt = document.getElementById('toolbarCC');
      const wordCharElt = document.getElementById('toolbarWC');
      if (countCharElt) {
        countCharElt.parentNode.removeChild(countCharElt);
        if (document.getElementById('toolbarCC') != null) {
          // A la 1ère ouverture de la popup, la suppression propre ne fonctionne pas
          // on doit passer par la fonction remove()
          document.getElementById('toolbarCC').remove();
        }
      }
      if (wordCharElt) {
        wordCharElt.parentNode.removeChild(wordCharElt);
        if (document.getElementById('toolbarWC') != null) {
          // A la 1ère ouverture de la popup, la suppression propre ne fonctionne pas
          // on doit passer par la fonction remove()
          document.getElementById('toolbarWC').remove();
        }
      }
    };

    return {
      resources: resources,
      vAlignPositions: vAlignPositions,
      hAlignPositions: hAlignPositions,
      wheelDirections: wheelDirections,
      gotoApp: gotoApp,
      getApplicationThenGetConfiguration: getApplicationThenGetConfiguration,
      getPortalHomepageName: getPortalHomepageName,
      get: get,
      save: save,
      getById: getById,
      getImages: getImages,
      copyUploadedImgToCssRepo: copyUploadedImgToCssRepo,
      saveHomepageHeader: saveHomepageHeader,
      deleteImageByName: deleteImageByName,
      deleteImage: deleteImage,
      ShowDialogToDeleteImage: ShowDialogToDeleteImage,
      getByNameAndHeader: getByNameAndHeader,
      getApplications: getApplications,
      getDefaultHomepage: getDefaultHomepage,
      getEmptyGroup: getEmptyGroup,
      getEmptyLink: getEmptyLink,
      activeImageByIndex: activeImageByIndex,
      addTitlesIsBoldProperty: addTitlesIsBoldProperty,
      toggleBoldByWeight: toggleBoldByWeight,
      sortByIndex: sortByIndex,
      prefixImageName: prefixImageName,
      getCarousel: getCarousel,
      getCarouselId: getCarouselId,
      setHalign: setHalign,
      resetIndex: resetIndex,
      isUserAdmin: isUserAdmin,
      isUserAllowedToConfigureHomepage: isUserAllowedToConfigureHomepage,
      addNewsEventDatestring: addNewsEventDatestring,
      getTimeFromTimestamp: getTimeFromTimestamp,
      combineDateTime: combineDateTime,
      saveHomepageNewsEvent: saveHomepageNewsEvent,
      deleteHomepageNewsEvent: deleteHomepageNewsEvent,
      hideEditEventCcWcToolbar: hideEditEventCcWcToolbar,
      addNewsEventsDatestring: addNewsEventsDatestring,
      addNewsEventsTime: addNewsEventsTime
    };
  };
  homeFactory.$inject = [
    '$q',
    '$http',
    '$rootScope',
    '$filter',
    '$timeout',
    'ApplicationFactory',
    'ConfigFactory',
    'PortalsFactory',
    'gaJsUtils',
    'gaDomUtils'
  ];
  return homeFactory;
});