Skip to content
Snippets Groups Projects
classe_notice_integration.php 47.01 KiB
<?php
/**
 * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
 *
 * BOKEH is free software; you can redistribute it and/or modify
 * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
 * the Free Software Foundation.
 *
 * There are special exceptions to the terms and conditions of the AGPL as it
 * is applied to this software (see README file).
 *
 * BOKEH is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
 * along with BOKEH; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

require_once 'Class/Isbn.php';
require_once 'classe_indexation.php';
require_once 'classe_unimarc.php';
require_once 'classe_codif_matiere.php';
require_once 'classe_codif_langue.php';
require_once 'classe_profil_donnees.php';
require_once 'classe_communication.php';

class notice_integration {
  const
    RECORD_REJECT = 0,
    RECORD_INSERT = 1,
    RECORD_DELETE = 2,
    RECORD_FULLUPDATE = 3,
    RECORD_UPDATE = 4,
    RECORD_RENEW = 5,
    RECORD_UPGRADE = 6,
    RECORD_SUCCINCT = 7,
    STATUS_NOTFOUND = 'non trouvée';

  private $id_profil;                  // Id du profil de données initialisé
  private $format;                    // Format de fichier 0=unimarc
  private $id_article_periodique;      // Mode d'indentification des articles de periodiques
  private $type_doc_force;            // Type de document forcé dans maj_auto
  private $analyseur;                  // Instance de la classe qui découpe la notice
  private $indexation;                // Instance de la classe d'indexation
  private $id_bib;                    // Bibliotheque pour qui on integre @deprecated, see below setParamsIntegration
  private $id_int_bib;                // Intégration programmée pour qui on integre
  private $type_operation;            // Maj ou suppression
  private $notice;                    // Structure notice en cours de traitement
  private $qualite_bib;                // Code qualite de la bib
  private $sigb;                      // Sigb de la bib
  private $statut;                    // Statut de la notice traitee

  public  $libStatut = [self::RECORD_REJECT =>  "Rejetées",
                        self::RECORD_INSERT =>  "Créée",
                        self::RECORD_DELETE =>  "Supprimée",
                        self::RECORD_FULLUPDATE => "Mise à jour notice et exemplaires",
                        self::RECORD_UPDATE =>  "Mise à jour exemplaires",
                        self::RECORD_RENEW =>   "Remplacées",
                        self::RECORD_UPGRADE => "Homogénéisées",
                        self::RECORD_SUCCINCT =>"Mises en notices succintes"];

  private $erreur;                    // Message d'erreur notice traitee
  private $filtrer_fulltext;          // Vient de la variable filtrer_fulltext.
  private $identification;            // Mode d'identification de la notice
  private $notice_sgbd;                // Instance unimarc pour notices de la base
  private $mode_doublon;              // Mode de dédoublonnage (tous identifiants ou clef alpha prioritaire)
  private $url_site;                  // Url d'origine du site opac
  private $flag_koha=false;            // flag pour réentrance des notices de periodiques koha

  /** @category testing */
  protected $_service_runner;

  protected $_codif_provider;

  public function __construct() {
    $this->indexation = indexation::getInstance();
    $this->filtrer_fulltext = Class_CosmoVar::get("filtrer_fulltext");
    $this->mode_doublon = Class_CosmoVar::get("mode_doublon");
    $this->notice_sgbd = new notice_unimarc();
  }


  public function setParamsIntegration($id_bib, $type_operation, $id_profil, $type_doc_force='')  {
    /**
     * $this->id_bib is deprecated, use $this->id_int_bib instead.
     * @see http://forge.afi-sa.fr/issues/14279
     */
    $this->id_bib = $this->id_int_bib = $id_bib;
    $this->type_operation = $type_operation;
    $this->type_doc_force = $type_doc_force;

    $bib = Class_IntBib::getOrCreate($id_bib);
    $this->qualite_bib = $bib->getQualite();
    $this->sigb = $bib->getSigb();

    $this->id_profil = $id_profil;
    $profil = Class_IntProfilDonnees::getOrCreate($id_profil);

    $this->id_article_periodique = $profil->getIdArticlePeriodique();
    $this->format = $profil->isCustomFileType()
      ? $profil->getTypeFichier()
      : $profil->getFormat();

    unset($this->analyseur);

    switch($this->format) {
      case Class_IntProfilDonnees::FORMAT_UNIMARC:
      case Class_IntProfilDonnees::FORMAT_UNIMARC_XML:
        $this->analyseur = new notice_unimarc();
        break;

      case Class_IntProfilDonnees::FORMAT_CG68_ARCHIVES:
        require_once("classe_notice_archive_calice.php");
        $this->analyseur = new notice_archive_calice();
        break;

      case Class_IntProfilDonnees::FORMAT_AVENIO:
        require_once("classe_notice_avenio.php");
        $this->analyseur = new notice_avenio($id_bib);
        break;

      case Class_IntProfilDonnees::FORMAT_MARC21:
        require_once("classe_notice_marc21.php");
        $this->analyseur = new notice_marc21();
        break;

      case Class_IntProfilDonnees::FORMAT_DUBLIN_CORE:
        $this->analyseur = new Class_Cosmogramme_Integration_Record_DublinCore();
        break;

      case Class_IntProfilDonnees::FORMAT_BIBLIONDEMAND:
        $this->analyseur = new Class_Cosmogramme_Integration_Record_Bibliondemand();
        break;

      default:
        require_once("classe_notice_ascii.php");
        $this->analyseur = new notice_ascii();
        break;
    }
  }


  public function traiteNotice($data) {
    global $sql;

    $id_bib = $this->id_bib;

    if(!$this->analyseur)
      return false;

    $this->statut = static::RECORD_REJECT;
    $this->erreur = '';

    unset($this->notice);

    if(!$this->analyseur->ouvrirNotice($data,$this->id_profil,$this->sigb,$this->type_doc_force)) {
      $this->erreur=$this->analyseur->getLastError();
      return 0;
    }

    $this->notice=$this->analyseur->getNoticeIntegration();

    if($this->notice['type_doc'] == Class_TypeDoc::LIVRE_NUM) {
      $this->ecrireArticlePeriodique();
      return;
    }

    $id_notice = $this->chercheNotice();

    if($this->type_operation == 1) {
        $this->statut = static::RECORD_DELETE;

        if($this->format == Class_IntProfilDonnees::FORMAT_UNIMARC )
          $this->supprimerNotice($id_notice,$this->notice["id_origine"]);
        else
          $this->supprimerExemplaire($id_notice,$this->notice["exemplaires"][0]);
        return;
    }

    if($this->notice["statut"] == 1) {
      $this->statut = static::RECORD_DELETE;
      $this->supprimerNotice($id_notice,$this->notice["id_origine"]);
      return;
    }

    if($this->notice["type_doc"] == Class_TypeDoc::PERIODIQUE) {
      // Periodiques Koha et orphee (1 exemplaire pour chaque numéro)
      if(($this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_KOHA
          or $this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_ORPHEE)
         and $this->flag_koha == false) {
        $this->flag_koha = true;
        $ret = $this->traitePeriodiquesKoha();
        $this->flag_koha = false;
        return $ret;
      }

      // pergame : on efface tous les articles
      if($this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_PERGAME) {
        $clef_chapeau = $this->notice['clef_chapeau'];
        $clef_numero = addslashes($this->notice['tome_alpha']);
        $date = dateDuJour(0);
        Class_Notice_SerialArticles::deleteBy(['clef_chapeau' => $clef_chapeau,
                                               'clef_numero' => $clef_numero,
                                               'date_maj not' => $date]);
      }

      // opsys indexpresse : on cree les articles manquants
      if($this->id_article_periodique == Class_IntProfilDonnees::SERIAL_FORMAT_ALOES_INDEXPRESS
         and $this->notice['articles_periodiques']) {
        $enreg = ["clef_chapeau"=>$this->notice["articles_periodiques"]["clef_chapeau"],
                  "clef_numero"=>$this->notice["articles_periodiques"]["clef_numero"],
                  "date_maj"=>date("Y-m-d")];

        foreach($this->notice["articles_periodiques"]["articles"] as $id_unimarc) {
          $clef_unimarc = intval($id_unimarc);

          if(!$clef_unimarc) {
            $this->notice["warnings"][] = ["Identifiant article de périodique incorrect",
                                           $id_unimarc];
            continue;
          }

          $existe = $sql->fetchOne("select count(*) from notices_articles where clef_unimarc='$clef_unimarc'");
          if($existe){
            unset($enreg["clef_unimarc"]);
            $sql->update("update notices_articles set @SET@ where clef_unimarc='$clef_unimarc'",$enreg);
          }
          else {
            $enreg["clef_unimarc"]=$clef_unimarc;
            $sql->insert("notices_articles",$enreg);
          }
        }
      }
    }

    if(!$id_notice) {
      $this->notice["qualite"] = $this->qualite_bib;
      $id_notice = $this->insertNotice();

      if(!$id_notice)
        return ;
      else
        $this->statut = static::RECORD_INSERT;

    } else {
      $this->updateNoticeWithId($id_notice);
    }
    $this->ecrireExemplaires($id_notice);
    return $this->notice;
  }


  protected function updateNoticeWithId($id_notice) {
    if(($this->identification["statut"] == "clef_alpha") && $this->flag_koha)
      return $id_notice;

    return $this->updateNotice($id_notice,$this->qualite_bib);
  }


  public function traiteHomogene($id_notice, $isbn, $ean, $id_commerciale, $no_request) {
    global $sql;

    // Appel du service
    $args["isbn"]=$isbn;
    $args["ean"]= $ean;
    $args["id_commerciale"]=$id_commerciale;
    $args["no_request"]=$no_request;
    $ret=communication::runService(4,$args);

    // Formatter la reponse
    $ret["timeout"]=10;

    // Statut not found : Mise a jour nombre de retries
    if ($ret["statut_z3950"] == '1') {
      $notice = Class_Notice::find($id_notice);
      $retry = $notice->getZ3950Retry() + 1;
      $notice->setZ3950Retry($retry)->save();
    }

    // Statut ok : on remplace la notice
    if(!$this->analyseur) $this->analyseur=new notice_unimarc();
    if($ret["statut_z3950"] > "1")
    {
      $this->analyseur->ouvrirNotice($ret["unimarc"],
                                     $args['isbn']
                                     ? profil_donnees::HOMOGENIZATION_ISBN
                                     : profil_donnees::HOMOGENIZATION_EAN);
      $this->notice=$this->analyseur->getNoticeIntegration();
      $qualite=getVariable("homogene_code_qualite");
      $this->updateNotice($id_notice,$qualite);
    }
    return $ret;
  }


  public function traiteSuccinte($enreg) {
    global $sql;

    extract($enreg);
    /**
     * $this->id_bib is deprecated, use $this->id_int_bib instead.
     * @see http://forge.afi-sa.fr/issues/14279
     */
    $this->id_bib = $this->id_int_bib = $id_bib;
    $notice = unserialize($data);
    $this->notice = $notice;

    // On la cherche dans la base
    if ($id_notice = $this->chercheNotice()) {
      $this->handleSuccinteLocaly($notice, $id_notice, $sql);
      $ret["statut"]=4;
    }

    // On cherche sur les serveurs z3950
    else {
      $args["isbn"]=$notice["isbn"];
      $args["ean"]= $notice["ean"];
      $args["id_commerciale"]=$notice["id_commerciale"];
      $args["no_request"]=getVariable("Z3950_cache_only");
      $ret = $this->getServiceRunner()->run(4, $args);

      // Statut ok : on remplace la notice
      if (!$this->analyseur)
        $this->analyseur = new notice_unimarc();

      if ($ret["statut_z3950"] > "1") {
        $this->analyseur->ouvrirNotice($ret["unimarc"],1);
        $this->notice=$this->analyseur->getNoticeIntegration();
        $this->notice["id_origine"]=$notice["id_origine"];
        $this->notice["statut_exemplaires"] = $notice["statut_exemplaires"];
        $this->notice["exemplaires"] = $notice["exemplaires"];
        $this->notice["qualite"]=getVariable("homogene_code_qualite");
        if ($this->notice["titre_princ"]
            && $id_notice = $this->insertNotice()) {
          $this->ecrireExemplaires($id_notice);
          $ret["statut"] = 1;
        }
        $ret["statut"] = 0;
      } else {
        $ret["statut"] = 0;
      }
    }

    // Mise a jour
    $ret["id_notice"]=$id_notice;
    $ret["id_bib"]=$id_bib;
    $ret["isbn"]=$notice["isbn"];
    $ret["ean"]= $notice["ean"];
    $ret["id_commerciale"] = $notice["id_commerciale"];

    $query = ($id_notice)
      ? "delete from notices_succintes where id=$id"
      : "Update notices_succintes set z3950_retry = z3950_retry +1 Where id=$id";
    $sql->execute($query);
    return $ret;
  }


  protected function handleSuccinteLocaly($succinte, $id_notice, $sql) {
    $unimarc=$sql->fetchOne("select unimarc from notices where id_notice=$id_notice");
    $this->notice_sgbd->ouvrirNotice($unimarc,0);
    $this->notice=$this->notice_sgbd->getNoticeIntegration();
    $this->notice["id_origine"]=$succinte["id_origine"];
    $this->notice["statut_exemplaires"] = $succinte["statut_exemplaires"];
    $this->notice["exemplaires"] = $succinte["exemplaires"];
    $this->updateNotice($id_notice, $this->notice["qualite"]);
    $this->ecrireExemplaires($id_notice);
  }


  /**
   * 1 item by number
   */
  private function traitePeriodiquesKoha() {
    if (0 == count($this->notice['exemplaires']))
      return [];

    $number_map = [3 => 'v', 4 => '6'];
    if (!array_key_exists($this->id_article_periodique, $number_map))
      return [];

    $champ_numero = $number_map[$this->id_article_periodique];
    $unimarc = $this->notice['unimarc'];

    foreach($this->notice['exemplaires'] as $exemplaire) {
      $this->notice_sgbd->ouvrirNotice($unimarc,
                                       $this->id_profil,
                                       $this->sigb,
                                       $this->type_doc_force);

      $champs = unserialize($exemplaire['zone995']);
      $table_champs = [];
      $numero = '';
      foreach($champs as $champ) {
        $table_champs[] = [$champ['code'], $champ['valeur']];

        if ($champ['code'] != $champ_numero)
          continue;


        if (preg_match('/(\d+)/', $champ['valeur'], $numbers)) {
          $numero = $numbers[0];
          $complement_titre = trim(substr($champ['valeur'], strpos($champ['valeur'], $numero) + strlen($numero)));
        } else {
          $numero = $champ['valeur'];
        }

      }

      $this->notice_sgbd->add_field('461', '11', 't' . $this->notice['titre_princ']);
      $this->notice_sgbd->add_field('461', '11', 'v' . $numero);
      if ($complement_titre)
        $this->notice_sgbd->add_field('461', '11', 'o' . $complement_titre);
      $this->notice_sgbd->add_field('995', '  ', $table_champs);
      $this->notice_sgbd->update();
      $data = $this->notice_sgbd->getFullRecord();
      $this->traiteNotice($data);
    }
  }

