From 86da5dd72741a064a842de486163d85cf6f394fc Mon Sep 17 00:00:00 2001
From: nicod_ <nicod@lerebooteux.fr>
Date: Thu, 16 Nov 2023 17:52:03 +0100
Subject: [PATCH] =?UTF-8?q?feat(WIP):=20V2=20avec=20rupture=20de=20compati?=
 =?UTF-8?q?bilit=C3=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Les blocktypes ne sont plus gérés dans le privé avec une table dédiée et un constructeur de formulaires, mais se basent sur des fichiers blocks/*.yaml (comme saisies, compositions, inserer_modele etc.)
Une procédure de mise à jour / migration est prévue, cf README.md
---
 README.md                                    |  13 +++
 TODO.md                                      |  14 +--
 base/blocks.php                              |  81 ++------------
 blocks_administrations.php                   |  62 ++++-------
 formulaires/exporter_blocktypes.html         |  30 +++++
 formulaires/exporter_blocktypes.php          | 111 +++++++++++++++++++
 formulaires/supprimer_blocktypes.html        |  26 +++++
 formulaires/supprimer_blocktypes.php         |  26 +++++
 paquet.xml                                   |   4 +-
 prive/squelettes/contenu/update_blocks2.html |  13 +++
 10 files changed, 256 insertions(+), 124 deletions(-)
 create mode 100644 formulaires/exporter_blocktypes.html
 create mode 100644 formulaires/exporter_blocktypes.php
 create mode 100644 formulaires/supprimer_blocktypes.html
 create mode 100644 formulaires/supprimer_blocktypes.php
 create mode 100644 prive/squelettes/contenu/update_blocks2.html

diff --git a/README.md b/README.md
index bae5074..5330048 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,19 @@ Un plugin pour composer des pages sous forme de blocs, rangés les uns à la sui
 
 **[WIP]** En cours de développement, des choses peuvent bouger de façon significative (cf TODO.md)
 
+## Mise à jour / migration en v2
+
+Les types de blocks ne sont plus gérés en wysiwyg dans l'espace privé avec un constructeur de formulaire.\
+On se base plutôt sur le même fonctionnement que inserer_modeles, saisies ou compostions, en cherchant des fichiers .yaml dans les répertoires 'blocks' des squelettes.
+
+Les tables spip_blocktypes et spip_blocktypes_liens ne sont donc plus utilisées.
+
+Une procédure de mise à jour est prévue : dans l'espace privé, rendez vous sur ecrire/?exec=update_blocks2 qui vous permettra :
+* d'exporter tous les types de blocks existant en yaml dans un répertoire
+* de supprimer les tables inutiles
+
+## Description
+
 Chaque type de bloc a un identifiant unique (slug), qui permet de chercher un squelette dans le path sous la forme blocks/identifiant.html, sinon on prend blocks/dist.html\
 Dans le privé, on cherche d'abord blocks_prive/identifiant.html, ce qui permet d'avoir un affichage différent dans l'espace privé du site public (espace plus réduit en largeur, pas les même css ou js chargés)
 
diff --git a/TODO.md b/TODO.md
index 5acfd87..94e56c6 100644
--- a/TODO.md
+++ b/TODO.md
@@ -13,32 +13,26 @@
 
 **Technique**
 
+[ ] Pouvoir brancher un type de block sur un modèle auto-documenté (html + yaml) : un blocks/modele_*.yaml spécifique qui référence le modèle ?
+
 [ ] Pouvoir restreindre l'utilisation de certains types de blocs à certains objets
 - ajouter un critère sur les boucles blocks ou modifier ces boucles en pipeline pour ne remonter que les blocs dont les types sont associables à l'objet en cours
 
 [ ] Si le type de block est associable aux rubriques, pouvoir restreindre son utilisation à une branche
 - remarque idem point précédent
 
-[ ] Pouvoir brancher un type de block sur un modèle auto-documenté (html + yaml), ou un blocks/*.yaml spécifique
-- continuer à utiliser en parallèle le constructeur de formulaire ? il est quand même très pratique
-
 [ ] Gestion de champs de type fichiers : comment les associer aux blocks ?
 - comme des documents liés dont l'id est référencé dans la valeur du champ, en plus d'un lien dans spip_documents_liens ? (hum...)
 - avec des rôles dynamiques ? (hum...)
 
 **UX**
 
-[ ] Créer quelques blocks types à l'installation
+[ ] Créer quelques types de blocks de démo en .yaml + .html dans demo/blocks
 - Texte simple avec titre optionnel + niveaux de titre
-- ...
+- Bloc conteneur + bloc enfant
 
 ## Questions
 
-[ ] Saisie blocktypes : utilisation de leurs logos comme illustrations d'exemples de mise en page ?
-
-[ ] Une config pour insérer directement les blocks dans la balise #TEXTE des articles, pour ne pas nécessiter de modification de squelette et pour que TEXTE renvoie tout le temps le contenu complet (texte + block)
-- quid des autres objets
-
 [ ] Pouvoir détacher un block d'un objet et le rattacher à un autre objet ?
 
 [ ] CSS : charger (privé et public) un blocks/identifiant.css ou html.css ou scss ?
diff --git a/base/blocks.php b/base/blocks.php
index 0c005fa..b8688c3 100644
--- a/base/blocks.php
+++ b/base/blocks.php
@@ -25,7 +25,6 @@ if (!defined('_ECRIRE_INC_VERSION')) {
 function blocks_declarer_tables_interfaces($interfaces) {
 
 	$interfaces['table_des_tables']['blocks'] = 'blocks';
-	$interfaces['table_des_tables']['blocktypes'] = 'blocktypes';
 
 	return $interfaces;
 }
@@ -46,7 +45,7 @@ function blocks_declarer_tables_objets_sql($tables) {
 		'principale'              => 'oui',
 		'field'                   => [
 			'id_block'     => 'bigint(21) NOT NULL',
-			'id_blocktype' => 'bigint(21) NOT NULL DEFAULT 0',
+			'blocktype'    => 'varchar(255) DEFAULT "" NOT NULL',
 			'objet'        => 'varchar(25) DEFAULT "" NOT NULL',
 			'id_objet'     => 'bigint(21) DEFAULT "0" NOT NULL',
 			'rang_lien'    => 'int(4) DEFAULT "0" NOT NULL',
@@ -57,17 +56,16 @@ function blocks_declarer_tables_objets_sql($tables) {
 			'maj'          => 'timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
 		],
 		'key'                     => [
-			'PRIMARY KEY'      => 'id_block',
-			'KEY id_blocktype' => 'id_blocktype',
-			'KEY objet'        => 'objet',
-			'KEY id_objet'     => 'id_objet',
-			'KEY statut'       => 'statut',
+			'PRIMARY KEY'   => 'id_block',
+			'KEY blocktype' => 'blocktype',
+			'KEY objet'     => 'objet',
+			'KEY id_objet'  => 'id_objet',
+			'KEY statut'    => 'statut',
 		],
 		'titre'                   => 'id_blocktype AS titre',
 		'date'                    => 'date',
-		'champs_editables'        => ['id_blocktype', 'id_objet', 'objet', 'ancre'],
-		'champs_versionnes'       => ['id_blocktype', 'id_objet', 'objet', 'ancre', 'valeurs'],
-		'tables_jointures'        => ['spip_blocktypes'],
+		'champs_editables'        => ['blocktype', 'id_objet', 'objet', 'ancre'],
+		'champs_versionnes'       => ['blocktype', 'id_objet', 'objet', 'ancre', 'valeurs'],
 		'statut_textes_instituer' => [
 			'prepa'  => 'texte_statut_en_cours_redaction',
 			'prop'   => 'texte_statut_propose_evaluation',
@@ -88,71 +86,8 @@ function blocks_declarer_tables_objets_sql($tables) {
 		'page'                    => false,
 	];
 
-	$tables['spip_blocktypes'] = [
-		'type'              => 'blocktype',
-		'principale'        => 'oui',
-		'field'             => [
-			'id_blocktype' => 'bigint(21) NOT NULL',
-			'titre'        => 'text NOT NULL DEFAULT ""',
-			'description'  => 'text NOT NULL DEFAULT ""',
-			'identifiant'  => 'varchar(255) NOT NULL DEFAULT ""',
-			'saisies'      => 'text NOT NULL DEFAULT ""',
-			'objets'       => 'text NOT NULL DEFAULT ""',
-			'maj'          => 'timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
-		],
-		'key'               => [
-			'PRIMARY KEY'            => 'id_blocktype',
-			'UNIQUE KEY identifiant' => 'identifiant',
-		],
-		'titre'             => 'titre AS titre, "" AS lang',
-		'champs_editables'  => ['titre', 'description', 'saisies', 'objets', 'identifiant'],
-		'champs_versionnes' => ['titre', 'description', 'saisies', 'objets', 'identifiant'],
-		'rechercher_champs' => ["titre" => 10, 'description' => 5, 'identifiant' => 5],
-		'tables_jointures'  => [],
-		'roles_titres'      => [
-			'enfant' => _T('blocktype:role_enfant'),
-			'parent' => _T('blocktype:role_parent'),
-		],
-		'roles_objets'      => [
-			'blocktypes' => [
-				'choix'  => ['enfant', 'parent'],
-				'defaut' => '',
-			],
-		],
-		'page'              => false,
-	];
-
 	// jointure potentielle avec tous les objets
 	$tables[]['tables_jointures'][] = 'blocks';
 
 	return $tables;
 }
-
-/**
- * Déclaration des tables secondaires (liaisons)
- *
- * @pipeline declarer_tables_auxiliaires
- * @param array $tables
- *     Description des tables
- * @return array
- *     Description complétée des tables
- */
-function blocks_declarer_tables_auxiliaires($tables) {
-
-	// table de liaison entre blocktypes enfants / parents
-	$tables['spip_blocktypes_liens'] = [
-		'field' => [
-			'id_blocktype' => 'bigint(21) DEFAULT "0" NOT NULL',
-			'id_objet'     => 'bigint(21) DEFAULT "0" NOT NULL',
-			'objet'        => 'varchar(25) DEFAULT "" NOT NULL',
-			'rang_lien'    => 'int(4) DEFAULT "0" NOT NULL',
-			'role'         => 'varchar(25) DEFAULT "" NOT NULL', // [enfant|parent]
-		],
-		'key'   => [
-			'PRIMARY KEY'      => 'id_blocktype,id_objet,objet,role',
-			'KEY id_blocktype' => 'id_blocktype',
-		],
-	];
-
-	return $tables;
-}
diff --git a/blocks_administrations.php b/blocks_administrations.php
index 39a2d92..385095d 100644
--- a/blocks_administrations.php
+++ b/blocks_administrations.php
@@ -45,6 +45,12 @@ function blocks_upgrade($nom_meta_base_version, $version_cible) {
 		['sql_drop_table', 'spip_blocks_liens'],
 	];
 
