<?php
// +-------------------------------------------------+
// © 2002-2004 PMB Services / www.sigb.net pmb@sigb.net et contributeurs (voir www.sigb.net)
// +-------------------------------------------------+
// $Id: export.php,v 1.34.4.1.4.2 2026/08/20 08:32:56 rtigero Exp $

use Pmb\DSI\Models\Channel\Export\ExportChannel;
use Pmb\DSI\Models\Diffusion;
use Pmb\DSI\Orm\DiffusionOrm;
use Pmb\ImportExport\Models\ExportScenarioRegistry;

$base_path = ".";
require_once($base_path."/includes/init.inc.php");

global $include_path, $action, $msg, $popup_header, $popup_footer;
global $action, $typeexport, $notice_id, $select_item, $opac_export_allow, $opac_export_allow_expl;
// E12-O3 : export d'un resultat de recherche (contexte re-poste par l'ecran de resultats)
global $export_scope, $user_query, $mode, $opac_max_export_items;

//fichiers nécessaires au bon fonctionnement de l'environnement
require_once($base_path."/includes/common_includes.inc.php");

require_once('./includes/templates/common.tpl.php');
require_once('./includes/notice_categories.inc.php');

// classe de gestion des catégories
require_once($base_path.'/classes/categorie.class.php');
require_once($base_path.'/classes/notice.class.php');
require_once($base_path.'/classes/notice_display.class.php');

// classe indexation interne
require_once($base_path.'/classes/indexint.class.php');

// classe d'affichage des tags
require_once($base_path.'/classes/tags.class.php');

// pour l'affichage correct des notices
require_once($base_path."/includes/templates/notice.tpl.php");
require_once($base_path."/includes/navbar.inc.php");
require_once($base_path."/includes/explnum.inc.php");

require_once('./classes/notice_affichage.class.php');
require_once('./classes/notice_affichage.ext.class.php');

require_once($include_path."/mail.inc.php") ;

// pour export panier
require_once("$base_path/admin/convert/start_export.class.php");

// si paramétrage authentification particulière et pour la re-authentification ntlm
if (file_exists($base_path.'/includes/ext_auth.inc.php')) {
    require_once($base_path.'/includes/ext_auth.inc.php');
}

/**
 * Script de recopie du contexte de recherche dans le formulaire de la popup d'export (E12-O3).
 *
 * L'ecran de resultats de l'OPAC porte un formulaire cache 'form_values' qui
 * contient exactement les valeurs necessaires pour rejouer la recherche : c'est
 * le meme jeu de champs que 'cart_values' utilise par "Ajouter le resultat au
 * panier", donc celui qu'attend caddie_controller. La popup le LIT (meme
 * origine) et recopie ses champs dans son propre formulaire ; elle ne modifie
 * jamais la fenetre appelante, dont le formulaire de repagination reste intact.
 *
 * @param string $no_opener_msg Message affiche si la fenetre appelante est inaccessible
 * @return string Bloc <script> a inserer dans la popup
 */