// ----------------------------------------------------------------
// Retourne l'url d'origine du site (ex: http://opac3.pergame.net/)
// ----------------------------------------------------------------
  private function getUrlSite() {
    if (isset($this->url_site))
      return $this->url_site;

    $adresse=getVariable("url_site");
    if(strtolower(substr($adresse,0,7)) !="http://") $adresse="http://".$adresse;
    if(substr($adresse,-1,1)!="/") $adresse.="/";
    return $this->url_site = $adresse;
  }


  private function chercheNotice() {
    $this->identification = ['statut' => static::STATUS_NOTFOUND];

    $double = new Class_Notice_DoubleFinder($this->notice,
                                            $this->id_int_bib,
                                            $this->id_profil);
    if (!$double->find())
      return '';

    $matched_on = $double->getMatchedCriteria();
    $this->identification['statut'] = $matched_on;
    $this->identification[$matched_on] = $double->getId();

    return $double->getId();
  }


  private function insertNotice() {
    if(!$this->notice["statut_exemplaires"]["nb_ex"]) {
      $this->erreur = "notice sans exemplaire";
      $this->statut = static::RECORD_REJECT;
      return false;
    }

    if(!$this->notice["titre_princ"]) {
      if($this->notice["isbn"] or $this->notice["ean"] or $this->notice["id_commerciale"]) {
        $id_bib=$this->id_bib;
        $data=serialize($this->notice);
        Class_NoticeSuccincte::newInstance(['id_bib' => $this->id_bib,
                                            'data' => serialize($this->notice)])
          ->save();

        $this->statut = static::RECORD_SUCCINCT;
        return false;
      }

      if($this->format == Class_IntProfilDonnees::FORMAT_UNIMARC or $this->format == Class_IntProfilDonnees::FORMAT_CG68_ARCHIVES)
        $this->erreur = "pas de titre principal";
      else
        $this->erreur = "Aucun identifiant valide";

      $this->statut = static::RECORD_REJECT;
      return false;
    }

    $this->traiteFacettes();

    $notice = Class_Notice::newInstance($this->noticeToDBEnreg());
    $notice->save();
    $this->statut = static::RECORD_INSERT;
    return $notice->getId();
  }


  public function noticeToDBEnreg() {
    return ["type_doc" => $this->notice["type_doc"],
            "alpha_titre" => $this->notice["alpha_titre"],
            "alpha_auteur" => $this->notice["alpha_auteur"],

            "titres" => $this->indexation->getfullText(array_merge($this->notice["titres"],
                                                                   [$this->notice["clef_chapeau"],
                                                                    $this->notice["tome_alpha"]])),

            "auteurs" => $this->indexation->getfullText($this->_getFulltextAuthors()),

            "editeur" => $this->indexation->getfullText($this->notice["editeur"]),
            "collection" => $this->indexation->getfullText($this->notice["collection"]),
            "matieres" => $this->indexation->getfullText($this->_getFulltextTopics()),
            "dewey" => $this->indexation->getfullText($this->notice["full_dewey"]),
            "facettes" => $this->notice["facettes"],
            "isbn" => $this->notice["isbn"],
            "ean" => $this->notice["ean"],
            "id_commerciale" => $this->notice["id_commerciale"],
            "clef_alpha" => $this->notice["clef_alpha"],
            "clef_chapeau" => $this->notice["clef_chapeau"],
            "clef_oeuvre" => $this->notice["clef_oeuvre"],
            "tome_alpha" => $this->notice["tome_alpha"],
            "annee" => $this->notice["annee"],
            "qualite" => $this->notice["qualite"],
            "exportable" => $this->notice["exportable"],
            "cote" => $this->notice["cote"],
            "unimarc" => $this->notice['unimarc'],
            "date_maj" => dateDuJour(2)];
  }


  protected function _getFulltextAuthors() {
    if (!$this->notice['auteurs'])
      return $this->notice["200_f"];

    return array_merge($this->notice["auteurs"],
                       $this->notice["auteurs_renvois"],
                       $this->_getCodifAuteurRenvois());
  }


  protected function _getCodifAuteurRenvois() {
    $see_also = [];
    foreach($this->notice['auteurs'] as $author) {
      if (($codif = $this->_getCodifAuteur($author))
          && $codif->hasMotsRenvois())
        $see_also[] = $codif->getMotsRenvois();
    }

    return $see_also;
  }


  protected function _getFulltextTopics() {
    if (!$this->notice['matieres'])
      return ;

    return array_merge($this->notice["matieres"],
                       $this->notice["matieres_renvois"],
                       $this->_getCodifTopicsRenvois());
  }


  protected function _getCodifTopicsRenvois() {
    $see_also = [];

    foreach($this->notice['matieres'] as $topic) {
      if (($codif = $this->_getCodifTopic($topic))
          && $codif->hasMotsRenvois())
        $see_also[] = $codif->getMotsRenvois();
    }
    return $see_also;
  }


  public function updateNotice($id_notice, $qualite) {
    if (!$existing_notice = Class_Notice::find($id_notice))
      return $id_notice;

    $this->notice["qualite"] = $existing_notice->getQualite();
    $this->notice["facette"] = $existing_notice->getFacettes();

    // Test qualite
    if ($qualite >= $this->notice["qualite"]) {
      $this->notice["qualite"] = $qualite;
      $this->statut = static::RECORD_RENEW;
    } else {
      $this->statut = static::RECORD_UPDATE;
    }

    // Si la notice n'a pas de titre on substitue par celle de la base en forcant a une qualite inferieure
    if (!$this->notice['titre_princ'])
      $this->statut = static::RECORD_UPDATE;

    if (in_array($this->format,
                 [ Class_IntProfilDonnees::FORMAT_DUBLIN_CORE,
                   Class_IntProfilDonnees::FORMAT_BIBLIONDEMAND]))
      return $this->_realUpdate($existing_notice);

    // Zones forcees
    $this->notice_sgbd
      ->ouvrirNotice($existing_notice->getUnimarc(),
                     $this->id_profil,
                     0,
                     '',
                     false);

    $champs_forces = $this->notice_sgbd->getChampsForces();

    $id_origine = $this->notice["id_origine"];
    $qualite = $this->notice["qualite"];
    $statut_exemplaires = $this->notice["statut_exemplaires"];
    $exemplaires = $this->notice["exemplaires"];
    $warnings = $this->notice["warnings"];

    // Si la notice de la base est de meilleure qualite on la prend
    if ($this->statut == static::RECORD_RENEW) {
      $full_unimarc = array_key_exists('unimarc_with_items', $this->notice)
        ? $this->notice['unimarc_with_items']
        : $this->notice['unimarc'];

      $this->notice_sgbd
        ->ouvrirNotice($full_unimarc, $this->id_profil, 0, '', false);
    }

    $this->updateForcedZones($champs_forces, $this->notice["champs_forces"]);

    $this->notice = $this->notice_sgbd->getNoticeIntegration();
    $this->notice["statut_exemplaires"] = $statut_exemplaires;
    $this->notice["exemplaires"] = $exemplaires;
    $this->notice["warnings"] = $warnings;
    $this->notice["qualite"] = $qualite;
    $this->notice["id_origine"] = $id_origine;

    if ($this->statut == static::RECORD_UPDATE)
      return $id_notice;
    return $this->_realUpdate($existing_notice);
  }


  protected function _realUpdate($record) {
    $this->traiteFacettes($record->getId());

    $record
      ->updateAttributes($this->noticeToDBEnreg())
      ->save();

    return $record->getId();
  }



  protected function updateForcedZones($existing, $current) {
    if (!$existing || $existing == $current)
      return;

    $new = ($current) ?
      array_merge_recursive($existing, $current) :
      $existing;

    // Fusion des champs forces
    $champs_forces = [];
    foreach ($new as $zone => $valeurs) {
      $zone = substr($zone, 1); // on retire le Z qui sert au array_merge_recursive
      $this->notice_sgbd->delete_field($zone); // On supprime l'ancienne zone
      // si champ matiere on dedoublonne directement
      if ('6' == substr($zone, 0, 1)) {
        $champs = array_unique($valeurs);
        $champs_forces[$zone] = $champs;
        continue;
      }
      // sinon on decoupe les elements on les dedoublonne et on les remet dans 1 seule zone
      $champs=array();
      foreach($valeurs as $valeur)
        $champs = array_merge($champs, $this->notice_sgbd->getValeursBloc($valeur));
      $champs = array_unique($champs);
      $champs_forces[$zone][] = $this->notice_sgbd->makeZoneByValeurs(substr($valeurs[0],0,2),"a",$champs);
    }

    // On remet les nouvelles zones
    foreach($champs_forces as $zone => $valeurs)
      foreach($valeurs as $valeur)
      $this->notice_sgbd->add_zone($zone,$valeur);

    $this->statut = static::RECORD_FULLUPDATE;
  }


