<?php

/***************************************************************************\
 *  SPIP, Système de publication pour l'internet                           *
 *                                                                         *
 *  Copyright © avec tendresse depuis 2001                                 *
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
 *                                                                         *
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
\***************************************************************************/

/**
 * Gestion des notes de bas de page
 *
 * @package SPIP\Textwheel\Notes
 **/

if (!defined('_ECRIRE_INC_VERSION')) {
	return;
}

//
// Notes de bas de page
//
if (!defined('_NOTES_OUVRE_REF')) {
	define('_NOTES_OUVRE_REF', '<span class="spip_note_ref">&nbsp;[');
}
if (!defined('_NOTES_FERME_REF')) {
	define('_NOTES_FERME_REF', ']</span>');
}
if (!defined('_NOTES_OUVRE_NOTE')) {
	define('_NOTES_OUVRE_NOTE', '<span class="spip_note_ref">[');
}
if (!defined('_NOTES_FERME_NOTE')) {
	define('_NOTES_FERME_NOTE', ']&nbsp;</span>');
}
if (!defined('_NOTES_RACCOURCI')) {
	define('_NOTES_RACCOURCI', ',\[\[(\s*(<([^>\'"]*)>)?(.*?))\]\],msS');
}


/**
 * Empile ou dépile des notes de bas de page
 *
 * Point d'entrée pour la gestion des notes.
 *
 * @note
 *     C'est stocké dans la globale `$les_notes`, mais pas besoin de le savoir
 * @param bool|string|array $arg
 *     Argument :
 *
 *     - true : empiler l'etat courant, initialiser un nouvel état
 *     - false : restaurer l'état précédent, dénonce un état courant perdu
 *     - chaîne : on y recherche les notes et on les renvoie en tableau
 *     - tableau : texte de notes à rajouter dans ce qu'on a déjà
 *
 *     Ce dernier cas retourne la composition totale, en particulier,
 *     envoyer un tableau vide permet de tout récupérer.
 * @param string $operation
 *     En complément du format attendu dans `$arg`, on indique ici
 *     le type d'oopétation à réaliser (traiter | empiler | depiler |
 *     sauver_etat | restaurer_etat | contexter_cache)
 * @param bool $ignorer_autobr
 *     True pour ne pas prendre en compte les retours à la ligne comme
 *     des tags br (mais 2 à la suite font un paragraphe tout de même).
 * @return string|array
 **/
function inc_notes_dist($arg, $operation = 'traiter', $ignorer_autobr = false) {
	static $pile = [];
	static $next_marqueur = 1;
	static $marqueur = 1;
	global $les_notes, $compt_note, $notes_vues;
	switch ($operation) {
		case 'traiter':
			if (is_array($arg)) {
				return traiter_les_notes($arg, $ignorer_autobr);
			} else {
				return traiter_raccourci_notes($arg, $marqueur > 1 ? $marqueur : '');
			}
			break;
		case 'empiler':
			if ($compt_note == 0) { // si le marqueur n'a pas encore ete utilise, on le recycle dans la pile courante
				array_push($pile, [$les_notes ?? '', $compt_note ?? 0, $notes_vues, 0]);
			} else {
				// sinon on le stocke au chaud, et on en cree un nouveau
				array_push($pile, [$les_notes ?? '', $compt_note ?? 0, $notes_vues, $marqueur]);
				$next_marqueur++; // chaque fois qu'on rempile on incremente le marqueur general
				$marqueur = $next_marqueur; // et on le prend comme marqueur courant
			}
			$les_notes = '';
			$compt_note = 0;
			break;
		case 'depiler':
			#$prev_notes = $les_notes;
			if (strlen($les_notes ?? '')) {
				spip_log('notes perdues');
			}
			// si le marqueur n'a pas servi, le liberer
			if (!strlen($les_notes ?? '') and $marqueur == $next_marqueur) {
				$next_marqueur--;
			}
			// on redepile tout suite a une fin d'inclusion ou d'un affichage des notes
			[$les_notes, $compt_note, $notes_vues, $marqueur] = array_pop($pile);
			#$les_notes .= $prev_notes;
			// si pas de marqueur attribue, on le fait
			if (!$marqueur) {
				$next_marqueur++; // chaque fois qu'on rempile on incremente le marqueur general
				$marqueur = $next_marqueur; // et on le prend comme marqueur courant
			}
			break;
		case 'sauver_etat':
			if ($compt_note or $marqueur > 1 or $next_marqueur > 1) {
				return [$les_notes, $compt_note, $notes_vues, $marqueur, $next_marqueur];
			} else {
				return '';
			} // rien a sauver
			break;
		case 'restaurer_etat':
			if ($arg and is_array($arg)) { // si qqchose a restaurer
				[$les_notes, $compt_note, $notes_vues, $marqueur, $next_marqueur] = $arg;
			}
			break;
		case 'contexter_cache':
			if ($compt_note or $marqueur > 1 or $next_marqueur > 1) {
				return ["$compt_note:$marqueur:$next_marqueur"];
			} else {
				return '';
			}
			break;
		case 'reset_all': // a n'utiliser qu'a fins de test
			if (strlen($les_notes ?? '')) {
				spip_log('notes perdues [reset_all]');
			}
			$pile = [];
			$next_marqueur = 1;
			$marqueur = 1;
			$les_notes = '';
			$compt_note = 0;
			$notes_vues = [];
			break;
	}
}