+	$maj['2.0.0'] = [
+		['sql_alter', 'TABLE spip_blocks DROP INDEX id_blocktype'],
+		['maj_tables', ['spip_blocks']],
+		['blocks_update_2_0_0'],
+	];
+
 	include_spip('base/upgrade');
 	maj_plugin($nom_meta_base_version, $version_cible, $maj);
 }
@@ -75,50 +81,12 @@ function blocks_vider_tables($nom_meta_base_version) {
 }
 
 function blocks_installe_config() {
-	include_spip('blocks_fonctions');
-
-	// associer par défaut aux articles
+	// associer par défaut les blocks aux articles
 	ecrire_config('blocks/objets',
 		[
 			0 => 'spip_articles',
 		],
 	);
-
-	// un type de block de démo : titre + texte
-	sql_insertq(
-		'spip_blocktypes',
-		[
-			'titre'       => 'Démo texte simple',
-			'identifiant' => 'demo_simple',
-			'saisies'     => blocks_serialize([
-				[
-					'options'     =>
-						[
-							'nom'             => 'titre',
-							'label'           => 'Titre',
-							'conteneur_class' => 'pleine_largeur',
-							'obligatoire'     => 'oui',
-						],
-					'identifiant' => uniqid('@'),
-					'saisie'      => 'input',
-				],
-				[
-					'options'     =>
-						[
-							'nom'              => 'texte',
-							'label'            => 'Texte',
-							'conteneur_class'  => 'pleine_largeur',
-							'rows'             => '10',
-							'inserer_barre'    => 'edition',
-							'previsualisation' => 'on',
-						],
-					'identifiant' => uniqid('@'),
-					'saisie'      => 'textarea',
-				],
-			]),
-		]
-	);
-
 }
 
 function blocks_update_1_0_1() {
@@ -148,3 +116,19 @@ function blocks_update_1_3_0() {
 		);
 	}
 }