// --------------------------------------------------------------------------------
// Suppression de notice (ne supprime pas l'enreg notice)
// --------------------------------------------------------------------------------
  private function supprimerNotice($id_notice,$id_origine) {
    foreach (['id_notice' => $id_notice,
              'id_origine' => $id_origine] as $key => $value) {

      if ($exemplaires = Class_Exemplaire::findAllBy([$key => $value,
                                                     'id_bib' => $this->id_bib])) {
        foreach ($exemplaires as $exemplaire)
          $exemplaire->delete();

        return;
      }
    }

    $this->statut = static::RECORD_REJECT;
    $this->erreur="notice à supprimer non reconnue";
    return false;
  }


  public function ecrireExemplaires($id_notice) {
    $code_barres = [];
    $exemplaires = [];
    foreach ($this->notice['exemplaires'] as $ex) {
      $code_barres []= $ex['code_barres'];
      if ($ex["activite"]=="d")
        continue;

      $exemplaire = Class_Exemplaire::newInstance($ex)
        ->setIdNotice($id_notice)
        ->setIdOrigine($this->notice['id_origine'])
        ->setIdBib($this->id_bib)
        ->setIdIntBib($this->id_int_bib);

      /**
       * @see http://forge.afi-sa.fr/issues/14279
       * we can have one file with items for different libraries. We use "annexe" codification to find
       * to put each item in the library it belongs to.
       **/
      if ($exemplaire->hasAnnexe() && ($annexe =  CodifAnnexeCache::getInstance()->find($ex['annexe']))) {
        $exemplaire->setIdBib($annexe->getIdBib());
      }

      $exemplaires []= $exemplaire;
    }

    if (!empty($code_barres))  {
      $unicite_codes_barres = getVariable('unicite_code_barres');
      $delete_duplicates_args = ($unicite_codes_barres == '1') ? [] : ['id_int_bib' => $this->id_int_bib];
      $delete_duplicates_args['id_notice'] = $id_notice;
      $delete_duplicates_args['code_barres'] = $code_barres;
      Class_Exemplaire::deleteBy($delete_duplicates_args);
    }

    foreach($exemplaires as $exemplaire)
      $exemplaire->save();

    if($record = Class_Notice::find($id_notice))
      $record->setDateMaj(dateDuJour(2))->save();

    Class_Exemplaire::clearCache();
    Class_Notice::clearCache();
  }


  public function supprimerExemplaire($id_notice,$ex) {
    if(!$id_notice)  {
      $this->statut = static::RECORD_REJECT;
      $this->erreur="notice de l'exemplaire à supprimer non trouvée";
      return false;
    }

    $exemplaires = Class_Exemplaire::findAllBy(['id_notice' => $id_notice,
                                                'id_int_bib' => $this->id_int_bib,
                                                'code_barres' => $ex['code_barres']]);
    if(!$exemplaires)  {
      $this->statut = static::RECORD_REJECT;
      $this->erreur="code-barres à supprimer non trouvé";
      return false;
    }

    $notice = Class_Notice::find($id_notice);
    foreach($exemplaires as $exemplaire) {
      $notice->removeExemplaire($exemplaire);
    }
    $notice->setDateMaj(dateDuJour(2))
           ->save();
  }


  public function traiteFacettes($id_notice=null) {
    $facettes = [];

    // Tags
    if ($id_notice)
      foreach (Class_CodifTags::findForRecord($id_notice) as $tag)
        $facettes[] = $tag->asFacet();

    // Doc type
    if($type_doc = $this->notice['infos_type_doc']) {
      if($code_type_doc = $type_doc['code']){
        $facettes[] = 'T'.$code_type_doc;
      }
    }

    // Dewey
    $this->notice["full_dewey"] = '';
    if($this->notice["dewey"])
    {
      foreach($this->notice["dewey"] as $indice)
      {
        if (!$dewey = Class_CodifDewey::find($indice)) {
          $dewey = Class_CodifDewey::newInstance(['id_dewey' => $indice,
                                                  'libelle' => '']);
          $dewey->save();
        }


        $this->notice["full_dewey"] .= $dewey->getLibelle()." ";
        $facettes[]="D".$indice;
      }
    }

    // Pcdm4
    if($this->notice["pcdm4"]) {
      $indice = $this->notice["pcdm4"];
      if (!$pcdm4 = Class_CodifPcdm4::find($indice)) {
          $pcdm4 = Class_CodifPcdm4::newInstance(['id_pcdm4' => $indice,
                                                  'libelle' => '']);
          $pcdm4->save();
      }

      $this->notice["full_dewey"] .= $pcdm4->getLibelle()." ";
      $facettes[]="P".$indice;
    }

    // Thesaurus
    if($thesauri=$this->notice["thesauri"])
      {

        foreach ($thesauri as $thesaurus) {
          if (empty($thesaurus))
            continue;
          if ($thesaurus->getLibelle())
            $this->notice["full_dewey"] .= $thesaurus->getLibelle().' ';
          $facettes[]="H".$thesaurus->getIdThesaurus();
        }
      }

    // Auteurs
    if($this->notice["auteurs"])
    {
      foreach($this->notice["auteurs"] as $auteur) {
        if ($facette_auteur= $this->getFacetteAuteur($auteur))
          $facettes []= $facette_auteur;
      }
    }

    // Matieres
    if($this->notice["matieres"])  {
      foreach($this->notice["matieres"] as $matiere)  {
        if (!$codif_matiere = $this->_getCodifTopic($matiere)) continue;
        $facettes[]="M".$codif_matiere->getId();
      }
    }

    // Centres d'interet
    if ($this->notice["interet"]) {
      foreach($this->notice["interet"] as $interet) {
        if (!$code_alpha = $this->indexation->alphaMaj($interet))
          continue;

        if (!$centre_interet = Class_CodifCentreInteret::findFirstBy(['code_alpha' => $code_alpha])) {
          $centre_interet = Class_CodifCentreInteret::newInstance(['libelle' => $interet,
                                                                   'code_alpha' => $code_alpha]);
          $centre_interet->save();
        }

        $facettes[] = "F" . $centre_interet->getId();
        $this->notice["full_dewey"] .= $interet." ";
      }
    }

    // Langues
    if($this->notice["langues"])
    {
      foreach($this->notice["langues"] as $langue)
      {
        if (Class_CodifLangue::find($langue))
          $facettes[]="L".$langue;
        else
          $this->notice["warnings"][]=array("Code langue non reconnu",$langue);
      }
    }

    // genres
    if(isset($this->notice["genres"]) && count($this->notice["genres"]))
    {
      foreach(array_filter($this->notice["genres"]) as $genre)
      {
        $facettes[]="G".$genre." ";
        if ($codif_genre = Class_CodifGenre::find($genre))
          $this->notice["full_dewey"].=$codif_genre->getLibelle();
      }
    }

    // emplacements
    if (isset($this->notice["emplacements"]) && count($this->notice['emplacements']))
        $facettes = array_merge($facettes, array_map(function($e) { return "E$e";}, $this->notice['emplacements']));

    // Maj enreg facette
    $this->notice["facettes"] = trim(implode(' ', array_unique($facettes)));
  }


  public function getFacetteAuteur($auteur) {
    return ($codif = $this->_getCodifAuteur($auteur))
      ? 'A' . $codif->getId() : '';
  }


  protected function _getCodifAuteur($auteur) {
    return $this->getCodifProvider()->getAuthor($auteur);
  }

  protected function _getCodifTopic($topic) {
    return $this->getCodifProvider()->getTopic($topic);
  }


