['id' => int, 'name' => string] // Document type labels private const DOC_TYPE_LABELS = [ 'ART' => 'Article', 'COUV' => "Chapitre d'ouvrage", 'OUV' => 'Ouvrage', 'COMM' => 'Communication', 'ISSUE' => 'Direction de numéro', 'PROCEEDINGS' => 'Colloque', 'THESE' => 'Thèse', 'HDR' => 'HDR', 'SON' => 'Son', 'VIDEO' => 'Vidéo', ]; public function __construct() { $this->api = new Thalim_HAL_API(); } public function render() { if (!current_user_can('edit_others_posts')) { wp_die('Unauthorized'); } $this->handle_actions(); echo '

THALIM HAL Importer

'; $this->render_styles(); $this->render_message(); if (self::CONFIG_PANEL_ENABLED) { $this->render_config(); } $this->render_preview(); if (self::CSV_IMPORT_ENABLED) { $this->render_csv_import(); } echo '
'; } private function handle_actions() { if (!isset($_POST['thalim_hal_action'])) return; if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'thalim_hal_action')) { $this->message = ['error', 'Security check failed.']; return; } $action = sanitize_text_field($_POST['thalim_hal_action']); if (self::CONFIG_PANEL_ENABLED && $action === 'test_api') { $this->handle_test_api(); } if ($action === 'refresh') { // Clear all preview transients (they are keyed by date range hash) global $wpdb; $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_thalim_hal_preview_%'"); $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_thalim_hal_preview_%'"); $this->message = ['success', 'Preview data refreshed from HAL API.']; } if ($action === 'import_pending') { $this->handle_import(); } if ($action === 'import_single') { $this->handle_import_single(); } 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(); } } /** * Handle bulk import of ready publications as pending posts. * Uses cached raw HAL docs to avoid a second outbound API call. */ private function handle_import() { $date_from = sanitize_text_field($_POST['hal_date_from'] ?? ''); $date_to = sanitize_text_field($_POST['hal_date_to'] ?? ''); $author_hal_id = sanitize_text_field($_POST['hal_author_id'] ?? ''); // Reuse the cached preview data — raw_docs are stored alongside processed docs $preview = $this->get_preview_data($date_from, $date_to, $author_hal_id); if (is_wp_error($preview)) { $this->message = ['error', 'API Error: ' . $preview->get_error_message()]; return; } $raw_docs = $preview['raw_docs'] ?? []; if (empty($raw_docs)) { $this->message = ['warning', 'Aucune publication dans le cache. Utilisez Filtrer pour charger les données d\'abord.']; return; } $this->load_wp_users_hal_ids(); $importer = new Thalim_HAL_Importer_Logic(); $imported = 0; $skipped = 0; $errors = []; $cache_updates = []; // hal_id => ['id', 'status'] for cache mutation foreach ($raw_docs as $doc) { $hal_id = $doc['halId_s'] ?? ''; $author_hal_ids = $doc['authIdHal_s'] ?? []; $matched_users = $this->match_authors_to_users($author_hal_ids); if (empty($matched_users) || $importer->is_imported($hal_id)) { $skipped++; continue; } $post_id = $importer->import($doc, $this->wp_users_by_hal_id); if (is_wp_error($post_id)) { $errors[] = $hal_id . ': ' . $post_id->get_error_message(); } else { $imported++; $cache_updates[$hal_id] = ['id' => $post_id, 'status' => 'pending']; } } $this->update_preview_cache_after_import($date_from, $date_to, $author_hal_id, $cache_updates); $msg = sprintf('%d publication(s) importée(s) en statut "En attente".', $imported); if ($skipped) $msg .= sprintf(' %d ignorée(s) (déjà importées ou sans membre THALIM correspondant).', $skipped); if (!empty($errors)) $msg .= ' Erreurs : ' . implode('; ', $errors); $this->message = [empty($errors) ? 'success' : 'warning', $msg]; } /** * Import a single ready publication. hal_id + filter values are POSTed * from the per-row form so the cache lookup hits the right preview entry. */ private function handle_import_single() { $hal_id = sanitize_text_field($_POST['hal_id'] ?? ''); $date_from = sanitize_text_field($_POST['hal_date_from'] ?? ''); $date_to = sanitize_text_field($_POST['hal_date_to'] ?? ''); $author_hal_id = sanitize_text_field($_POST['hal_author_id'] ?? ''); if (!$hal_id) { $this->message = ['error', 'hal_id manquant.']; return; } $preview = $this->get_preview_data($date_from, $date_to, $author_hal_id); if (is_wp_error($preview)) { $this->message = ['error', 'API Error: ' . $preview->get_error_message()]; return; } $doc = null; foreach ($preview['raw_docs'] ?? [] as $d) { if (($d['halId_s'] ?? '') === $hal_id) { $doc = $d; break; } } if (!$doc) { $this->message = ['warning', "Publication $hal_id introuvable dans le tableau (cache expiré ?). Rafraîchir et réessayer."]; return; } $importer = new Thalim_HAL_Importer_Logic(); if ($importer->is_imported($hal_id)) { $this->message = ['warning', "Publication $hal_id déjà importée."]; return; } $this->load_wp_users_hal_ids(); if (empty($this->match_authors_to_users($doc['authIdHal_s'] ?? []))) { $this->message = ['error', "Aucun membre THALIM identifié pour $hal_id."]; return; } $post_id = $importer->import($doc, $this->wp_users_by_hal_id); if (is_wp_error($post_id)) { $this->message = ['error', "Erreur import : " . $post_id->get_error_message()]; return; } $this->update_preview_cache_after_import($date_from, $date_to, $author_hal_id, [ $hal_id => ['id' => $post_id, 'status' => 'pending'], ]); $this->message = ['success', sprintf('Publication %s importée (post #%d, en attente).', $hal_id, $post_id)]; } private function render_styles() { ?> message) return; printf('

%s

', esc_attr($this->message[0]), esc_html($this->message[1])); } private function render_preview() { // Read filters from POST (after submit) or GET (page reload with state) $date_from = sanitize_text_field($_POST['hal_date_from'] ?? $_GET['hal_date_from'] ?? ''); $date_to = sanitize_text_field($_POST['hal_date_to'] ?? $_GET['hal_date_to'] ?? ''); $author_hal_id = sanitize_text_field($_POST['hal_author_id'] ?? $_GET['hal_author_id'] ?? ''); // Users must be loaded before rendering the dropdown $this->load_wp_users_hal_ids(); $preview = $this->get_preview_data($date_from, $date_to, $author_hal_id); $ready_count = is_wp_error($preview) ? 0 : $preview['stats']['ready']; ?>

Import Preview

Mis en cache 5 min

get_error_message()); ?>

