<?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.     *
 * \***************************************************************************/

namespace Spip\Texte\Collecteur;

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

/**
 *    traite les modeles (dans la fonction typo), en remplacant
 *    le raccourci <modeleN|parametres> par la page calculee a
 *    partir du squelette modeles/modele.html
 *    Le nom du modele doit faire au moins trois caracteres (evite <h2>)
 *    Si $doublons==true, on repere les documents sans calculer les modeles
 *    mais on renvoie les params (pour l'indexation par le moteur de recherche)
 */
class Modeles extends AbstractCollecteur {

	protected static string $markPrefix = 'MODELE';

	/**
	 * La preg pour découper et collecter les modèles
	 * @var string
	 */
	protected string $preg_modele;

	public function __construct(?string $preg = null) {

		$this->preg_modele = ($preg ?:
			'@<([a-z_-]{3,})' # <modele
			. '\s*([0-9]*)\s*' # id
			. '([|](?:<[^<>]*>|[^>])*?)?' # |arguments (y compris des tags <...>)
			. '\s*/?' . '>@isS' # fin du modele >
		);
	}

	/**
	 * Sanitizer une collection d'occurences de modèle : on ne fait rien
	 *
	 * @param array $collection
	 * @param string $sanitize_callback
	 * @return array
	 */
	protected function sanitizer_collection(array $collection, string $sanitize_callback): array {

		return $collection;
	}

	/**
	 * @param array{collecter_liens?: bool, detecter_presence?: bool} $options
	 */
	public function collecter(string $texte, array $options = []): array {
		if (!$texte) {
			return [];
		}

		// collecter les matchs de la preg
		$modeles = $this->collecteur($texte, '', '<', $this->preg_modele);

		$pos_prev = 0;
		foreach ($modeles as $k => &$modele) {
			$pos = $modele['pos'];
			$modele['type'] = $modele['match'][1];
			$modele['id'] = $modele['match'][2] ?? '';
			$modele['params'] = $modele['match'][3] ?? '';

			$longueur = $modele['length'];
			$end = $pos + $longueur;

			// il faut avoir un id ou des params commençant par un | sinon c'est une simple balise html
			if (empty($modele['id']) and empty($modele['params'])) {
				unset($modeles[$k]);
				continue;
			}

			// si on veut seulement detecter la présence, on peut retourner tel quel
			if (!empty($options['detecter_presence'])) {
				break;
			}

			$modele['lien'] = false;
			if (!empty($options['collecter_liens'])
				and $pos_fermeture_lien = stripos($texte, '</a>', $end)
				and !strlen(trim(substr($texte, $end, $pos_fermeture_lien - $end)))) {

				$pos_lien_ouvrant = stripos($texte, '<a', $pos_prev);
				if ($pos_lien_ouvrant !== false
					and $pos_lien_ouvrant < $pos
					and preg_match('/<a\s[^<>]*>\s*$/i', substr($texte, $pos_prev, $pos - $pos_prev), $r)
				) {
					$modele['lien'] = [
						'href' => extraire_attribut($r[0], 'href'),
						'class' => extraire_attribut($r[0], 'class'),
						'mime' => extraire_attribut($r[0], 'type'),
						'title' => extraire_attribut($r[0], 'title'),
						'hreflang' => extraire_attribut($r[0], 'hreflang')
					];
					$n = strlen($r[0]);
					$pos -= $n;
					$longueur = $pos_fermeture_lien - $pos + 4;
					$end = $pos + $longueur;
				}
			}


			$modele['pos'] = $pos;
			$modele['length'] = $longueur;
			$pos_prev = $end;
		}

		return $modeles;
	}

	/**
	 * Traiter les modeles d'un texte
	 * @param array{doublons?: bool|array, echap?: string, collecteurLiens?: \Spip\Texte\Collecteur\Liens, env?: array, connect?:string} $options
	 * @return string
	 */
	public function traiter(string $texte, array $options) {
		if ($texte) {
			$doublons = $options['doublons'] ?? false;
			$echap = $options['echap'] ?? '';
			$collecteurLiens = $options['collecteurLiens'] ?? null;
			$env = $options['env'] ?? [];
			$connect = $options['connect'] ?? '';

			// preserver la compatibilite : true = recherche des documents
			if ($doublons === true) {
				$doublons = ['documents' => ['doc', 'emb', 'img']];
			}

			$modeles = $this->collecter($texte, ['collecter_liens' => true]);
			if (!empty($modeles)) {
				include_spip('public/assembler');
				$wrap_embed_html = charger_fonction('wrap_embed_html', 'inc', true);

				$offset_pos = 0;
				foreach ($modeles as $m) {
					// calculer le modele
					# hack indexation
					if ($doublons) {
						$texte .= preg_replace(',[|][^|=]*,s', ' ', $m['params']);
					} # version normale
					else {
						// si un tableau de liens a ete passe, reinjecter le contenu d'origine
						// dans les parametres, plutot que les liens echappes
						$params = $m['params'];
						if (!is_null($collecteurLiens)) {
							$params = $collecteurLiens->retablir($params);
						}

						$modele = inclure_modele($m['type'], $m['id'], $params, $m['lien'], $connect ?? '', $env);

						// en cas d'echec,
						// si l'objet demande a une url,
						// creer un petit encadre vers elle
						if ($modele === false) {
							$modele = $m['raw'];

							if (!is_null($collecteurLiens)) {
								$modele = $collecteurLiens->retablir($modele);
							}

							$contexte = array_merge($env, ['id' => $m['id'], 'type' => $m['type'], 'modele' => $modele]);

							if (!empty($m['lien'])) {
								# un eventuel guillemet (") sera reechappe par #ENV
								$contexte['lien'] = str_replace('&quot;', '"', $m['lien']['href']);
								$contexte['lien_class'] = $m['lien']['class'];
								$contexte['lien_mime'] = $m['lien']['mime'];
								$contexte['lien_title'] = $m['lien']['title'];
								$contexte['lien_hreflang'] = $m['lien']['hreflang'];
							}

							$modele = recuperer_fond('modeles/dist', $contexte, [], $connect ?? '');
						}

						// le remplacer dans le texte
						if ($modele !== false) {
							$modele = protege_js_modeles($modele);

							if ($wrap_embed_html) {
								$modele = $wrap_embed_html($m['raw'], $modele);
							}

							$rempl = code_echappement($modele, $echap);
							$texte = substr_replace($texte, $rempl, $m['pos'] + $offset_pos, $m['length']);
							$offset_pos += strlen($rempl) - $m['length'];
						}
					}

					// hack pour tout l'espace prive
					if ((test_espace_prive() or ($doublons)) and !empty($m['id'])) {
						$type = strtolower($m['type']);
						foreach ($doublons ?: ['documents' => ['doc', 'emb', 'img']] as $quoi => $type_modeles) {
							if (in_array($type, $type_modeles)) {
								$GLOBALS["doublons_{$quoi}_inclus"][] = $m['id'];
							}
						}
					}
				}
			}
		}

		return $texte;
	}

}