// --------------------------------------------------------------------------------
// Ecrit une notice : article de périodique
// --------------------------------------------------------------------------------
  private function ecrireArticlePeriodique() {
    if( 1 == $this->notice["statut"])
      return $this->deleteRecordArticles();

    $title_key = hash('crc32b', $this->notice['titre_princ']);

    $unimarc_key = $this->notice["clef_unimarc"]
      ? $this->notice["clef_unimarc"]
      : $title_key;

    $article_from_record = Class_Notice_SerialArticles::newInstance(['clef_chapeau' => $this->notice["clef_chapeau"],
                                                                     'clef_numero' => $this->notice["clef_numero"],
                                                                     'clef_unimarc' => $unimarc_key,
                                                                     'unimarc' => $this->notice['unimarc'],
                                                                     'date_maj' => dateDuJour(0),
                                                                     'qualite' => $this->qualite_bib,
                                                                     'clef_article' => $this->notice['clef_article']]);

    if(!$this->notice['clef_unimarc']) {
      $common_attribs = ['clef_chapeau' => $this->notice['clef_chapeau'],
                         'clef_numero' => $this->notice['clef_numero']];

      if(!$article = Class_Notice_SerialArticles::findFirstBy(array_merge($common_attribs, ['clef_unimarc' => $title_key])))
        $article = Class_Notice_SerialArticles::findFirstBy(array_merge($common_attribs, ['clef_article' => $this->notice['clef_article'],
                                                                                          'clef_unimarc' => '']));
      return $this->updateOrInsertArticle($article, $article_from_record);
    }

    if ($article = Class_Notice_SerialArticles::findFirstBy(['clef_unimarc' => $this->notice['clef_unimarc']])) {
        $article_from_record->setClefChapeau($article->getClefChapeau());
        $article_from_record->setClefNumero($article->getClefNumero());
        return $this->updateOrInsertArticle($article, $article_from_record);
    }
  }


  protected function updateOrInsertArticle($article, $article_from_record) {
    return $article
      ? $this->updateArticleWith($article, $article_from_record)
      : $this->insertArticleWith($article_from_record);
  }


  protected function insertArticleWith($article_from_record) {
    Class_Notice_SerialArticles::newInstance($article_from_record->toArray())->save();
    Class_Notice_SerialArticles::clearCache();
    $this->statut = self::RECORD_INSERT;
    return;
  }


  protected function updateArticleWith($article, $article_from_record) {
    if($enreg["qualite"] >= $article->getQualite())  {
      $article->updateAttributes($article_from_record->toArray())->save();
      $this->statut = self::RECORD_RENEW;
    }
  }


  protected function deleteRecordArticles() {
    global $sql;

    $this->statut = self::RECORD_DELETE;

    if($this->notice["clef_unimarc"])
      $controle = $sql->execute("delete from notices_articles where clef_unimarc='$clef_unimarc'");
    else
      $controle = $sql->execute("delete from notices_articles where clef_chapeau='$clef_chapeau' and clef_numero='$clef_numero' and clef_article='$clef_article'" );

    if(!$controle) {
      $this->statut = static::RECORD_REJECT;
      $this->erreur = "notice à supprimer non reconnue";
    }
  }


