diff --git a/includes/class-admin-page.php b/includes/class-admin-page.php index 9af033b..ae1492c 100644 --- a/includes/class-admin-page.php +++ b/includes/class-admin-page.php @@ -9,6 +9,14 @@ if (!defined('ABSPATH')) { class Thalim_HAL_Admin_Page { + use Thalim_HAL_Config_Trait; + use Thalim_HAL_CSV_Legacy_Trait; + + // Feature flags — panneaux désactivés en prod, code conservé dans les traits. + // Basculer à true pour réactiver le panneau correspondant. + private const CONFIG_PANEL_ENABLED = false; + private const CSV_IMPORT_ENABLED = false; + private $api; private $message = null; private $wp_users_by_hal_id = null; // Cache: normalized_hal_id => ['id' => int, 'name' => string] @@ -39,9 +47,13 @@ class Thalim_HAL_Admin_Page { echo '

THALIM HAL Importer

'; $this->render_styles(); $this->render_message(); - $this->render_config(); + if (self::CONFIG_PANEL_ENABLED) { + $this->render_config(); + } $this->render_preview(); - $this->render_csv_import(); + if (self::CSV_IMPORT_ENABLED) { + $this->render_csv_import(); + } echo '
'; } @@ -53,11 +65,8 @@ class Thalim_HAL_Admin_Page { } $action = sanitize_text_field($_POST['thalim_hal_action']); - if ($action === 'test_api') { - $result = $this->api->test_connection(); - $this->message = is_wp_error($result) - ? ['error', 'API Error: ' . $result->get_error_message()] - : ['success', "Connection OK! Found {$result['total']} publications."]; + if (self::CONFIG_PANEL_ENABLED && $action === 'test_api') { + $this->handle_test_api(); } if ($action === 'refresh') { @@ -72,10 +81,12 @@ class Thalim_HAL_Admin_Page { $this->handle_import(); } - if ($action === 'csv_upload') $this->handle_csv_upload(); - if ($action === 'csv_batch') $this->handle_csv_batch(); - if ($action === 'csv_cancel') $this->handle_csv_cancel(); - if ($action === 'csv_download_report') $this->handle_csv_download_report(); + if (self::CSV_IMPORT_ENABLED) { + if ($action === 'csv_upload') $this->handle_csv_upload(); + if ($action === 'csv_batch') $this->handle_csv_batch(); + if ($action === 'csv_cancel') $this->handle_csv_cancel(); + if ($action === 'csv_download_report') $this->handle_csv_download_report(); + } } /** @@ -165,24 +176,6 @@ class Thalim_HAL_Admin_Page { esc_attr($this->message[0]), esc_html($this->message[1])); } - private function render_config() { - ?> -
-

Configuration

- - - - -
Structure ID (THALIM)
Document Types
API Endpointapi->get_api_url(10)); ?>
-
- - - -
-
- -
-

Import en masse depuis CSV

-

- Uploader le couple hal-to-import.csv + hal-to-import-context.json - (généré par php scripts/prepare-csv-context.php) pour importer les publications legacy. - Chaque batch traite publications — cliquer plusieurs fois jusqu'à terminaison. -

- - -
- - - - - - - - - - - - - - - - - - - -
Fichier CSV
Fichier contexte (JSON) -

Généré par scripts/prepare-csv-context.php.

Statut des posts - - -
Date du post WP - -
-

-
- - render_csv_progress($queue); ?> - -
- 0 ? round(100 * $done / $total, 1) : 0; - $report_ct = count($queue['report'] ?? []); - ?> -
-

File d'attente active — statut cible : - — backdate :

-

- / - publications traitées (%) - — restantes -

- -

Dernière mise à jour :

- - - -

- Erreur dernier batch : -

- - -
- - - -
- - 0): ?> -
- - - -
- - -
- - - -
-
- message = ['error', 'CSV ou fichier contexte manquant.']; - return; - } - - // Parse CSV -> list of hal_ids - $fh = fopen($_FILES['csv_file']['tmp_name'], 'r'); - if (!$fh) { $this->message = ['error', 'Impossible de lire le CSV.']; return; } - $header = fgetcsv($fh); - $hal_col = array_search('hal_id', $header); - $spip_col = array_search('spip_id', $header); - if ($hal_col === false) { - fclose($fh); - $this->message = ['error', 'Header CSV : colonne hal_id manquante.']; - return; - } - $hal_ids = []; - $spip_map = []; // hal_id => spip_id - while (($row = fgetcsv($fh)) !== false) { - $hid = trim($row[$hal_col] ?? ''); - if ($hid === '') continue; - $hal_ids[] = $hid; - if ($spip_col !== false) $spip_map[$hid] = trim($row[$spip_col] ?? ''); - } - fclose($fh); - $hal_ids = array_values(array_unique($hal_ids)); - - // Parse JSON context - $ctx_raw = file_get_contents($_FILES['ctx_file']['tmp_name']); - $ctx_data = json_decode($ctx_raw, true); - if (!is_array($ctx_data) || !isset($ctx_data['ctx'])) { - $this->message = ['error', 'Fichier contexte JSON invalide.']; - return; - } - - $status = ($_POST['post_status'] ?? 'publish') === 'pending' ? 'pending' : 'publish'; - $backdate = !empty($_POST['backdate_post']); - - $queue = [ - 'hal_ids' => $hal_ids, - 'spip_map' => $spip_map, - 'status' => $status, - 'backdate' => $backdate, - 'total' => count($hal_ids), - 'done' => 0, - 'spip_ctx' => $ctx_data['ctx'], - 'wp_users_by_hal_id' => $ctx_data['wp_users_by_hal_id'] ?? [], - 'report' => [], - 'last_error' => '', - 'updated_at' => current_time('mysql'), - ]; - update_option(self::CSV_QUEUE_OPTION, $queue, false); - $this->message = ['success', sprintf( - 'CSV chargé : %d publications prêtes. Statut cible : %s. Cliquer "Traiter le prochain batch" pour lancer.', - count($hal_ids), $status - )]; - } - - private function handle_csv_batch(): void { - $queue = get_option(self::CSV_QUEUE_OPTION, null); - if (!$queue) { $this->message = ['error', 'Aucune queue active.']; return; } - - $batch = array_slice($queue['hal_ids'], $queue['done'], self::CSV_BATCH_SIZE); - if (empty($batch)) { - $this->message = ['success', 'Import terminé — tous les batches ont été traités.']; - return; - } - - $docs = $this->api->fetch_by_hal_ids($batch, self::CSV_BATCH_SIZE); - if (is_wp_error($docs)) { - $queue['last_error'] = $docs->get_error_message(); - $queue['updated_at'] = current_time('mysql'); - update_option(self::CSV_QUEUE_OPTION, $queue, false); - $this->message = ['error', 'Erreur HAL API : ' . $docs->get_error_message()]; - return; - } - - // Normalize wp_users_by_hal_id keys to lowercase for the importer - $users_map = []; - foreach ($queue['wp_users_by_hal_id'] as $hid => $u) { - $users_map[strtolower(trim((string) $hid))] = $u; - } - - $importer = new Thalim_HAL_Importer_Logic(); - $batch_imported = 0; - $batch_skipped = 0; - $batch_errors = 0; - - foreach ($batch as $hal_id) { - $spip_id = $queue['spip_map'][$hal_id] ?? ''; - $doc = $docs[$hal_id] ?? null; - $ctx = $queue['spip_ctx'][$hal_id] ?? []; - - if (!$doc) { - $queue['report'][] = [$hal_id, $spip_id, '', 'not_found_in_hal', 'false', 'none', 'HAL API did not return this hal_id']; - $batch_errors++; - continue; - } - - $post_id = $importer->import($doc, $users_map, $queue['status'], (bool) $queue['backdate'], $ctx); - if (is_wp_error($post_id)) { - $code = $post_id->get_error_code(); - $queue['report'][] = [$hal_id, $spip_id, '', $code, 'false', 'none', $post_id->get_error_message()]; - if ($code === 'exists') $batch_skipped++; - else $batch_errors++; - } else { - $source = $importer->last_axes_source; - $has_axe = $source !== 'none' ? 'true' : 'false'; - $queue['report'][] = [$hal_id, $spip_id, (string) $post_id, 'imported', $has_axe, $source, '']; - $batch_imported++; - } - } - - $queue['done'] += count($batch); - $queue['last_error'] = ''; - $queue['updated_at'] = current_time('mysql'); - update_option(self::CSV_QUEUE_OPTION, $queue, false); - - $this->message = ['success', sprintf( - 'Batch traité : %d importé(s), %d déjà importé(s), %d erreur(s). Progression : %d / %d.', - $batch_imported, $batch_skipped, $batch_errors, - $queue['done'], $queue['total'] - )]; - } - - private function handle_csv_cancel(): void { - delete_option(self::CSV_QUEUE_OPTION); - $this->message = ['success', 'Queue CSV annulée.']; - } - - private function handle_csv_download_report(): void { - $queue = get_option(self::CSV_QUEUE_OPTION, null); - if (!$queue || empty($queue['report'])) { - $this->message = ['warning', 'Aucun rapport à télécharger.']; - return; - } - $filename = 'hal-import-report-' . date('Ymd-His') . '.csv'; - header('Content-Type: text/csv; charset=utf-8'); - header('Content-Disposition: attachment; filename="' . $filename . '"'); - $out = fopen('php://output', 'w'); - fputcsv($out, ['hal_id', 'spip_id', 'post_id', 'status', 'has_axe', 'axes_source', 'error']); - foreach ($queue['report'] as $row) fputcsv($out, $row); - fclose($out); - exit; - } - - // ======================================================================== - // End CSV bulk import - // ======================================================================== - private function get_row_class($doc) { if ($doc['is_imported']) return 'hal-status-imported'; if ($doc['has_match']) return 'hal-status-ready'; diff --git a/includes/trait-admin-page-config.php b/includes/trait-admin-page-config.php new file mode 100644 index 0000000..d141c66 --- /dev/null +++ b/includes/trait-admin-page-config.php @@ -0,0 +1,39 @@ + +
+