function export_get_search_context_script($no_opener_msg) {
    return "
<script type='text/javascript'>
function export_search_fill_form(form) {
	var opener_form = null;
	try {
		if (window.opener && !window.opener.closed) {
			opener_form = window.opener.document.forms['form_values'];
		}
	} catch (error) {
		opener_form = null;
	}
	if (!opener_form) {
		alert(\"".addslashes($no_opener_msg)."\");
		return false;
	}
	// Purge d'une recopie precedente : la popup reste ouverte apres le
	// telechargement, l'usager peut demander un second format.
	var previous = form.querySelectorAll(\"input[data-search-context='1']\");
	for (var p = 0; p < previous.length; p++) {
		previous[p].parentNode.removeChild(previous[p]);
	}
	// Champs pilotes par la popup : ils ne doivent pas etre ecrases par le
	// contexte recopie, qui porte son propre 'mode' en recherche simple.
	var reserved = ',action,export_scope,typeexport,mode,';
	var elements = opener_form.elements;
	for (var i = 0; i < elements.length; i++) {
		var element = elements[i];
		if (!element.name || reserved.indexOf(',' + element.name + ',') != -1) {
			continue;
		}
		if ((element.type == 'checkbox' || element.type == 'radio') && !element.checked) {
			continue;
		}
		var hidden = document.createElement('input');
		hidden.setAttribute('type', 'hidden');
		hidden.setAttribute('name', element.name);
		hidden.setAttribute('data-search-context', '1');
		hidden.value = element.value;
		form.appendChild(hidden);
	}
	return true;
}
</script>";
}

global $lvl, $id;
if ($lvl == "dsi") {
    $id = intval($id);
    if (DiffusionOrm::exist($id)) {
        $diffusion = new Diffusion($id);
        $history = $diffusion->getLastHistorySent(ExportChannel::class);
        if (!empty($history)) {
            $history->send();
        }
    }
    die();
}

switch(true) {
    case isset($notice_id):
        $items = [$notice_id];
        break;
    case (isset($select_item) && $select_item):
        $items = explode(",", $select_item);
        break;
    case (isset($_SESSION["cart"])):
        $items = $_SESSION["cart"];
        break;
    default:
        $items = [];
        break;
}

if (($opac_export_allow == '1') || (($opac_export_allow == '2') && ($_SESSION["user_code"]))) {
    switch ($opac_export_allow_expl) {
        case '1':
            $keep_expl = 1 ;
            $keep_explnum = 0 ;
            break;
        case '2':
            $keep_expl = 0 ;
            $keep_explnum = 1 ;
            break;
        case '3':
            $keep_expl = 1 ;
            $keep_explnum = 1 ;
            break;
        case '0':
        default:
            $keep_expl = 0 ;
            $keep_explnum = 0 ;
            break;
    }
    // E12-O3 : contexte "resultat de recherche", commun au selecteur (action=select)
    // et a l'export (action=export). $lvl et $mode pilotent la resolution de la liste
    // par caddie_controller : ils sont contraints a une LISTE BLANCHE des deux cotes.
    // Un appelant ne doit pas pouvoir detourner la resolution vers un contexte non
    // expose (bannettes, prets, pages d'autorite...) en jouant sur l'URL.
    $export_scope_search = (isset($export_scope) && $export_scope == "search");
    $export_search_lvl = "more_results";
    $export_search_id = 0;
    $export_search_mode = "tous";
    if ($export_scope_search && isset($lvl) && $lvl == "search_segment") {
        // Segment d'univers de recherche. Seuls les segments de NOTICES sont
        // exposes : export.php ne sait exporter que des notices, un segment
        // d'autorites n'a rien d'exportable. Le mode est donc impose, il n'est
        // pas lu dans la requete.
        $export_search_lvl = "search_segment";
        $export_search_id = intval($id);
        $export_search_mode = strval(TYPE_NOTICE);
    } elseif ($export_scope_search) {
        $export_search_modes = ["tous", "title", "titre", "keyword", "abstract", "extended", "external", "docnum"];
        if (isset($mode) && in_array($mode, $export_search_modes, true)) {
            $export_search_mode = $mode;
        }
    }
    // Le contexte d'une recherche vit dans le formulaire cache de la fenetre
    // appelante (il faut le recopier) ; celui d'un segment tient entierement dans
    // lvl + id + mode, la popup se suffit a elle-meme.
    $export_search_needs_opener = ($export_scope_search && $export_search_lvl == "more_results");
    // Plafond dedie a l'export (parametre OPAC max_export_items, section
    // a_general). Il ne remplace pas opac_max_cart_items, qui continue de borner
    // le remplissage du panier : celui-ci s'applique a TOUS les exports de
    // l'OPAC - panier, notice et resultat de recherche. 0 = pas de limite.
    $export_max_items = intval($opac_max_export_items);
    if($action == 'select') {
        print $popup_header;
        // Meme popup pour une notice et pour un resultat de recherche : seuls le
        // titre et les champs de contexte changent, la liste des formats (exports
        // legacy + scenarios ie_) est strictement le meme code.
        $export_popup_title = $export_scope_search ? $msg['export_search_result_title'] : $msg['notice_title_export_record'];
        // La recopie du contexte de recherche peut representer beaucoup de champs
        // (recherche multicriteres) : POST obligatoire, le GET par defaut de la
        // popup notice ne tiendrait pas la longueur d'URL.
        $export_form_attributes = $export_search_needs_opener ? " method='post' onsubmit='return export_search_fill_form(this);'" : "";
        $form = "
            <div id='att' style='z-Index:1000; width=200px'></div>
            <div id='titre-popup'>".$export_popup_title."</div>
            <div>
                <form name='export_form' action='./export.php'".$export_form_attributes."><br />";

        $exp = start_export::get_exports();
        $form .= "<select id='typeexport' name='typeexport'>" ;
        for ($i = 0; $i < count($exp); $i++) {
            $form .= "<option value='".$exp[$i]['ID']."'>".$exp[$i]['NAME']."</option>";
        }
        // E12 : scenarios d'export ImportExport visibles a l'OPAC (opt-in strict),
        // ajoutes a la suite des exports du catalogue legacy (purement additif)
        $ie_scenarios = ExportScenarioRegistry::getSelectableScenarios(true);
        if (count($ie_scenarios)) {
            $form .= "<optgroup label=\"".htmlentities($msg['ie_export_scenarios_group'], ENT_QUOTES, $charset)."\">";
            foreach ($ie_scenarios as $ie_scenario) {
                $form .= "<option value='".$ie_scenario['value']."'>".htmlentities($ie_scenario['name'], ENT_QUOTES, $charset)."</option>";
            }
            $form .= "</optgroup>";
        }
        $form .= "</select>" ;
        $form .= "<input type='hidden' name='action' value='export' />";
        if ($export_scope_search) {
            $form .= "<input type='hidden' name='export_scope' value='search' />";
            $form .= "<input type='hidden' name='mode' value='".htmlentities($export_search_mode, ENT_QUOTES, $charset)."' />";
            if (!$export_search_needs_opener) {
                // Contexte auto-porte (segment) : lvl + id suffisent a la resolution.
                $form .= "<input type='hidden' name='lvl' value='".htmlentities($export_search_lvl, ENT_QUOTES, $charset)."' />";
                $form .= "<input type='hidden' name='id' value='".intval($export_search_id)."' />";
            }
            // Sinon, le reste du contexte (user_query, clause, tri, criteres de la
            // multicriteres...) n'est pas connu de la popup : il est recopie au
            // moment de la soumission depuis la fenetre appelante.
        } else {
            $form .= "<input type='hidden' name='notice_id' value='". htmlentities($notice_id, ENT_QUOTES, $charset) ."' />";
        }

        $form .= "<span class=\"export_notice_button\">&nbsp;</span><input type='submit' class='bouton' value=\"{$msg['notice_title_export_record_button']}\" />";
        // Annonce du plafond a l'usager AVANT qu'il ne lance l'export : la reponse
        // etant un telechargement, il n'y a pas d'ecran ou l'avertir apres coup.
        // Rien a afficher pour une notice unique (toujours un seul element) ni
        // quand le plafond est desactive.
        if ($export_scope_search && $export_max_items > 0) {
            $form .= "<div class='export_max_items_info'>".sprintf(htmlentities($msg['export_max_items_info'], ENT_QUOTES, $charset), $export_max_items)."</div>";
        }
        $form .= '</form>
                    <div class="close_export_popup"><a href="#" onclick="window.close()">'.$msg['close_export_record_popup'].'</a></div>
                    </div>';
        if ($export_search_needs_opener) {
            $form .= export_get_search_context_script($msg['export_search_result_no_opener']);
        }
        print $form;
        print $popup_footer;
    } elseif ($action == "export") {
        // E12-O3 : export d'un RESULTAT DE RECHERCHE. La liste des notices est
        // resolue par le MEME chemin que "Ajouter le resultat au panier"
        // (caddie_controller::get_notices), sans jamais toucher a
        // $_SESSION['cart'] : aucun panier n'est cree, la liste transite
        // directement. Une fois $items constitue, les deux flux d'export
        // existants (scenario ie_ et catalogue legacy) sont inchanges.
        if ($export_scope_search) {
            require_once($base_path."/classes/search.class.php");
            require_once($base_path."/classes/searcher.class.php");
            require_once($base_path."/classes/filter_results.class.php");
            require_once($base_path."/classes/cart.class.php");
            require_once($base_path."/classes/caddie/caddie_controller.class.php");

            // Contexte issu de la liste blanche calculee plus haut : jamais des
            // valeurs brutes de la requete.
            $lvl = $export_search_lvl;
            $mode = $export_search_mode;
            caddie_controller::set_user_query(stripslashes(isset($user_query) ? $user_query : ""));

            // Le plafond est aussi passe a la resolution : il y borne la memoire
            // pendant la constitution de la liste, et s'applique apres le
            // filtrage de visibilite. Ce n'est pas un doublon du plafonnement
            // final ci-dessous, qui reste le point d'application unique.
            $search_export_list = caddie_controller::get_notices($export_search_id, $export_max_items);
            $items = ($search_export_list === "") ? [] : explode(",", $search_export_list);

            // Filtrage de visibilite applique systematiquement sur le chemin
            // export. Deux precautions :
            // - les notices externes (es<rid>) sont mises de cote, elles ne
            //   vivent pas dans la table notices ;
            // - filter_results reconstruit sa propre liste dans l'ordre de sa
            //   requete : on l'utilise en LISTE BLANCHE pour conserver l'ordre
            //   de tri affiche a l'usager.
            $search_export_local = [];
            $search_export_extern = [];
            foreach ($items as $search_export_item) {
                if (substr((string) $search_export_item, 0, 2) == "es") {
                    $search_export_extern[] = $search_export_item;
                } elseif (intval($search_export_item)) {
                    $search_export_local[] = intval($search_export_item);
                }
            }
            if (count($search_export_local)) {
                $search_export_filter = new filter_results(implode(",", $search_export_local));
                $search_export_allowed = array_flip($search_export_filter->get_array_results());
                $search_export_kept = [];
                foreach ($search_export_local as $search_export_id) {
                    if (isset($search_export_allowed[$search_export_id])) {
                        $search_export_kept[] = $search_export_id;
                    }
                }
                $search_export_local = $search_export_kept;
            }
            $items = array_merge($search_export_local, $search_export_extern);
            if (!count($items)) {
                print "<script>alert(\"".addslashes($msg['export_aucune_notice'])."\"); history.go(-1);</script>";
                die();
            }
        }
        // Point d'application UNIQUE du plafond d'export : il couvre les trois
        // points d'entree de l'OPAC (panier, notice, resultat de recherche) et
        // les deux branches d'export (scenario ie_ et catalogue legacy), qui
        // consomment $items sans savoir d'ou il vient. 0 = pas de limite.
        if ($export_max_items > 0 && count($items) > $export_max_items) {
            $items = array_slice($items, 0, $export_max_items);
        }
        // E12 : routage des scenarios d'export ImportExport (valeur de selecteur ie_<id>).
        // Execution SYNCHRONE (l'usager attend son fichier), liste blanche visibleOpac
        // re-verifiee cote serveur, notices externes (es<recid>) exclues (le scenario
        // lit la base locale), filtre de diffusion NG72 conserve.
        $ie_scenario_id = ExportScenarioRegistry::scenarioIdFromSelectorValue((string) $typeexport);
        if ($ie_scenario_id) {
            if (!ExportScenarioRegistry::isSelectable($ie_scenario_id, true)) {
            	print "<script>alert(\"".addslashes($msg['ie_export_selector_failed'])."\"); history.go(-1);</script>";
                die();
            }
            $ie_ids = [];
            foreach ($items as $ie_item) {
                if (substr((string) $ie_item, 0, 2) == "es") {
                    continue;
                }
                $ie_id = intval($ie_item);
                if ($ie_id) {
                    $ie_ids[] = $ie_id;
                }
            }
            // Exclure de l'export les fiches interdites de diffusion (administration,
            // Notices > Origines des notices NG72) - meme regle que le flux legacy
            if (count($ie_ids)) {
                $ie_query = "SELECT notice_id FROM notices JOIN origine_notice ON origine_catalogage = orinot_id
                    WHERE orinot_diffusion = '1' AND notice_id IN (".implode(",", $ie_ids).")";
                $ie_ids = [];
                $ie_result = pmb_mysql_query($ie_query);
                if ($ie_result) {
                    while ($ie_row = pmb_mysql_fetch_row($ie_result)) {
                        $ie_ids[] = intval($ie_row[0]);
                    }
                }
            }
            if (!count($ie_ids)) {
                print "<script>alert(\"".addslashes($msg['export_aucune_notice'])."\"); history.go(-1);</script>";
                die();
            }
            $ie_files = [];
            try {
                $ie_files = ExportScenarioRegistry::run($ie_scenario_id, [
                    "exportType" => 5,
                    "listEntityType" => "record",
                    "listIds" => $ie_ids
                ], 0);
            } catch (Throwable $ie_error) {
                $ie_files = [];
            }
            $ie_temp_dir = realpath($base_path."/temp");
            // Plusieurs fichiers produits : une seule reponse HTTP possible -> archive zip
            if (count($ie_files) > 1 && class_exists('ZipArchive') && $ie_temp_dir !== false) {
                $ie_zip_name = "export_scenario_".$ie_scenario_id."_".md5(uniqid("", true)).".zip";
                $ie_zip = new ZipArchive();
                if ($ie_zip->open($ie_temp_dir.DIRECTORY_SEPARATOR.$ie_zip_name, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
                    foreach ($ie_files as $ie_file) {
                        $ie_path = realpath($base_path."/temp/".basename($ie_file));
                        if ($ie_path !== false && strpos($ie_path, $ie_temp_dir.DIRECTORY_SEPARATOR) === 0 && is_file($ie_path)) {
                            $ie_zip->addFile($ie_path, basename($ie_file));
                        }
                    }
                    $ie_zip->close();
                    $ie_files = [$ie_zip_name];
                }
            }
            $ie_download = count($ie_files) ? basename($ie_files[0]) : "";
            $ie_path = ($ie_download !== "" && $ie_temp_dir !== false) ? realpath($base_path."/temp/".$ie_download) : false;
            if ($ie_path === false || strpos($ie_path, $ie_temp_dir.DIRECTORY_SEPARATOR) !== 0 || !is_file($ie_path)) {
                print "<script>alert(\"".addslashes($msg['ie_export_selector_failed'])."\"); history.go(-1);</script>";
                die();
            }
            header('Content-Description: File Transfer');
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="'.$ie_download.'"');
            header('Content-Length: '.filesize($ie_path));
            header('Cache-Control: no-cache, no-store, must-revalidate');
            header('Pragma: no-cache');
            readfile($ie_path);
            generate_log('export', [], true);
            die();
        }
        $exportation = "";
        $nb_fiche = 0;
        $nb_fiche_total = count($items);
        $n_notices = $nb_fiche_total;
        $_SESSION["param_export"]["notice_exporte"] = [];
        for ($z = 0; $z < $nb_fiche_total; $z++) {
            $id_externe = 0;
            $is_externe = false;
            $sql = '';
            if (substr($items[$z], 0, 2) != "es") {
                // Exclure de l'export (opac, panier) les fiches interdites de diffusion dans administration, Notices > Origines des notices NG72
                $sql = "select 1 from origine_notice,notices where notice_id = '$items[$z]' and origine_catalogage = orinot_id and orinot_diffusion='1'";
            } else {
                $id_externe = intval(substr($items[$z], 2));
                if($id_externe) {
                    $is_externe = true;
                    $requete = "SELECT source_id FROM external_count WHERE rid=".$id_externe;
                    $myQuery = pmb_mysql_query($requete);
                    $source_id = pmb_mysql_result($myQuery, 0, 0);
                    $sql = "select 1 from entrepot_source_$source_id where recid='".$id_externe."' group by ufield,usubfield,field_order,subfield_order,value";
                }
            }
            if($sql) {
                $res = pmb_mysql_query($sql);
                if (pmb_mysql_fetch_array($res)) {
                    $nb_fiche++;
                    $export = new start_export(($id_externe ? $id_externe : $items[$z]), $typeexport, $is_externe, $keep_expl, $keep_explnum) ;
                    $exportation .= $export->output_notice;
                }
            }
        }

        if ($nb_fiche > 0) {
            $exportation = $export->get_header().$exportation.$export->get_footer();
            header("Content-type: ".$export->get_mime_type());
            header('Content-Disposition: attachment; filename="export.'.$export->get_suffix().'"');

            print $exportation;

            generate_log('export', [], true);
        } else {
            print "<script>alert(\"".$msg['export_aucune_notice']."\"); history.go(-1);</script>";
        }
    }
}