// --------------------------------------------------------------------------------
// Recupere titre, auteurs, matieres pour 1 article de périodique
// --------------------------------------------------------------------------------
  public function getDataArticlePeriodique($unimarc)
  {
    if(!$unimarc) { $this->erreur="notice sans unimarc"; return false; }
    if(!$this->analyseur->ouvrirNotice($unimarc,$this->id_profil,0,"")) { $this->erreur=$this->analyseur->getLastError(); return false; }

    $ret["titres"]=$this->analyseur->getTitres();
    $ret["auteurs"]=$this->analyseur->getAuteurs();
    $ret["matieres"]=$this->analyseur->getMatieres();
    return $ret;
  }

// --------------------------------------------------------------------------------
// Rend la derniere erreur et les derniers warnings
// --------------------------------------------------------------------------------
  public function getLastStatut()
  {
    $ret["statut"]=$this->statut;
    $ret["erreur"]=$this->erreur;
    $ret["warnings"]=$this->notice["warnings"];
    $ret["identification"]=$this->identification["statut"];
    return $ret;
  }

// --------------------------------------------------------------------------------
// Rend tout le contenu de la notice
// --------------------------------------------------------------------------------
  public function getNotice()
  {
    if(!$this->notice) $this->notice=array();
    return $this->notice;
  }