Configuration

+ + + + +
Structure ID (THALIM)
Document Types
API Endpointapi->get_api_url(10)); ?>
+
+ + + +
+
+ api->test_connection(); + $this->message = is_wp_error($result) + ? ['error', 'API Error: ' . $result->get_error_message()] + : ['success', "Connection OK! Found {$result['total']} publications."]; + } +} diff --git a/includes/trait-admin-page-csv-legacy.php b/includes/trait-admin-page-csv-legacy.php new file mode 100644 index 0000000..b14502f --- /dev/null +++ b/includes/trait-admin-page-csv-legacy.php @@ -0,0 +1,268 @@ + +
+

Import en masse depuis CSV

+

+ Uploader le couple hal-to-import.csv + hal-to-import-context.json + (généré par php scripts/prepare-csv-context.php) pour importer les publications legacy. + Chaque batch traite publications — cliquer plusieurs fois jusqu'à terminaison. +

+ + +
+ + + + + + + + + + + + + + + + + + + +
Fichier CSV
Fichier contexte (JSON) +

Généré par scripts/prepare-csv-context.php.

Statut des posts + + +
Date du post WP + +
+

+
+ + render_csv_progress($queue); ?> + +
+ 0 ? round(100 * $done / $total, 1) : 0; + $report_ct = count($queue['report'] ?? []); + ?> +
+