+
+function blocks_update_2_0_0() {
+	// on modifie id_blocktype en renseignant directement l'identifiant du blocktype
+	$blocktypes = sql_allfetsel('id_blocktype, identifiant', 'spip_blocktypes');
+	foreach ($blocktypes as $blocktype) {
+		sql_updateq(
+			'spip_blocks',
+			[
+				'blocktype' => $blocktype['identifiant'],
+			],
+			'id_blocktype = ' . $blocktype['id_blocktype']
+		);
+	}
+	// on n'a plus besoin de cette colonne
+	sql_alter('TABLE spip_blocks DROP id_blocktype');
+}
diff --git a/formulaires/exporter_blocktypes.html b/formulaires/exporter_blocktypes.html
new file mode 100644
index 0000000..ccbdef4
--- /dev/null
+++ b/formulaires/exporter_blocktypes.html
@@ -0,0 +1,30 @@
+<div id="formulaire_#FORM" class='formulaire_spip formulaire_#FORM'>
+	[<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+	[<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+
+	<form method="post" action="#ENV{action}#formulaire_#FORM">
+		<div>
+			#ACTION_FORMULAIRE{#ENV{action}}
+
+			<div class="editer-groupe">
+
+				<div class="editer pleine_largeur">
+					<div class="explication">
+						<p>Les types de blocks seront exportes en YAML dans le répertoire spécifié</p>
+						<p>Précisez le chemin relatif à l'espace privé, à priori celui qui contient déjà
+							vos squelettes de blocks (ex: ../plugins/mon_plugin/squelettes/blocks)</p>
+					</div>
+				</div>
+
+				[(#SAISIE{input, chemin, label=Chemin pour l'export des fichiers})]
+
+			</div>
+
+			<p class="boutons">
+				<button type="submit" class="submit">Générer les fichiers YAML</button>
+			</p>
+
+		</div>
+	</form>
+
+</div>
diff --git a/formulaires/exporter_blocktypes.php b/formulaires/exporter_blocktypes.php
new file mode 100644
index 0000000..c0168da
--- /dev/null
+++ b/formulaires/exporter_blocktypes.php
@@ -0,0 +1,111 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+	return;
+}
+
+function formulaires_exporter_blocktypes_charger() {
+	if (!autoriser('webmestre')) {
+		return null;
+	}
+
+	return ['chemin' => ''];
+}
+
+function formulaires_exporter_blocktypes_verifier() {
+	$erreurs = [];
+	$chemin = _request('chemin');
+	if (!$chemin) {
+		$erreurs['chemin'] = _T('info_obligatoire');
+	}
+	if (!is_dir($chemin)) {
+		$erreurs['chemin'] = 'Le chemin n\'existe pas';
+	} else if (!is_writable($chemin)) {
+		$erreurs['chemin'] = 'Le chemin n\'est pas autorisé en écriture';
+	}
+
+	return $erreurs;
+}
+
+function formulaires_exporter_blocktypes_traiter() {
+	$retour = [];
+	$chemin = _request('chemin');
+
+	if ($chemin && autoriser('webmestre')) {
+		$chemin = rtrim($chemin, '/') . '/';
+
+		include_spip('inc/yaml');
+		include_spip('blocks_fonctions');
+
+		$blocks = sql_allfetsel('*', 'spip_blocktypes');
+		foreach ($blocks as $block) {
+
+			$block['enfants'] = array_column(sql_allfetsel(
+				'b.identifiant',
+				'spip_blocktypes_liens bl join spip_blocktypes b on(b.id_blocktype = bl.id_objet)',
+				[
+					'bl.objet="blocktype"',
+					'bl.role="enfant"',
+					'bl.id_blocktype=' . $block['id_blocktype'],
+				]
+			), 'identifiant');
+
+			$block['parents'] = array_column(sql_allfetsel(
+				'b.identifiant',
+				'spip_blocktypes_liens bl join spip_blocktypes b on(b.id_blocktype = bl.id_objet)',
+				[
+					'bl.objet="blocktype"',
+					'bl.role="parent"',
+					'bl.id_blocktype=' . $block['id_blocktype'],
+				]
+			), 'identifiant');
+
+			$data = [
+				'identifiant' => $block['identifiant'],
+				'titre'       => $block['titre'],
+				'description' => $block['description'],
+			];
+			if ($block['objets']) {
+				$data['objets'] = $block['objets'];
+			}
+			if ($block['enfants']) {
+				$data['enfants'] = $block['enfants'];
+			}
+			if ($block['parents']) {
+				$data['parents'] = $block['parents'];
+			}
+
+			if ($saisies = blocks_deserialize($block['saisies'])) {
+				// nettoyage des saisies
+				$data['saisies'] = array_filter($saisies);
+				foreach ($data['saisies'] as $key => $saisie) {
+					if (empty($saisie['verifier'])) {
+						unset($saisie['verifier']);
+					}
+					if (($saisie['options']['autocomplete'] ?? '') === 'defaut') {
+						unset($saisie['options']['autocomplete']);
+					}
+					if ((string)($saisie['options']['size'] ?? '') === '40') {
+						unset($saisie['options']['size']);
+					}
+					unset($saisie['identifiant']);
+					$data['saisies'][$key] = array_filter($saisie);
+				}
+			}
+
+			// export en yaml
+			$yaml = yaml_encode($data, ['inline' => 6]);
+			if (ecrire_fichier($chemin . $block['identifiant'] . '.yaml', $yaml)) {
+				$retour['message_ok'][] = 'Fichier généré : ' . $chemin . $block['identifiant'] . '.yaml';
+			} else {
+				$retour['message_erreur'][] = 'Erreur écriture fichier : ' . $chemin . $block['identifiant'] . '.yaml';
+			}
+
+		}
+
+		$retour['message_ok'] = join('<br>', $retour['message_ok'] ?? []);
+		$retour['message_erreur'] = join('<br>', $retour['message_erreur'] ?? []);
+
+	}
+	return $retour;
+}
diff --git a/formulaires/supprimer_blocktypes.html b/formulaires/supprimer_blocktypes.html
new file mode 100644
index 0000000..178fede
--- /dev/null
+++ b/formulaires/supprimer_blocktypes.html
@@ -0,0 +1,26 @@
+<div id="formulaire_#FORM" class='formulaire_spip formulaire_#FORM'>
+	[<p class="reponse_formulaire reponse_formulaire_ok">(#ENV**{message_ok})</p>]
+	[<p class="reponse_formulaire reponse_formulaire_erreur">(#ENV*{message_erreur})</p>]
+
+	<form method="post" action="#ENV{action}#formulaire_#FORM">
+		<div>
+			#ACTION_FORMULAIRE{#ENV{action}}
+
+			<div class="editer-groupe">
+
+				<div class="editer pleine_largeur">
+					<div class="explication">
+						<p>Une fois les blocktypes exportés en YAML, vous pouvez supprimer les tables <em>spip_blocktypes</em> et <em>spip_blocktypes_liens</em></p>
+					</div>
+				</div>
+
+			</div>
+
+			<p class="boutons">
+				<button type="submit" class="submit">Supprimer les tables</button>
+			</p>
+
+		</div>
+	</form>
+
+</div>
diff --git a/formulaires/supprimer_blocktypes.php b/formulaires/supprimer_blocktypes.php
new file mode 100644
index 0000000..cc15b4f
--- /dev/null
+++ b/formulaires/supprimer_blocktypes.php
@@ -0,0 +1,26 @@
+<?php
+
+if (!defined('_ECRIRE_INC_VERSION')) {
+	return;
+}
+
+function formulaires_supprimer_blocktypes_charger() {
+	if (!autoriser('webmestre')) {
+		return null;
+	}
+
+	return [];
+}
+
+function formulaires_supprimer_blocktypes_traiter() {
+	if (
+		sql_drop_table('spip_blocktypes')
+		&& sql_drop_table('spip_blocktypes_liens')
+	) {
+		$retour['message_ok'] = 'Tables supprimées';
+	} else {
+		$retour['message_erreur'] = 'Erreur de suppression';
+	}
+
+	return $retour;
+}
diff --git a/paquet.xml b/paquet.xml
index 1785c5f..6980bba 100644
--- a/paquet.xml
+++ b/paquet.xml
@@ -1,11 +1,11 @@
 <paquet
     prefix="blocks"
-    version="1.0.0"
+    version="2.0.0"
     etat="dev"
     compatibilite="[4.2.0;4.2.*]"
     logo="prive/themes/spip/images/blocks-xx.svg"
     documentation=""
-    schema="1.3.0"
+    schema="2.0.0"
 >
 
     <nom>Blocks</nom>
diff --git a/prive/squelettes/contenu/update_blocks2.html b/prive/squelettes/contenu/update_blocks2.html
new file mode 100644
index 0000000..ac3714e
--- /dev/null
+++ b/prive/squelettes/contenu/update_blocks2.html
@@ -0,0 +1,13 @@
+[(#AUTORISER{webmestre}|sinon_interdire_acces)]
+
+<h1 class="grostitre">Mise à jour du plugin Blocsk en version 2</h1>
+
+<div class="ajax">
+	<h2>1 - Exporter les blocktypes en YAML</h2>
+	#FORMULAIRE_EXPORTER_BLOCKTYPES
+</div>
+
+<div class="ajax">
+	<h2>2 - Supprimer les tables des blocktypes</h2>
+	#FORMULAIRE_SUPPRIMER_BLOCKTYPES
+</div>
-- 
GitLab