Import unitaire, statut publié/pending et validation des idHAL
This commit is contained in:
@@ -81,6 +81,10 @@ class Thalim_HAL_Admin_Page {
|
||||
$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();
|
||||
@@ -116,6 +120,7 @@ class Thalim_HAL_Admin_Page {
|
||||
$imported = 0;
|
||||
$skipped = 0;
|
||||
$errors = [];
|
||||
$cache_updates = []; // hal_id => ['id', 'status'] for cache mutation
|
||||
|
||||
foreach ($raw_docs as $doc) {
|
||||
$hal_id = $doc['halId_s'] ?? '';
|
||||
@@ -132,9 +137,12 @@ class Thalim_HAL_Admin_Page {
|
||||
$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);
|
||||
@@ -142,6 +150,61 @@ class Thalim_HAL_Admin_Page {
|
||||
$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() {
|
||||
?>
|
||||
<style>
|
||||
@@ -231,7 +294,11 @@ class Thalim_HAL_Admin_Page {
|
||||
<?php else: ?>
|
||||
<?php $this->render_wp_users_debug(); ?>
|
||||
<?php $this->render_summary($preview['stats']); ?>
|
||||
<?php $this->render_preview_table($preview['docs']); ?>
|
||||
<?php $this->render_preview_table($preview['docs'], [
|
||||
'date_from' => $date_from,
|
||||
'date_to' => $date_to,
|
||||
'author_hal_id' => $author_hal_id,
|
||||
]); ?>
|
||||
<?php $this->render_legend(); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -261,7 +328,7 @@ class Thalim_HAL_Admin_Page {
|
||||
<?php
|
||||
}
|
||||
|
||||
private function render_preview_table($docs) {
|
||||
private function render_preview_table($docs, $filters = []) {
|
||||
if (empty($docs)) {
|
||||
echo '<p>No publications found.</p>';
|
||||
return;
|
||||
@@ -279,12 +346,13 @@ class Thalim_HAL_Admin_Page {
|
||||
<th>Date</th>
|
||||
<th>Membres THALIM</th>
|
||||
<th>Lien HAL</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($docs as $doc): ?>
|
||||
<tr class="<?php echo esc_attr($this->get_row_class($doc)); ?>">
|
||||
<td><?php echo $this->get_status_icon($doc); ?></td>
|
||||
<td><?php $this->render_status_cell($doc); ?></td>
|
||||
<td><code style="font-size:11px"><?php echo esc_html($doc['hal_id']); ?></code></td>
|
||||
<td>
|
||||
<strong><?php echo esc_html($doc['title']); ?></strong>
|
||||
@@ -325,6 +393,19 @@ class Thalim_HAL_Admin_Page {
|
||||
<td>
|
||||
<a href="<?php echo esc_url($doc['url']); ?>" target="_blank" class="button button-small">Voir sur HAL</a>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($doc['has_match'] && !$doc['is_imported']): ?>
|
||||
<form method="post" style="margin:0">
|
||||
<?php wp_nonce_field('thalim_hal_action'); ?>
|
||||
<input type="hidden" name="thalim_hal_action" value="import_single">
|
||||
<input type="hidden" name="hal_id" value="<?php echo esc_attr($doc['hal_id']); ?>">
|
||||
<input type="hidden" name="hal_date_from" value="<?php echo esc_attr($filters['date_from'] ?? ''); ?>">
|
||||
<input type="hidden" name="hal_date_to" value="<?php echo esc_attr($filters['date_to'] ?? ''); ?>">
|
||||
<input type="hidden" name="hal_author_id" value="<?php echo esc_attr($filters['author_hal_id'] ?? ''); ?>">
|
||||
<button class="button button-primary button-small">Importer</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
@@ -343,24 +424,57 @@ class Thalim_HAL_Admin_Page {
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate WP users' HAL IDs against HAL's author referential.
|
||||
* Cached 24h to avoid hitting the API on every page load.
|
||||
* Returns map [normalized_hal_id => 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 '<div class="notice notice-warning"><p>Aucun utilisateur WordPress n\'a le champ <code>identifiant_hal</code> renseigné.</p></div>';
|
||||
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));
|
||||
?>
|
||||
<details style="margin-bottom:15px;background:#f0f6fc;padding:10px;border-radius:4px;border-left:4px solid #0073aa">
|
||||
<summary style="cursor:pointer;font-weight:bold">
|
||||
Utilisateurs WordPress avec identifiant HAL (<?php echo count($this->wp_users_by_hal_id); ?> utilisateurs) — Cliquer pour déplier
|
||||
Utilisateurs WordPress avec identifiant HAL (<?php echo count($this->wp_users_by_hal_id); ?> utilisateurs<?php
|
||||
if ($invalid_count > 0) echo ', <span style="color:#dc3545">' . $invalid_count . ' invalide(s)</span>';
|
||||
?>) — Cliquer pour déplier
|
||||
</summary>
|
||||
<table class="widefat" style="margin-top:10px;font-size:12px">
|
||||
<thead><tr><th>Utilisateur</th><th>Identifiant HAL</th><th>Debug (brut)</th><th>Modifier</th></tr></thead>
|
||||
<thead><tr><th>Utilisateur</th><th>Identifiant HAL</th><th>Validité HAL</th><th>Debug (brut)</th><th>Modifier</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->wp_users_by_hal_id as $hal_id => $user): ?>
|
||||
<tr>
|
||||
<?php foreach ($this->wp_users_by_hal_id as $hal_id => $user):
|
||||
$valid = $validity[$hal_id] ?? null;
|
||||
$row_style = $valid === false ? 'background:#f8d7da' : '';
|
||||
?>
|
||||
<tr style="<?php echo $row_style; ?>">
|
||||
<td><?php echo esc_html($user['name']); ?> <small style="color:#999">(ID : <?php echo esc_html($user['id']); ?>)</small></td>
|
||||
<td><code><?php echo esc_html($hal_id); ?></code></td>
|
||||
<td>
|
||||
<?php if ($valid === true): ?>
|
||||
<span style="color:#28a745" title="Trouvé dans le référentiel auteur HAL">✓ valide</span>
|
||||
<?php elseif ($valid === false): ?>
|
||||
<span style="color:#dc3545;font-weight:600" title="Aucun auteur HAL ne correspond à cet idHAL">✗ invalide</span>
|
||||
<?php else: ?>
|
||||
<span style="color:#999;font-style:italic" title="Erreur API ou validation indisponible">— inconnu</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><code style="background:#ffe;font-size:10px">"<?php echo esc_html($hal_id); ?>" (<?php echo strlen($hal_id); ?> car.)</code></td>
|
||||
<td><a href="<?php echo esc_url(get_edit_user_link($user['id'])); ?>" target="_blank" class="button button-small">Modifier</a></td>
|
||||
</tr>
|
||||
@@ -371,8 +485,38 @@ class Thalim_HAL_Admin_Page {
|
||||
<?php
|
||||
}
|
||||
|
||||
private function get_preview_cache_key($date_from, $date_to, $author_hal_id) {
|
||||
return 'thalim_hal_preview_' . md5($date_from . '|' . $date_to . '|' . $author_hal_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mute the cached preview in place to reflect newly-imported posts —
|
||||
* avoids re-hitting the HAL API just to refresh statuses after an import.
|
||||
*
|
||||
* @param array $imports Map hal_id => ['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 = 'thalim_hal_preview_' . md5($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;
|
||||
|
||||
@@ -396,7 +540,8 @@ class Thalim_HAL_Admin_Page {
|
||||
|
||||
foreach ($result['response']['docs'] ?? [] as $doc) {
|
||||
$hal_id = $doc['halId_s'] ?? '';
|
||||
$is_imported = $importer->is_imported($hal_id);
|
||||
$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);
|
||||
@@ -411,18 +556,20 @@ class Thalim_HAL_Admin_Page {
|
||||
}
|
||||
|
||||
$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,
|
||||
'matched_users' => $matched_users,
|
||||
'has_match' => $has_match,
|
||||
'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
|
||||
}
|
||||
@@ -478,9 +625,24 @@ class Thalim_HAL_Admin_Page {
|
||||
return 'hal-status-blocked';
|
||||
}
|
||||
|
||||
private function get_status_icon($doc) {
|
||||
if ($doc['is_imported']) return '<span title="Already imported" style="color:#28a745;font-size:18px">✓</span>';
|
||||
if ($doc['has_match']) return '<span title="Ready to import" style="color:#ffc107;font-size:18px">★</span>';
|
||||
return '<span title="No matched user" style="color:#dc3545;font-size:18px">✗</span>';
|
||||
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);
|
||||
?>
|
||||
<span title="Importé" style="color:#28a745;font-size:18px">✓</span>
|
||||
<div><small><?php echo esc_html($label); ?></small></div>
|
||||
<?php if ($is_pending && !empty($doc['imported_post_id'])): ?>
|
||||
<a href="<?php echo esc_url(get_edit_post_link($doc['imported_post_id'])); ?>" class="button button-small" style="margin-top:3px">Modifier</a>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
if ($doc['has_match']) {
|
||||
echo '<span title="Ready to import" style="color:#ffc107;font-size:18px">★</span>';
|
||||
return;
|
||||
}
|
||||
echo '<span title="No matched user" style="color:#dc3545;font-size:18px">✗</span>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,49 @@ class Thalim_HAL_API {
|
||||
return $docs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check which HAL IDs exist in HAL's author referential (ref/author).
|
||||
* Returns map [normalized_hal_id => bool|null] — null when the API errored
|
||||
* (treated as "unknown", not "invalid").
|
||||
*/
|
||||
public function validate_hal_ids(array $hal_ids) {
|
||||
$result = [];
|
||||
$clean = [];
|
||||
foreach ($hal_ids as $id) {
|
||||
$id = trim((string) $id);
|
||||
if ($id !== '') $clean[strtolower($id)] = $id;
|
||||
}
|
||||
if (empty($clean)) return $result;
|
||||
|
||||
// Default all to false; flip to true when found
|
||||
foreach ($clean as $norm => $_) $result[$norm] = false;
|
||||
|
||||
$endpoint = 'https://api.archives-ouvertes.fr/ref/author/';
|
||||
$chunks = array_chunk(array_values($clean), 100);
|
||||
foreach ($chunks as $chunk) {
|
||||
// Solr OR with quoted values to tolerate dashes/dots in slugs
|
||||
$quoted = array_map(fn($id) => '"' . str_replace('"', '', $id) . '"', $chunk);
|
||||
$params = [
|
||||
'q=' . urlencode('idHal_s:(' . implode(' OR ', $quoted) . ')'),
|
||||
'fl=' . urlencode('idHal_s'),
|
||||
'rows=' . count($chunk),
|
||||
'wt=json',
|
||||
];
|
||||
$data = $this->request($endpoint . '?' . implode('&', $params));
|
||||
if (is_wp_error($data)) {
|
||||
foreach ($chunk as $id) $result[strtolower($id)] = null;
|
||||
continue;
|
||||
}
|
||||
foreach ($data['response']['docs'] ?? [] as $doc) {
|
||||
if (!empty($doc['idHal_s'])) {
|
||||
$result[strtolower($doc['idHal_s'])] = true;
|
||||
}
|
||||
}
|
||||
if (count($chunks) > 1) usleep(250000);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test API connection
|
||||
*/
|
||||
|
||||
@@ -50,6 +50,22 @@ class Thalim_HAL_Importer_Logic {
|
||||
)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ['id' => int, 'status' => string] for the post matching this hal_id, or null.
|
||||
*/
|
||||
public function get_imported_post($hal_id) {
|
||||
if (empty($hal_id)) return null;
|
||||
global $wpdb;
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT p.ID, p.post_status FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
|
||||
WHERE pm.meta_key = 'hal_id' AND pm.meta_value = %s
|
||||
LIMIT 1",
|
||||
$hal_id
|
||||
));
|
||||
return $row ? ['id' => (int) $row->ID, 'status' => $row->post_status] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category ID for HAL doc type
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user