File d'attente active — statut cible : + — backdate :

+

+ / + publications traitées (%) + — restantes +

+ +

Dernière mise à jour :

+ + + +

+ Erreur dernier batch : +

+ + +
+ + + +
+ + 0): ?> +
+ + + +
+ + +
+ + + +
+
+ message = ['error', 'CSV ou fichier contexte manquant.']; + return; + } + + // Parse CSV -> list of hal_ids + $fh = fopen($_FILES['csv_file']['tmp_name'], 'r'); + if (!$fh) { $this->message = ['error', 'Impossible de lire le CSV.']; return; } + $header = fgetcsv($fh); + $hal_col = array_search('hal_id', $header); + $spip_col = array_search('spip_id', $header); + if ($hal_col === false) { + fclose($fh); + $this->message = ['error', 'Header CSV : colonne hal_id manquante.']; + return; + } + $hal_ids = []; + $spip_map = []; // hal_id => spip_id + while (($row = fgetcsv($fh)) !== false) { + $hid = trim($row[$hal_col] ?? ''); + if ($hid === '') continue; + $hal_ids[] = $hid; + if ($spip_col !== false) $spip_map[$hid] = trim($row[$spip_col] ?? ''); + } + fclose($fh); + $hal_ids = array_values(array_unique($hal_ids)); + + // Parse JSON context + $ctx_raw = file_get_contents($_FILES['ctx_file']['tmp_name']); + $ctx_data = json_decode($ctx_raw, true); + if (!is_array($ctx_data) || !isset($ctx_data['ctx'])) { + $this->message = ['error', 'Fichier contexte JSON invalide.']; + return; + } + + $status = ($_POST['post_status'] ?? 'publish') === 'pending' ? 'pending' : 'publish'; + $backdate = !empty($_POST['backdate_post']); + + $queue = [ + 'hal_ids' => $hal_ids, + 'spip_map' => $spip_map, + 'status' => $status, + 'backdate' => $backdate, + 'total' => count($hal_ids), + 'done' => 0, + 'spip_ctx' => $ctx_data['ctx'], + 'wp_users_by_hal_id' => $ctx_data['wp_users_by_hal_id'] ?? [], + 'report' => [], + 'last_error' => '', + 'updated_at' => current_time('mysql'), + ]; + update_option(self::CSV_QUEUE_OPTION, $queue, false); + $this->message = ['success', sprintf( + 'CSV chargé : %d publications prêtes. Statut cible : %s. Cliquer "Traiter le prochain batch" pour lancer.', + count($hal_ids), $status + )]; + } + + private function handle_csv_batch(): void { + $queue = get_option(self::CSV_QUEUE_OPTION, null); + if (!$queue) { $this->message = ['error', 'Aucune queue active.']; return; } + + $batch = array_slice($queue['hal_ids'], $queue['done'], self::CSV_BATCH_SIZE); + if (empty($batch)) { + $this->message = ['success', 'Import terminé — tous les batches ont été traités.']; + return; + } + + $docs = $this->api->fetch_by_hal_ids($batch, self::CSV_BATCH_SIZE); + if (is_wp_error($docs)) { + $queue['last_error'] = $docs->get_error_message(); + $queue['updated_at'] = current_time('mysql'); + update_option(self::CSV_QUEUE_OPTION, $queue, false); + $this->message = ['error', 'Erreur HAL API : ' . $docs->get_error_message()]; + return; + } + + // Normalize wp_users_by_hal_id keys to lowercase for the importer + $users_map = []; + foreach ($queue['wp_users_by_hal_id'] as $hid => $u) { + $users_map[strtolower(trim((string) $hid))] = $u; + } + + $importer = new Thalim_HAL_Importer_Logic(); + $batch_imported = 0; + $batch_skipped = 0; + $batch_errors = 0; + + foreach ($batch as $hal_id) { + $spip_id = $queue['spip_map'][$hal_id] ?? ''; + $doc = $docs[$hal_id] ?? null; + $ctx = $queue['spip_ctx'][$hal_id] ?? []; + + if (!$doc) { + $queue['report'][] = [$hal_id, $spip_id, '', 'not_found_in_hal', 'false', 'none', 'HAL API did not return this hal_id']; + $batch_errors++; + continue; + } + + $post_id = $importer->import($doc, $users_map, $queue['status'], (bool) $queue['backdate'], $ctx); + if (is_wp_error($post_id)) { + $code = $post_id->get_error_code(); + $queue['report'][] = [$hal_id, $spip_id, '', $code, 'false', 'none', $post_id->get_error_message()]; + if ($code === 'exists') $batch_skipped++; + else $batch_errors++; + } else { + $source = $importer->last_axes_source; + $has_axe = $source !== 'none' ? 'true' : 'false'; + $queue['report'][] = [$hal_id, $spip_id, (string) $post_id, 'imported', $has_axe, $source, '']; + $batch_imported++; + } + } + + $queue['done'] += count($batch); + $queue['last_error'] = ''; + $queue['updated_at'] = current_time('mysql'); + update_option(self::CSV_QUEUE_OPTION, $queue, false); + + $this->message = ['success', sprintf( + 'Batch traité : %d importé(s), %d déjà importé(s), %d erreur(s). Progression : %d / %d.', + $batch_imported, $batch_skipped, $batch_errors, + $queue['done'], $queue['total'] + )]; + } + + private function handle_csv_cancel(): void { + delete_option(self::CSV_QUEUE_OPTION); + $this->message = ['success', 'Queue CSV annulée.']; + } + + private function handle_csv_download_report(): void { + $queue = get_option(self::CSV_QUEUE_OPTION, null); + if (!$queue || empty($queue['report'])) { + $this->message = ['warning', 'Aucun rapport à télécharger.']; + return; + } + $filename = 'hal-import-report-' . date('Ymd-His') . '.csv'; + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + $out = fopen('php://output', 'w'); + fputcsv($out, ['hal_id', 'spip_id', 'post_id', 'status', 'has_axe', 'axes_source', 'error']); + foreach ($queue['report'] as $row) fputcsv($out, $row); + fclose($out); + exit; + } +} diff --git a/thalim-hal-importer.php b/thalim-hal-importer.php index d7b7613..a64c7d8 100644 --- a/thalim-hal-importer.php +++ b/thalim-hal-importer.php @@ -46,6 +46,9 @@ class Thalim_HAL_Importer { private function load_dependencies() { require_once THALIM_HAL_PLUGIN_DIR . 'includes/class-hal-api.php'; + // Traits must be loaded before the class that `use`s them. + require_once THALIM_HAL_PLUGIN_DIR . 'includes/trait-admin-page-config.php'; + require_once THALIM_HAL_PLUGIN_DIR . 'includes/trait-admin-page-csv-legacy.php'; require_once THALIM_HAL_PLUGIN_DIR . 'includes/class-admin-page.php'; require_once THALIM_HAL_PLUGIN_DIR . 'includes/class-importer.php'; }