770 lines
35 KiB
PHP
770 lines
35 KiB
PHP
<?php
|
|
/**
|
|
* Admin Page Class - Handles the admin interface
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
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]
|
|
private $contributor_idhal = null; // Set by check_contributor_idhal_gate() — forced filter in contributor mode
|
|
|
|
// 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_posts')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$is_contributor = $this->is_contributor_mode();
|
|
|
|
// Contributor precondition: must have a valid idHAL on their profile
|
|
if ($is_contributor) {
|
|
$gate = $this->check_contributor_idhal_gate();
|
|
if ($gate !== true) {
|
|
echo '<div class="wrap"><h1>Importer depuis HAL</h1>';
|
|
echo $gate;
|
|
echo '</div>';
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->handle_actions();
|
|
|
|
$title = $is_contributor ? 'Importer mes publications HAL' : 'THALIM HAL Importer';
|
|
echo '<div class="wrap"><h1>' . esc_html($title) . '</h1>';
|
|
$this->render_styles();
|
|
$this->render_message();
|
|
if ($is_contributor) {
|
|
$this->render_contributor_notice();
|
|
}
|
|
if (self::CONFIG_PANEL_ENABLED && !$is_contributor) {
|
|
$this->render_config();
|
|
}
|
|
$this->render_preview();
|
|
if (self::CSV_IMPORT_ENABLED && !$is_contributor) {
|
|
$this->render_csv_import();
|
|
}
|
|
echo '</div>';
|
|
}
|
|
|
|
private function is_contributor_mode(): bool {
|
|
return current_user_can('edit_posts') && !current_user_can('edit_others_posts');
|
|
}
|
|
|
|
/**
|
|
* Returns true if the current contributor can use the page, or an HTML
|
|
* notice string explaining why not. Also caches the resolved idHAL into
|
|
* $this->contributor_idhal for downstream forcing.
|
|
*/
|
|
private function check_contributor_idhal_gate() {
|
|
$user_id = get_current_user_id();
|
|
$idhal = trim((string) get_user_meta($user_id, 'identifiant_hal', true));
|
|
$profile_url = get_edit_user_link($user_id);
|
|
|
|
if ($idhal === '') {
|
|
return sprintf(
|
|
'<div class="notice notice-error"><p><strong>Votre identifiant HAL n\'est pas renseigné.</strong></p>'
|
|
. '<p>Pour utiliser cet outil, ajoutez votre <code>identifiant_hal</code> (idHAL) à votre profil.</p>'
|
|
. '<p><a href="%s" class="button button-primary">Modifier mon profil</a></p></div>',
|
|
esc_url($profile_url)
|
|
);
|
|
}
|
|
|
|
$validity = $this->get_hal_ids_validity([strtolower($idhal)]);
|
|
$is_valid = $validity[strtolower($idhal)] ?? null;
|
|
|
|
if ($is_valid === false) {
|
|
return sprintf(
|
|
'<div class="notice notice-error"><p><strong>Votre identifiant HAL (<code>%s</code>) est introuvable dans le référentiel HAL.</strong></p>'
|
|
. '<p>Vérifiez l\'orthographe sur votre profil. La validation est mise en cache 24h.</p>'
|
|
. '<p><a href="%s" class="button button-primary">Modifier mon profil</a></p></div>',
|
|
esc_html($idhal), esc_url($profile_url)
|
|
);
|
|
}
|
|
|
|
// null = API error → graceful degradation, on laisse passer
|
|
$this->contributor_idhal = $idhal;
|
|
return true;
|
|
}
|
|
|
|
private function render_contributor_notice() {
|
|
?>
|
|
<div class="notice notice-info" style="border-left-color:#2196f3">
|
|
<p><strong>À compléter avant publication :</strong></p>
|
|
<ul style="list-style:disc;padding-left:25px;margin:5px 0">
|
|
<li><strong>Axe(s) thématique(s)</strong> — obligatoire pour la publication.</li>
|
|
<li>Autres membres THALIM co-auteurs (champ <em>autre_membres</em>) si applicable.</li>
|
|
<li>Image à la une (illustration).</li>
|
|
<li>Programme(s) de recherche associé(s) si pertinent.</li>
|
|
</ul>
|
|
<p><small>Les publications sont importées en statut <strong>En attente</strong>. Un éditeur les validera après votre complément.</small></p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
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'] ?? '');
|
|
|
|
$is_contributor = $this->is_contributor_mode();
|
|
if ($is_contributor) {
|
|
$author_hal_id = $this->contributor_idhal; // force, ignore POST
|
|
}
|
|
$force_author = $is_contributor ? get_current_user_id() : null;
|
|
|
|
// 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, 'pending', false, [], $force_author);
|
|
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'] ?? '');
|
|
|
|
$is_contributor = $this->is_contributor_mode();
|
|
if ($is_contributor) {
|
|
$author_hal_id = $this->contributor_idhal; // force, ignore POST
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Critical security check: in contributor mode, the requested hal_id MUST
|
|
// be a publication where the contributor is an author. Defends against a
|
|
// forged POST that tries to import another member's publication.
|
|
if ($is_contributor) {
|
|
$doc_authors = array_map('strtolower', array_map('trim', $doc['authIdHal_s'] ?? []));
|
|
if (!in_array(strtolower($this->contributor_idhal), $doc_authors, true)) {
|
|
$this->message = ['error', "Vous n'êtes pas auteur de la publication $hal_id."];
|
|
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;
|
|
}
|
|
|
|
$force_author = $is_contributor ? get_current_user_id() : null;
|
|
$post_id = $importer->import($doc, $this->wp_users_by_hal_id, 'pending', false, [], $force_author);
|
|
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>
|
|
.hal-status-imported { background-color: #d4edda !important; }
|
|
.hal-status-ready { background-color: #fff3cd !important; }
|
|
.hal-status-blocked { background-color: #f8d7da !important; }
|
|
.hal-summary { display: flex; gap: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; margin-bottom: 20px; }
|
|
.hal-summary-item { padding: 10px 15px; border-radius: 4px; text-align: center; }
|
|
.hal-summary-item.total { background: #e3f2fd; border-left: 4px solid #2196f3; }
|
|
.hal-summary-item.imported { background: #d4edda; border-left: 4px solid #28a745; }
|
|
.hal-summary-item.ready { background: #fff3cd; border-left: 4px solid #ffc107; }
|
|
.hal-summary-item.blocked { background: #f8d7da; border-left: 4px solid #dc3545; }
|
|
.hal-summary-item strong { display: block; font-size: 24px; }
|
|
.hal-type-badge { padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: 500; }
|
|
.hal-type-ART, .hal-type-ISSUE { background: #e3f2fd; color: #1565c0; }
|
|
.hal-type-COUV, .hal-type-OUV { background: #f3e5f5; color: #7b1fa2; }
|
|
.hal-type-PROCEEDINGS { background: #e8f5e9; color: #2e7d32; }
|
|
.hal-type-THESE, .hal-type-HDR { background: #fff3e0; color: #e65100; }
|
|
.hal-type-SON, .hal-type-VIDEO { background: #fce4ec; color: #c62828; }
|
|
.hal-users { font-size: 11px; color: #666; }
|
|
.hal-users-matched { color: #28a745; font-weight: 500; }
|
|
.hal-users-none { color: #dc3545; font-style: italic; }
|
|
.hal-preview-table th { white-space: nowrap; }
|
|
.hal-preview-table td { vertical-align: top; }
|
|
</style>
|
|
<?php
|
|
}
|
|
|
|
private function render_message() {
|
|
if (!$this->message) return;
|
|
printf('<div class="notice notice-%s is-dismissible"><p>%s</p></div>',
|
|
esc_attr($this->message[0]), esc_html($this->message[1]));
|
|
}
|
|
|
|
private function render_preview() {
|
|
$is_contributor = $this->is_contributor_mode();
|
|
|
|
// 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'] ?? '');
|
|
|
|
// Contributor mode: ignore any POSTed author filter, force their own idHAL.
|
|
if ($is_contributor) {
|
|
$author_hal_id = $this->contributor_idhal;
|
|
}
|
|
|
|
// Users must be loaded before rendering the dropdown (admins/editors only)
|
|
$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_label = $is_contributor
|
|
? sprintf('Importer mes %d publication(s) (En attente)', $ready_count)
|
|
: sprintf('Importer %d publication(s) (En attente)', $ready_count);
|
|
?>
|
|
<div class="card" style="max-width:100%;margin-bottom:20px">
|
|
<h2><?php echo $is_contributor ? 'Mes publications HAL' : 'Import Preview'; ?></h2>
|
|
|
|
<form method="post" style="margin-bottom:20px;display:flex;align-items:center;gap:15px;flex-wrap:wrap">
|
|
<?php wp_nonce_field('thalim_hal_action'); ?>
|
|
|
|
<label style="font-weight:600">Depuis
|
|
<input type="date" name="hal_date_from" value="<?php echo esc_attr($date_from); ?>" style="width:auto">
|
|
</label>
|
|
|
|
<label style="font-weight:600">Jusqu'au
|
|
<input type="date" name="hal_date_to" value="<?php echo esc_attr($date_to); ?>" style="width:auto">
|
|
</label>
|
|
|
|
<?php if (!$is_contributor): ?>
|
|
<label style="font-weight:600">Auteur
|
|
<select name="hal_author_id" style="max-width:220px">
|
|
<option value="">— Tous —</option>
|
|
<?php foreach ($this->wp_users_by_hal_id as $user): ?>
|
|
<option value="<?php echo esc_attr($user['hal_id']); ?>"
|
|
<?php selected($author_hal_id, $user['hal_id']); ?>>
|
|
<?php echo esc_html($user['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</label>
|
|
<?php else: ?>
|
|
<input type="hidden" name="hal_author_id" value="<?php echo esc_attr($author_hal_id); ?>">
|
|
<?php endif; ?>
|
|
|
|
<button class="button button-secondary" name="thalim_hal_action" value="filter">Filtrer</button>
|
|
<button class="button button-secondary" name="thalim_hal_action" value="refresh" style="margin-left:5px">Rafraîchir</button>
|
|
<small style="color:#666">Mis en cache 5 min</small>
|
|
|
|
<span style="flex:1"></span>
|
|
|
|
<button class="button button-primary" name="thalim_hal_action" value="import_pending"
|
|
<?php if ($ready_count === 0): ?>disabled title="Aucune publication prête à importer"<?php endif; ?>>
|
|
<?php echo esc_html($import_label); ?>
|
|
</button>
|
|
</form>
|
|
|
|
<?php if (is_wp_error($preview)): ?>
|
|
<div class="notice notice-error"><p><?php echo esc_html($preview->get_error_message()); ?></p></div>
|
|
<?php else: ?>
|
|
<?php if (!$is_contributor) $this->render_wp_users_debug(); ?>
|
|
<?php $this->render_summary($preview['stats']); ?>
|
|
<?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>
|
|
<?php
|
|
}
|
|
|
|
private function render_summary($stats) {
|
|
?>
|
|
<div class="hal-summary">
|
|
<div class="hal-summary-item total">
|
|
<strong><?php echo number_format($stats['total']); ?></strong>
|
|
<span>Total in HAL</span>
|
|
</div>
|
|
<div class="hal-summary-item imported">
|
|
<strong><?php echo $stats['imported']; ?></strong>
|
|
<span>Already Imported</span>
|
|
</div>
|
|
<div class="hal-summary-item ready">
|
|
<strong><?php echo $stats['ready']; ?></strong>
|
|
<span>Ready to Import</span>
|
|
</div>
|
|
<div class="hal-summary-item blocked">
|
|
<strong><?php echo $stats['blocked']; ?></strong>
|
|
<span>No Matched User</span>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
private function render_preview_table($docs, $filters = []) {
|
|
if (empty($docs)) {
|
|
echo '<p>No publications found.</p>';
|
|
return;
|
|
}
|
|
?>
|
|
<table class="widefat hal-preview-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Statut</th>
|
|
<th>HAL ID</th>
|
|
<th>Titre</th>
|
|
<th>Type</th>
|
|
<th>Auteurs</th>
|
|
<th>IDs HAL auteurs</th>
|
|
<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 $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>
|
|
<?php if ($doc['journal']): ?>
|
|
<br><small style="color:#666"><?php echo esc_html($doc['journal']); ?></small>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<span class="hal-type-badge hal-type-<?php echo esc_attr($doc['type']); ?>">
|
|
<?php echo esc_html(self::DOC_TYPE_LABELS[$doc['type']] ?? $doc['type']); ?>
|
|
</span>
|
|
</td>
|
|
<td class="hal-users">
|
|
<?php echo esc_html(implode(', ', $doc['authors'])); ?>
|
|
</td>
|
|
<td class="hal-users">
|
|
<?php if (!empty($doc['author_hal_ids'])): ?>
|
|
<?php foreach ($doc['author_hal_ids'] as $aid):
|
|
$normalized = strtolower(trim($aid));
|
|
$is_match = isset($this->wp_users_by_hal_id[$normalized]);
|
|
?>
|
|
<code style="font-size:10px;<?php echo $is_match ? 'background:#d4edda;' : ''; ?>"><?php echo esc_html($aid); ?></code>
|
|
<?php endforeach; ?>
|
|
<?php else: ?>
|
|
<span style="color:#999;font-style:italic">aucun</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?php echo esc_html($doc['publication_date'] ?: ($doc['produced_date'] ?: '-')); ?></td>
|
|
<td>
|
|
<?php if (!empty($doc['matched_users'])): ?>
|
|
<span class="hal-users-matched">
|
|
<?php echo esc_html(implode(', ', $doc['matched_users'])); ?>
|
|
</span>
|
|
<?php else: ?>
|
|
<span class="hal-users-none">Aucun</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<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>
|
|
</table>
|
|
<?php
|
|
}
|
|
|
|
private function render_legend() {
|
|
?>
|
|
<div style="margin-top:15px;padding:10px;background:#f8f9fa;border-radius:4px;font-size:12px">
|
|
<strong>Légende :</strong>
|
|
<span style="margin-left:15px;"><span style="background:#d4edda;padding:2px 8px;border-radius:3px;">✓ Importé</span></span>
|
|
<span style="margin-left:15px;"><span style="background:#fff3cd;padding:2px 8px;border-radius:3px;">★ Prêt</span> Membre THALIM identifié</span>
|
|
<span style="margin-left:15px;"><span style="background:#f8d7da;padding:2px 8px;border-radius:3px;">✗ Bloqué</span> Aucun membre THALIM ne correspond aux IDs auteurs HAL</span>
|
|
</div>
|
|
<?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<?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>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):
|
|
$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>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</details>
|
|
<?php
|
|
}
|
|
|
|
private function get_preview_cache_key($date_from, $date_to, $author_hal_id) {
|
|
// Scope the cache by user ID in contributor mode so two contributors
|
|
// don't share a cache entry (and don't collide with the admin cache
|
|
// that might use the same author_hal_id filter).
|
|
$scope = $this->is_contributor_mode() ? ('u' . get_current_user_id() . '|') : '';
|
|
return 'thalim_hal_preview_' . md5($scope . $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 = '') {
|
|
// Server-side override: in contributor mode, force the author filter
|
|
// to the contributor's own idHAL regardless of what was POSTed.
|
|
if ($this->is_contributor_mode() && $this->contributor_idhal) {
|
|
$author_hal_id = $this->contributor_idhal;
|
|
}
|
|
$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);
|
|
?>
|
|
<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>';
|
|
}
|
|
}
|