// ----------------------------------------------------------------
// Test d'une notice unimarc
// ----------------------------------------------------------------
  public function testNotice($data,$piege_numero="",$piege_titre="",$piege_code_barres="",$piege_isbn="",$piege_type_doc="")
  {
    global $sql;

    // lire la notice
    $this->analyseur->ouvrirNotice($data,$this->id_profil,$this->sigb);
    $notice=$this->analyseur->getNoticeIntegration();

  //  tracedebug($notice,true); //++++++++++++++++++++++++++++++++++++++++++++++++++++

    // Titre principal
    $ret["titre"]=$notice["titre_princ"];

    // Gestion des pieges
    if($piege_numero){$ret["statut"]=0; return $ret;}
    if($piege_titre >"" and $this->indexation->codeAlphaTitre($ret["titre"]) == $this->indexation->codeAlphaTitre($piege_titre)) {$ret["statut"]=1; return $ret;}
    if($piege_code_barres > "" and $notice["statut_exemplaires"]["nb_ex"]>0)
    {
      foreach($notice["exemplaires"] as $ex)
      {
        if($ex["code_barres"]==$piege_code_barres) {$ret["statut"]=1; return $ret;}
      }
    }
    if($piege_isbn > "" and ($notice["isbn10"] == $piege_isbn or $notice["isbn13"] == $piege_isbn)) {$ret["statut"]=1; return $ret;}
    if($piege_type_doc >"" and $notice["infos_type_doc"]["code"]==$piege_type_doc) {$ret["statut"]=1; return $ret; }
    if($piege_titre > "" or $piege_code_barres > "" or $piege_isbn > "" or $piege_type_doc >""){$ret["statut"]=0; return $ret;}

    // Type de document
    $td=$notice["infos_type_doc"];
    if(!$td["code"]){  $statut=1; $libelle=" code non reconnu"; $ret["type_doc"] = Class_TypeDoc::UNKNOWN; }
    else {$statut=0; $libelle=$td["code"]." - ".$td["libelle"]; $ret["type_doc"]=$td["code"]; }
    $ret["lig"][]=array($statut,"Type&nbsp;de&nbsp;document", $libelle,$td["infos"]);

    // Statut : 0=notice vivante 1=à détruire
    $lig[0]=0;
    $lig[1]="Statut";
    $lig[2]="ok";
    if($notice["statut"]==1)
    {
      $lig[0]=3;
      $lig[2]='<font color="purple">Notice à supprimer</font>';
    }
    else if($td["code"]!="100")
    {
      if($ret["titre"] == "") {$lig[0]=2; $lig[2]="pas de titre principal";$ret["lig"][]=$lig;}
      if($notice["statut_exemplaires"]["nb_ex"]==0)
      {
        $lig[0]=2;
        $lig[2]="pas de zone 995";
      }
      else
      {
        if($notice["statut_exemplaires"]["codes_barres"]==0) {$lig[0]=1; $lig[2]="code-barres non trouvé";}
        if($notice["statut_exemplaires"]["cotes"]==0) {$lig[0]=1; $lig[2] ="cote non trouvée";}
        if($notice["exportable"]==false)$lig[3]='<font color="purple">notice non libre de droits</font>'; else $lig[3]="notice libre de droits";
      }
    }
    $ret["lig"][]=$lig;

    // articles de periodiques
    if($td["code"]== "100")
    {
      $lig[0]=0;
      $lig[1]="titre du numéro";
      $lig[2]=$notice["titre_numero"];
      $ret["lig"][]=$lig;

      $lig[0]=0;
      $lig[1]="identifiant 001";
      $lig[2]=$notice["info_id"];
      $ret["lig"][]=$lig;
    }

    // tout sauf articles de periodiques
    else
    {
      // exemplaires
      $ret["nb_ex"]=$notice["statut_exemplaires"]["nb_ex"];
      $lig[0]=0;
      $lig[1]="Exemplaires";
      $lig[2]=$notice["statut_exemplaires"]["nb_ex"]." ex. actif(s)";
      $lig[3]="";
      if($notice["statut_exemplaires"]["nb_ex_detruits"] > 0 ) $lig[3]=$notice["statut_exemplaires"]["nb_ex_detruits"]." ex. à supprimer";
      $ret["lig"][]=$lig;

      // Identifiants (isbn / ean)
      $isbn=$this->analyseur->getIsbn();
      if(!$isbn) $isbn=$this->analyseur->getEan();

      $lig[0]=0;
      $lig[1]="Identifiant";
      $lig[2]="";
      $lig[3]="";
      if($isbn["multiple"]==true)
      {
        $lig[0]=1;
        $lig[2]="Isbn multiple";
        $lig[3]=$isbn["isbn"];
      }
      if($isbn["statut"] == 1)
      {
        $lig[0]=1;
        $lig[2]=$isbn["erreur"];
        $lig[3]=$isbn["code_brut"];
      }
      else
      {
        if($isbn["isbn"]) {$lig[2]="isbn"; $lig[3]=$isbn["isbn"];}
        if($isbn["ean"]) {$lig[2]="ean"; $lig[3]=$isbn["ean"];}
      }
      $ret["lig"][]=$lig;

      // Identifiants ID_COMMERCIALE
      $lig[0]=0;
      $lig[1]="No commercial";
      $lig[2]=$notice["id_commerciale"];
      $lig[3]="";
      $ret["lig"][]=$lig;
    }

    // Donnes principales
    $titre=$this->analyseur->getTitres();
    $auteur=$this->analyseur->getAuteurs();
    $lig[0]=0;
    $lig[1]="Données&nbsp;principales";
    $lig[2]="titre(s) : ".count($titre);
    $lig[3]="auteur(s) : ".count($auteur);
    $ret["lig"][]=$lig;

    if($td["code"]!= "100")  // tout sauf articles de periodiques
    {
      // section
      $lig[1]="Section";
      if(!$notice["sections"]) {$lig[0]=1; $lig[2]="non reconnue";}
      else
      {
        $lig[0]=0;
        $lig[2]="";
        foreach($notice["sections"] as $section) $lig[2].=$sql->fetchOne("select libelle from codif_section where id_section=".$section).BR;
      }
      $lig[3]="";
      $ret["lig"][]=$lig;

      // genre
      $lig[1]="Genre";
      if(!$notice["genres"]) {$lig[0]=1; $lig[2]="non reconnu";}
      else
      {
        $lig[0]=0;
        $lig[2]="";
        foreach($notice["genres"] as $genre) $lig[2].=$sql->fetchOne("select libelle from codif_genre where id_genre=".$genre).BR;
      }
      $lig[3]="";
      $ret["lig"][]=$lig;

      // emplacement
      $lig[1]="Emplacement";
      if(!$notice["emplacements"]) {$lig[0]=1; $lig[2]="non reconnu";}
      else
      {
        $lig[0]=0;
        $lig[2]="";
        foreach($notice["emplacements"] as $emplacement) $lig[2].=$sql->fetchOne("select libelle from codif_emplacement where id_emplacement=".$emplacement).BR;
      }
      $lig[3]="";
      $ret["lig"][]=$lig;

      // Champs forcés
      $lig[0]=0;
      $lig[1]="Champs à conserver";
      $lig[2]="";
      $lig[3]="";
      if(count($notice["champs_forces"]) > 0)
      {
        foreach($notice["champs_forces"] as $zone => $champ) $lig[2].= substr($zone,1)." ";
      }
      else $lig[2]="aucun";
      $ret["lig"][]=$lig;
    }

    // Statut general du retour
    $ret["statut"]=0;
    for($i=0; $i < count($ret["lig"]); $i++)
    {
      if($ret["lig"][$i][0]==2)
      {
        $ret["statut"]=2;
        break;
      }
      if($ret["lig"][$i][0]==1) $ret["statut"]=1;
      if($ret["lig"][$i][0]==3 and $ret["statut"] <2) $ret["statut"]=3;
    }
    return $ret;
  }