function traiter_raccourci_notes($letexte, $marqueur_notes) {
	global $compt_note, $notes_vues;

	if (
		strpos($letexte, '[[') === false
		or !preg_match_all(_NOTES_RACCOURCI, $letexte, $m, PREG_SET_ORDER)
	) {
		return [$letexte, []];
	}

	// quand il y a plusieurs series de notes sur une meme page
	$mn = !$marqueur_notes ? '' : ($marqueur_notes . '-');
	$mes_notes = [];
	foreach ($m as $r) {
		[$note_source, $note_all, $ref, $nom, $note_texte] = $r;

		// reperer une note nommee, i.e. entre chevrons
		// On leve la Confusion avec une balise en regardant
		// si la balise fermante correspondante existe
		// Cas pathologique:   [[ <a> <a href="x">x</a>]]

		if (
			!(isset($nom) and $ref
			and ((strpos($note_texte, '</' . $nom . '>') === false)
				or preg_match(",<$nom\W.*</$nom>,", $note_texte)))
		) {
			$nom = ++$compt_note;
			$note_texte = $note_all;
		}

		// eliminer '%' pour l'attribut id
		$ancre = $mn . str_replace('%', '_', rawurlencode($nom));

		// ne mettre qu'une ancre par appel de note (XHTML)
		if (!isset($notes_vues[$ancre])) {
			$notes_vues[$ancre] = 0;
		}
		$att = ($notes_vues[$ancre]++) ? '' : " id=\"nh$ancre\"";

		// creer le popup 'title' sur l'appel de note
		// propre est couteux => nettoyer_raccourcis_typo
		if ($title = supprimer_tags(nettoyer_raccourcis_typo($note_texte))) {
			$title = " title=\"" . couper($title, 80) . "\"";
		}

		// ajouter la note aux notes precedentes
		if ($note_texte) {
			$mes_notes[] = [$ancre, $nom, $note_texte];
		}

		// dans le texte, mettre l'appel de note a la place de la note
		if ($nom) {
			$nom = _NOTES_OUVRE_REF . "<a href=\"#nb$ancre\" class=\"spip_note\" rel=\"appendix\"$title$att>$nom</a>" . _NOTES_FERME_REF;
		}

		$pos = strpos($letexte, (string) $note_source);
		$letexte = rtrim(substr($letexte, 0, $pos), ' ')
			. code_echappement($nom)
			. substr($letexte, $pos + strlen($note_source));
	}

	return [$letexte, $mes_notes];
}


function traiter_les_notes($notes, $ignorer_autobr) {
	$mes_notes = '';
	if ($notes) {
		$title = _T('info_notes');
		foreach ($notes as $r) {
			[$ancre, $nom, $texte] = $r;
			$atts = " href=\"#nh$ancre\" class=\"spip_note\" title=\"$title $ancre\" rev=\"appendix\"";
			$mes_notes .= "\n\n"
				. "<div id=\"nb$ancre\"><p" . ($GLOBALS['class_spip'] ? " class=\"spip_note\"" : '') . '>'
				. code_echappement($nom
					? _NOTES_OUVRE_NOTE . '<a' . $atts . ">$nom</a>" . _NOTES_FERME_NOTE
					: '')
				. trim($texte)
				. '</div>';
		}
		if ($ignorer_autobr) {
			$mes_notes = _AUTOBR_IGNORER . $mes_notes;
		}
		$mes_notes = propre($mes_notes);
	}

	return ($GLOBALS['les_notes'] .= $mes_notes);
}