render_wp_users_debug(); ?> render_summary($preview['stats']); ?> render_preview_table($preview['docs'], [ 'date_from' => $date_from, 'date_to' => $date_to, 'author_hal_id' => $author_hal_id, ]); ?> render_legend(); ?>
Total in HAL
Already Imported
Ready to Import
No Matched User
No publications found.

'; return; } ?>
Statut HAL ID Titre Type Auteurs IDs HAL auteurs Date Membres THALIM Lien HAL Action
render_status_cell($doc); ?>
wp_users_by_hal_id[$normalized]); ?> aucun Aucun Voir sur HAL
Légende : ✓ Importé ★ Prêt Membre THALIM identifié ✗ Bloqué Aucun membre THALIM ne correspond aux IDs auteurs HAL
true|false|null] (null = API error). */ private function get_hal_ids_validity(array $hal_ids) { sort($hal_ids); $cache_key = 'thalim_hal_ids_validity_' . md5(implode('|', $hal_ids)); $cached = get_transient($cache_key); if ($cached !== false) return $cached; $result = $this->api->validate_hal_ids($hal_ids); set_transient($cache_key, $result, DAY_IN_SECONDS); return $result; } private function render_wp_users_debug() { $this->load_wp_users_hal_ids(); if (empty($this->wp_users_by_hal_id)) { echo '

Aucun utilisateur WordPress n\'a le champ identifiant_hal renseigné.

'; return; } $validity = $this->get_hal_ids_validity(array_keys($this->wp_users_by_hal_id)); $invalid_count = count(array_filter($validity, fn($v) => $v === false)); ?>
Utilisateurs WordPress avec identifiant HAL (wp_users_by_hal_id); ?> utilisateurs 0) echo ', ' . $invalid_count . ' invalide(s)'; ?>) — Cliquer pour déplier wp_users_by_hal_id as $hal_id => $user): $valid = $validity[$hal_id] ?? null; $row_style = $valid === false ? 'background:#f8d7da' : ''; ?>
UtilisateurIdentifiant HALValidité HALDebug (brut)Modifier
(ID : ) ✓ valide ✗ invalide — inconnu "" ( car.) Modifier
['id' => int, 'status' => string] */ private function update_preview_cache_after_import($date_from, $date_to, $author_hal_id, array $imports) { if (empty($imports)) return; $cache_key = $this->get_preview_cache_key($date_from, $date_to, $author_hal_id); $preview = get_transient($cache_key); if (!$preview) return; foreach ($preview['docs'] as &$d) { if (isset($imports[$d['hal_id']]) && empty($d['is_imported'])) { $d['is_imported'] = true; $d['imported_post_id'] = $imports[$d['hal_id']]['id']; $d['imported_post_status'] = $imports[$d['hal_id']]['status']; $preview['stats']['ready']--; $preview['stats']['imported']++; } } unset($d); set_transient($cache_key, $preview, 300); } private function get_preview_data($date_from = '', $date_to = '', $author_hal_id = '') { $cache_key = $this->get_preview_cache_key($date_from, $date_to, $author_hal_id); $cached = get_transient($cache_key); if ($cached !== false) return $cached; $rows = ($date_from || $date_to || $author_hal_id) ? 200 : 50; $result = $this->api->fetch_publications($rows, 0, 'producedDate_tdate desc', $date_from, $date_to, $author_hal_id); if (is_wp_error($result)) return $result; $importer = new Thalim_HAL_Importer_Logic(); $this->load_wp_users_hal_ids(); $preview = [ 'stats' => [ 'total' => $result['response']['numFound'] ?? 0, 'imported' => 0, 'ready' => 0, 'blocked' => 0 ], 'docs' => [], 'raw_docs' => [], // Raw HAL docs kept for import, avoids a second API call ]; foreach ($result['response']['docs'] ?? [] as $doc) { $hal_id = $doc['halId_s'] ?? ''; $imported_post = $importer->get_imported_post($hal_id); $is_imported = $imported_post !== null; $author_hal_ids = $doc['authIdHal_s'] ?? []; $matched_users = $this->match_authors_to_users($author_hal_ids); $has_match = !empty($matched_users); // Update stats if ($is_imported) { $preview['stats']['imported']++; } elseif ($has_match) { $preview['stats']['ready']++; } else { $preview['stats']['blocked']++; } $preview['docs'][] = [ 'hal_id' => $hal_id, 'title' => $doc['title_s'][0] ?? 'N/A', 'type' => $doc['docType_s'] ?? '', 'authors' => $doc['authFullName_s'] ?? [], 'author_hal_ids' => $author_hal_ids, 'publication_date' => $doc['publicationDate_s'] ?? '', 'produced_date' => $doc['submittedDate_s'] ?? '', 'journal' => $doc['journalTitle_s'] ?? $doc['bookTitle_s'] ?? '', 'url' => $doc['uri_s'] ?? '', 'is_imported' => $is_imported, 'imported_post_id' => $imported_post['id'] ?? null, 'imported_post_status' => $imported_post['status'] ?? null, 'matched_users' => $matched_users, 'has_match' => $has_match, ]; $preview['raw_docs'][] = $doc; // Full HAL doc kept for import } set_transient($cache_key, $preview, 300); return $preview; } /** * Load all WordPress users with HAL IDs into cache. * Stores: normalized_hal_id => ['id' => int, 'name' => string] */ private function load_wp_users_hal_ids() { if ($this->wp_users_by_hal_id !== null) return; $this->wp_users_by_hal_id = []; $users = get_users([ 'meta_key' => 'identifiant_hal', 'meta_compare' => 'EXISTS' ]); foreach ($users as $user) { $hal_id = get_user_meta($user->ID, 'identifiant_hal', true); if (!empty($hal_id)) { $normalized = strtolower(trim($hal_id)); $this->wp_users_by_hal_id[$normalized] = [ 'id' => $user->ID, 'name' => $user->display_name, 'hal_id' => trim($hal_id), // original value for API filter ]; } } } /** * Match HAL author IDs to WordPress users. * Returns array of display names (for preview display). */ private function match_authors_to_users($author_hal_ids) { $matched = []; foreach ($author_hal_ids as $hal_id) { $normalized = strtolower(trim($hal_id)); if (isset($this->wp_users_by_hal_id[$normalized])) { $matched[] = $this->wp_users_by_hal_id[$normalized]['name']; } } return $matched; } private function get_row_class($doc) { if ($doc['is_imported']) return 'hal-status-imported'; if ($doc['has_match']) return 'hal-status-ready'; return 'hal-status-blocked'; } private function render_status_cell($doc) { if ($doc['is_imported']) { $status = $doc['imported_post_status'] ?? ''; $is_pending = $status === 'pending'; $label = $is_pending ? 'En attente' : ($status === 'publish' ? 'Publié' : $status); ?>
Modifier ★'; return; } echo ''; } }