// ----------------------------------------------------------------
// Analyse de synthese d'une notice unimarc
// ----------------------------------------------------------------
  public function syntheseNotice($data)
  {
    global $sql;

    $this->analyseur->ouvrirNotice($data,$this->id_profil,$this->sigb);
    $notice=$this->analyseur->getNoticeIntegration();

    // Notice à supprimer
    if($notice["statut"]==1) {$ret["statut"]="suppr"; return $ret;}

    // Notice rejetee
    $ret["statut"]="rejet";
    if(!$notice["titre_princ"]) {$ret["rejet"]="notice sans titre"; return $ret;}
    if($notice["type_doc"] != Class_TypeDoc::LIVRE_NUM and !$notice["statut_exemplaires"]["nb_ex"]) {$ret["rejet"]="notice sans exemplaire"; return $ret;}

    // Warnings
    $ret["statut"]="warning";
    if($notice["type_doc"] != Class_TypeDoc::LIVRE_NUM)
    {
      if(!$notice["statut_exemplaires"]["codes_barres"]) $ret["warnings"][]="code-barres non trouvé";
      if(!$notice["statut_exemplaires"]["cotes"]) $ret["warnings"][]="cote non trouvée";
    }
    if(!$notice["type_doc"])$ret["warnings"][]="Type de document non reconnu";

    // Type de doc
    if($notice["type_doc"]) $ret["type_doc"]=$notice["infos_type_doc"]["libelle"];

    // Identifiants (isbn / ean)
    if($notice["type_doc"] != Class_TypeDoc::LIVRE_NUM)
    {
      $isbn=$this->analyseur->getIsbn();
      if(!$isbn) $isbn=$this->analyseur->getEan();
      if($isbn["multiple"]==true) $ret["warnings"][]="Isbn multiple";
      if($isbn["statut"] == 1) $ret["warnings"][]="ISBN ou EAN incorrect";
      if($isbn["isbn"]) $ret["identifiant"]="ISBN identifié";
      elseif($isbn["ean"])  $ret["identifiant"]="EAN identifié";
      else $ret["identifiant"]="Aucun identifiant";

      // Genres
      if($notice["genres"])
      {
        foreach($notice["genres"] as $genre) $ret["genres"][]=$sql->fetchOne("Select libelle from codif_genre where id_genre='$genre'");
      }
      else $ret["warnings"][]="Code genre non reconnu";

      // Sections
      if($notice["sections"])
      {
        foreach($notice["sections"] as $section) $ret["sections"][]=$sql->fetchOne("Select libelle from codif_section where id_section='$section'");
      }
      else $ret["warnings"][]="Code section non reconnu";

      // Emplacements
      if($notice["emplacements"])
      {
        foreach($notice["emplacements"] as $emplacement) $ret["emplacements"][]=$sql->fetchOne("Select libelle from codif_emplacement where id_emplacement='$emplacement'");
      }
      else $ret["warnings"][]="Code emplacement non reconnu";

      // Langues
      if($notice["langues"])
      {
        foreach($notice["langues"] as $langue)
        {
          $controle=$sql->fetchOne("Select libelle from codif_langue where id_langue='$langue'");
          if($controle) $ret["langues"][]=$controle;
          else $ret["warnings"][]="Code langue non reconnu";
        }
      }

      // Champs forcés
      if(count($notice["champs_forces"]) > 0)
      {
        foreach($notice["champs_forces"] as $zone => $champ) $ret["zones_forcees"][]= substr($zone,1);
      }
    }

    // Notice sans anomalie
    if(!$ret["warnings"]) $ret["statut"]="ok";

    // Nombre d'exemplaires
    $ret["nb_ex"]=$notice["statut_exemplaires"]["nb_ex"];
    if($ret["nb_ex"] > 100) $ret["warnings"][]="trop d'exemplaires";

    return $ret;
  }

// ----------------------------------------------------------------
// Analyse des valeurs distinctes
// ----------------------------------------------------------------
  public function valeursDistinctes($champs,$data)
  {
    $this->analyseur->ouvrirNotice($data,$this->id_profil,$this->sigb);
    foreach($champs as $champ)
    {
      $zone=substr($champ,0,3);
      $sous_champ=substr($champ,-1);
      $valeur=$this->analyseur->get_subfield($zone,$sous_champ);
      if(!$valeur) $valeur[0] = "non renseigné";
      foreach($valeur as $code)
      {
        $ret[$champ][$code]++;
      }
    }
    return $ret;
  }


  public function analyseurUpdate() {
    $this->analyseur->update();
  }


  public function get_subfield() {
    return call_user_func_array([$this->analyseur, 'get_subfield'], func_get_args());
  }


  protected function getServiceRunner() {
    return null != $this->_service_runner
      ? $this->_service_runner : new Service_Runner();
  }


  /** @category testing */
  public function setServiceRunner($runner) {
    $this->_service_runner = $runner;
    return $this;
  }



  public function getStatut() {
    return $this->statut;
  }


  protected function getCodifProvider() {
    if (null == $this->_codif_provider)
      $this->_codif_provider = (new Class_Cosmogramme_Integration_CodifProvider)
        ->setIndexation($this->indexation);

    return $this->_codif_provider;
  }


  protected function _isIncremental() {
    return Class_Cosmogramme_Integration::TYPE_OPERATION_INCREMENT == $this->type_operation;
  }


  /** @category testing */
  public function setCodifProvider($provider) {
    $this->_codif_provider = $provider;
    return $this;
  }
}


class Service_Runner {
  public function run($type, $args) {
    return communication::runService($type, $args);
  }
}