542 lines
16 KiB
PHP
542 lines
16 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Récupère tous les auteurs uniques d'une revue
|
|
*
|
|
* Cette fonction collecte tous les auteurs des articles liés à une revue spécifique.
|
|
* Elle parcourt tous les articles publiés qui ont une relation avec la revue
|
|
* et retourne un tableau d'IDs d'auteurs uniques.
|
|
*
|
|
* @param int $revueID L'ID de la revue pour laquelle récupérer les auteurs
|
|
* @return array Tableau d'IDs d'auteurs uniques
|
|
*/
|
|
function getRevueAuthors($revueID)
|
|
{
|
|
$revueRelatedArticles = new WP_Query(array(
|
|
'post_type' => 'articles',
|
|
'posts_per_page' => -1,
|
|
'post_status' => 'publish',
|
|
'meta_query' => array(
|
|
array(
|
|
'key' => 'related_revue',
|
|
'value' => $revueID,
|
|
'compare' => '=',
|
|
),
|
|
),
|
|
));
|
|
$authors = array();
|
|
|
|
foreach ($revueRelatedArticles->posts as $article) {
|
|
$currentArticleAuthors = get_field('authors', $article->ID);
|
|
|
|
if (empty($currentArticleAuthors) || !is_array($currentArticleAuthors)) continue;
|
|
|
|
foreach ($currentArticleAuthors as $authorID) {
|
|
$authors[] = $authorID;
|
|
}
|
|
}
|
|
return array_unique($authors);
|
|
}
|
|
|
|
function get_author_articles_amount($authorID)
|
|
{
|
|
if (empty($authorID)) return 0;
|
|
$articles = count_user_articles($authorID, 'articles');
|
|
return $articles;
|
|
}
|
|
|
|
/**
|
|
* Récupère tous les termes uniques d'une taxonomie pour une revue
|
|
*
|
|
* Cette fonction collecte tous les termes d'une taxonomie spécifique
|
|
* associés aux articles d'une revue. Elle parcourt tous les articles
|
|
* liés à la revue et retourne les termes uniques de la taxonomie donnée.
|
|
*
|
|
* @param int $revueID L'ID de la revue
|
|
* @param string $taxonomy Le nom de la taxonomie (ex: 'etiquettes', 'category')
|
|
* @return array Tableau d'objets de termes uniques
|
|
*/
|
|
function get_revue_terms($revueID, $taxonomy)
|
|
{
|
|
$revueRelatedArticles = new WP_Query(array(
|
|
'post_type' => 'articles',
|
|
'posts_per_page' => -1,
|
|
'post_status' => 'publish',
|
|
'meta_query' => array(
|
|
array(
|
|
'key' => 'related_revue',
|
|
'value' => $revueID,
|
|
'compare' => '=',
|
|
),
|
|
),
|
|
));
|
|
$terms = array();
|
|
|
|
foreach ($revueRelatedArticles->posts as $article) {
|
|
$currentArticleTerms = get_the_terms($article->ID, $taxonomy);
|
|
|
|
if (empty($currentArticleTerms) || !is_array($currentArticleTerms)) continue;
|
|
|
|
foreach ($currentArticleTerms as $term) {
|
|
$terms[] = $term->term_id;
|
|
}
|
|
}
|
|
$uniquesTermsArray = array();
|
|
foreach (array_unique($terms) as $term) {
|
|
$termObject = get_term($term, $taxonomy);
|
|
$uniquesTermsArray[] = $termObject;
|
|
}
|
|
|
|
return $uniquesTermsArray;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compte le nombre d'articles d'un utilisateur pour un type de post donné
|
|
*
|
|
* Cette fonction compte combien d'articles d'un type spécifique
|
|
* sont associés à un utilisateur donné via le champ custom 'authors'.
|
|
*
|
|
* @param int $userID L'ID de l'utilisateur
|
|
* @param string $postType Le type de post à compter (ex: 'articles')
|
|
* @return int Le nombre d'articles trouvés
|
|
*/
|
|
function count_user_articles($userID, $postType)
|
|
{
|
|
$args = array(
|
|
'post_type' => $postType,
|
|
'meta_query' => array(
|
|
array(
|
|
'key' => 'authors',
|
|
'value' => '"' . $userID . '"',
|
|
'compare' => 'LIKE',
|
|
),
|
|
),
|
|
);
|
|
$query = new WP_Query($args);
|
|
return $query->found_posts;
|
|
}
|
|
|
|
|
|
/**
|
|
* Génère un sommaire automatique à partir du contenu d'un post
|
|
*
|
|
* Cette fonction analyse les blocs Gutenberg d'un post pour extraire
|
|
* tous les titres de niveau 2 (h2) et créer un index de navigation.
|
|
* Elle génère des ancres automatiques pour chaque titre.
|
|
*
|
|
* @param int $postID L'ID du post à analyser
|
|
* @return array Tableau associatif contenant les éléments du sommaire
|
|
* Chaque élément contient: title, anchor, level
|
|
*/
|
|
function build_sommaire_from_content($postID)
|
|
{
|
|
$blocks = parse_blocks(get_the_content($postID));
|
|
|
|
// Collecter les h2 au niveau racine ET dans les innerBlocks immédiats (profondeur 1)
|
|
$titleBlocks = [];
|
|
|
|
$checkIsH2 = function ($block) {
|
|
if (!is_array($block) || empty($block['blockName'])) return false;
|
|
if ($block['blockName'] !== 'core/heading') return false;
|
|
|
|
// Exclure explicitement les titres marqués avec la classe not-in-index
|
|
if (!empty($block['attrs']['className']) && strpos($block['attrs']['className'], 'not-in-index') !== false) {
|
|
return false;
|
|
}
|
|
if (!empty($block['innerHTML']) && preg_match('/class="[^"]*not-in-index[^"]*"/i', $block['innerHTML'])) {
|
|
return false;
|
|
}
|
|
|
|
// Utiliser le niveau depuis les attributs si présent
|
|
if (!empty($block['attrs']['level'])) {
|
|
return intval($block['attrs']['level']) === 2;
|
|
}
|
|
|
|
// Sinon, détecter <h2> dans le HTML du bloc
|
|
if (!empty($block['innerHTML']) && preg_match('/<h2[^>]*>/i', $block['innerHTML'])) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
foreach ($blocks as $block) {
|
|
if ($checkIsH2($block)) {
|
|
$titleBlocks[] = $block;
|
|
}
|
|
// Parcours des enfants immédiats uniquement
|
|
if (!empty($block['innerBlocks']) && is_array($block['innerBlocks'])) {
|
|
foreach ($block['innerBlocks'] as $child) {
|
|
if ($checkIsH2($child)) {
|
|
$titleBlocks[] = $child;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$outputIndex = [];
|
|
|
|
foreach ($titleBlocks as $block) {
|
|
$title = strip_tags($block['innerHTML']);
|
|
|
|
// Extraire le niveau depuis le HTML ou les attributs
|
|
$level = $block['attrs']['level'] ?? null;
|
|
if (!$level && preg_match('/<h2[^>]*>/i', $block['innerHTML'], $matches)) {
|
|
$level = 2;
|
|
}
|
|
|
|
if ($level !== 2) continue;
|
|
|
|
$anchor = $block['attrs']['idName'] ?? ($block['attrs']['anchor'] ?? sanitize_title($title));
|
|
|
|
// Ajouter un préfixe si l'ancre commence par un chiffre
|
|
if (!empty($anchor) && preg_match('/^[0-9]/', $anchor)) {
|
|
$anchor = 'anchor-' . $anchor;
|
|
}
|
|
|
|
|
|
$outputIndex[] = [
|
|
'title' => $title,
|
|
'anchor' => $anchor,
|
|
'level' => $level,
|
|
];
|
|
}
|
|
|
|
return $outputIndex;
|
|
}
|
|
|
|
|
|
/**
|
|
* Construit un index des notes de bas de page à partir du contenu HTML
|
|
*
|
|
* Cette fonction analyse le contenu HTML pour détecter les liens de notes
|
|
* de bas de page (avec classe 'footnote-reference') et extrait leur contenu
|
|
* pour créer un index numéroté des notes.
|
|
*
|
|
* @param string $content Le contenu HTML à analyser
|
|
* @return array Tableau des notes de bas de page avec clé, ancre et contenu
|
|
*/
|
|
function build_footnotes_index_from_content($content)
|
|
{
|
|
if (empty($content)) {
|
|
return [];
|
|
}
|
|
|
|
$footnotes = [];
|
|
// Trouve les balises <a> avec classe footnote-reference ET attribut data-footnote-content (ordre flexible)
|
|
$pattern = '/<a[^>]*class="[^"]*footnote-reference[^"]*"[^>]*data-footnote-content="([^"]*)"[^>]*>|<a[^>]*data-footnote-content="([^"]*)"[^>]*class="[^"]*footnote-reference[^"]*"[^>]*>/i';
|
|
$has_footnotes = preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
|
|
|
|
if ($has_footnotes) {
|
|
foreach ($matches as $index => $match) {
|
|
|
|
// Le contenu peut être dans match[1] ou match[2] selon l'ordre des attributs
|
|
$footnote_content = !empty($match[1]) ? $match[1] : $match[2];
|
|
if (!empty($footnote_content)) {
|
|
$footnotes[] = array(
|
|
'key' => $index + 1,
|
|
'anchorID' => $index + 1,
|
|
'content' => $footnote_content
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $footnotes;
|
|
}
|
|
|
|
|
|
/**
|
|
* Ajoute des identifiants uniques aux liens de notes de bas de page et supprime le texte [NOTE]
|
|
*
|
|
* Cette fonction parcourt le contenu HTML et :
|
|
* 1. Ajoute un attribut id unique à chaque lien ayant la classe "footnote-reference"
|
|
* 2. Supprime le texte [NOTE] à l'intérieur des balises de footnotes
|
|
* Cela permet de créer des ancres de navigation et d'afficher uniquement le numéro via CSS.
|
|
*
|
|
* @param string $content Le contenu HTML à traiter
|
|
* @return string Le contenu modifié avec les IDs ajoutés et le texte [NOTE] supprimé
|
|
*/
|
|
function apply_footnotes_urls_to_content($content)
|
|
{
|
|
$post_type = get_post_type();
|
|
if ($post_type !== 'articles' && !is_admin()) return $content;
|
|
|
|
// Compter les références de notes de bas de page
|
|
$footnote_count = 0;
|
|
|
|
// Utiliser preg_replace_callback pour traiter chaque occurrence individuellement
|
|
$content = preg_replace_callback(
|
|
'/<a([^>]*class="[^"]*footnote-reference[^"]*"[^>]*)>/i',
|
|
function ($matches) use (&$footnote_count) {
|
|
$footnote_count++;
|
|
return '<a id="footnote-' . $footnote_count . '" ' . $matches[1] . '>';
|
|
},
|
|
$content
|
|
);
|
|
|
|
// Supprimer tout le contenu textuel à l'intérieur des balises <a> avec classe footnote-reference
|
|
$content = preg_replace(
|
|
'/<a([^>]*class="[^"]*footnote-reference[^"]*"[^>]*)>.*?<\/a>/i',
|
|
'<a$1></a>',
|
|
$content
|
|
);
|
|
|
|
return $content;
|
|
}
|
|
add_filter('the_content', 'apply_footnotes_urls_to_content', 10);
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Récupère le nombre de likes d'un post
|
|
*
|
|
* Cette fonction utilitaire récupère le compteur de likes stocké
|
|
* dans les métadonnées d'un post. Retourne 0 si aucun like n'existe.
|
|
*
|
|
* @param int $post_id L'ID du post
|
|
* @return int Le nombre de likes (0 si aucun)
|
|
*/
|
|
function get_post_likes_count($post_id)
|
|
{
|
|
$likes_count = get_post_meta($post_id, 'likes_count', true);
|
|
return $likes_count ? intval($likes_count) : 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Affiche le nombre de likes d'un post avec formatage
|
|
*
|
|
* Cette fonction utilitaire formate l'affichage du compteur de likes
|
|
* avec une icône optionnelle et la gestion du pluriel.
|
|
*
|
|
* @param int $post_id L'ID du post
|
|
* @param bool $show_icon Afficher l'icône cœur (défaut: true)
|
|
* @return string Le texte formaté (ex: "❤️ 5 likes" ou "3 like")
|
|
*/
|
|
function display_likes_count($post_id, $show_icon = true)
|
|
{
|
|
$likes_count = get_post_likes_count($post_id);
|
|
$icon = $show_icon ? '❤️ ' : '';
|
|
|
|
return $icon . $likes_count . ' like' . ($likes_count > 1 ? 's' : '');
|
|
}
|
|
|
|
|
|
/**
|
|
* Construit les URL de partage pour un post
|
|
*
|
|
* Cette fonction génère les URL de partage pour un post spécifique.
|
|
* Elle retourne un tableau associatif contenant les URL de partage pour Facebook, Twitter-X et LinkedIn.
|
|
*
|
|
* @return array Tableau associatif contenant les URL de partage
|
|
*/
|
|
function build_share_urls()
|
|
{
|
|
$post_id = get_the_ID();
|
|
|
|
$postUrl = get_permalink($post_id);
|
|
$postTitle = get_the_title($post_id);
|
|
$facebookUrl = 'https://www.facebook.com/sharer.php?u=' . $postUrl;
|
|
$twitterUrl = 'https://twitter.com/intent/tweet?text=' . $postTitle . '&url=' . get_the_permalink(get_the_id());
|
|
$linkedInUrl = 'https://www.linkedin.com/feed/?shareActive=true&text=' . $postTitle . ' ' . $postUrl;
|
|
|
|
return array(
|
|
'Facebook' => $facebookUrl,
|
|
'Twitter-X' => $twitterUrl,
|
|
'Linkedin' => $linkedInUrl,
|
|
'postUrl' => $postUrl
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Génère les métadonnées Open Graph pour le partage sur les réseaux sociaux
|
|
*/
|
|
function generate_og_meta_tags()
|
|
{
|
|
global $post;
|
|
|
|
// URL canonique
|
|
$og_url = is_home() ? home_url() : get_permalink();
|
|
|
|
// Titre
|
|
if (is_home() || is_front_page()) {
|
|
$og_title = get_bloginfo('name');
|
|
$og_description = get_bloginfo('description');
|
|
} elseif (is_single() || is_page()) {
|
|
$og_title = get_the_title();
|
|
$og_description = get_the_excerpt();
|
|
if (empty($og_description)) {
|
|
$og_description = wp_trim_words(strip_tags(get_the_content()), 30);
|
|
}
|
|
} elseif (is_category()) {
|
|
$og_title = single_cat_title('', false) . ' - ' . get_bloginfo('name');
|
|
$og_description = category_description();
|
|
} elseif (is_tag()) {
|
|
$og_title = single_tag_title('', false) . ' - ' . get_bloginfo('name');
|
|
$og_description = tag_description();
|
|
} elseif (is_archive()) {
|
|
$og_title = get_the_archive_title() . ' - ' . get_bloginfo('name');
|
|
$og_description = get_the_archive_description();
|
|
} else {
|
|
$og_title = wp_get_document_title();
|
|
$og_description = get_bloginfo('description');
|
|
}
|
|
|
|
// Nettoyer la description
|
|
$og_description = wp_strip_all_tags($og_description);
|
|
$og_description = str_replace(array("\r", "\n", "\t"), ' ', $og_description);
|
|
$og_description = wp_trim_words($og_description, 30);
|
|
|
|
// Image Open Graph fixe
|
|
$og_image = get_stylesheet_directory_uri() . '/resources/img/og/dynamiques-og.png';
|
|
|
|
// Type de contenu
|
|
$og_type = is_single() ? 'article' : 'website';
|
|
|
|
// Nom du site
|
|
$site_name = get_bloginfo('name');
|
|
|
|
// Auteur (pour les articles)
|
|
$og_author = '';
|
|
if (is_single()) {
|
|
$og_author = get_the_author_meta('display_name');
|
|
}
|
|
|
|
// Date de publication (pour les articles)
|
|
$published_time = '';
|
|
$modified_time = '';
|
|
if (is_single()) {
|
|
$published_time = get_the_date('c');
|
|
$modified_time = get_the_modified_date('c');
|
|
}
|
|
|
|
// Générer les balises meta
|
|
$meta_tags = array();
|
|
|
|
// Open Graph
|
|
$meta_tags[] = '<meta property="og:title" content="' . esc_attr($og_title) . '" />';
|
|
$meta_tags[] = '<meta property="og:description" content="' . esc_attr($og_description) . '" />';
|
|
$meta_tags[] = '<meta property="og:type" content="' . esc_attr($og_type) . '" />';
|
|
$meta_tags[] = '<meta property="og:url" content="' . esc_url($og_url) . '" />';
|
|
$meta_tags[] = '<meta property="og:site_name" content="' . esc_attr($site_name) . '" />';
|
|
|
|
if (!empty($og_image)) {
|
|
$meta_tags[] = '<meta property="og:image" content="' . esc_url($og_image) . '" />';
|
|
$meta_tags[] = '<meta property="og:image:alt" content="' . esc_attr($og_title) . '" />';
|
|
}
|
|
|
|
if (!empty($og_author)) {
|
|
$meta_tags[] = '<meta property="article:author" content="' . esc_attr($og_author) . '" />';
|
|
}
|
|
|
|
if (!empty($published_time)) {
|
|
$meta_tags[] = '<meta property="article:published_time" content="' . esc_attr($published_time) . '" />';
|
|
}
|
|
|
|
if (!empty($modified_time)) {
|
|
$meta_tags[] = '<meta property="article:modified_time" content="' . esc_attr($modified_time) . '" />';
|
|
}
|
|
|
|
// Twitter Cards
|
|
$meta_tags[] = '<meta name="twitter:card" content="summary_large_image" />';
|
|
$meta_tags[] = '<meta name="twitter:title" content="' . esc_attr($og_title) . '" />';
|
|
$meta_tags[] = '<meta name="twitter:description" content="' . esc_attr($og_description) . '" />';
|
|
|
|
if (!empty($og_image)) {
|
|
$meta_tags[] = '<meta name="twitter:image" content="' . esc_url($og_image) . '" />';
|
|
}
|
|
|
|
// Meta description standard
|
|
$meta_tags[] = '<meta name="description" content="' . esc_attr($og_description) . '" />';
|
|
|
|
// Canonical URL
|
|
$meta_tags[] = '<link rel="canonical" href="' . esc_url($og_url) . '" />';
|
|
|
|
return implode("\n\t", $meta_tags);
|
|
}
|
|
|
|
/**
|
|
* Génère un extrait de recherche avec le terme surligné
|
|
*
|
|
* Cette fonction cherche le terme de recherche dans le contenu du post
|
|
* et retourne un extrait avec le terme mis en évidence.
|
|
*
|
|
* @param int $post_id L'ID du post
|
|
* @param string $search_term Le terme à rechercher et surligner
|
|
* @param int $context_length Nombre de caractères de contexte autour du terme (défaut: 150)
|
|
* @return string L'extrait avec le terme surligné en HTML
|
|
*/
|
|
function get_search_snippet($post_id, $search_term, $context_length = 150)
|
|
{
|
|
if (empty($search_term)) {
|
|
return wp_trim_words(get_the_excerpt($post_id), 30);
|
|
}
|
|
|
|
// Récupérer le contenu complet
|
|
$content = get_post_field('post_content', $post_id);
|
|
$title = get_the_title($post_id);
|
|
|
|
// Nettoyer le contenu des balises HTML et shortcodes
|
|
$content = wp_strip_all_tags($content);
|
|
$content = strip_shortcodes($content);
|
|
|
|
// Recherche insensible à la casse
|
|
$search_term_escaped = preg_quote($search_term, '/');
|
|
$position = stripos($content, $search_term);
|
|
|
|
// Si le terme n'est pas trouvé dans le contenu, chercher dans le titre
|
|
if ($position === false) {
|
|
$position_in_title = stripos($title, $search_term);
|
|
if ($position_in_title !== false) {
|
|
// Si trouvé dans le titre, retourner juste le début du contenu
|
|
$snippet = substr($content, 0, $context_length * 2);
|
|
$snippet = wp_trim_words($snippet, 30);
|
|
|
|
// Surligner dans le titre et le snippet
|
|
$highlighted_title = preg_replace(
|
|
'/(' . $search_term_escaped . ')/i',
|
|
'<mark class="search-highlight">$1</mark>',
|
|
$title
|
|
);
|
|
|
|
return '<strong>' . $highlighted_title . ':</strong> ' . $snippet;
|
|
}
|
|
|
|
// Si vraiment pas trouvé, retourner l'excerpt normal
|
|
return wp_trim_words(get_the_excerpt($post_id), 30);
|
|
}
|
|
|
|
// Calculer les positions de début et fin de l'extrait
|
|
$start = max(0, $position - $context_length);
|
|
$end = min(strlen($content), $position + strlen($search_term) + $context_length);
|
|
|
|
// Extraire le snippet
|
|
$snippet = substr($content, $start, $end - $start);
|
|
|
|
// Ajouter des points de suspension si nécessaire
|
|
if ($start > 0) {
|
|
$snippet = '...' . $snippet;
|
|
}
|
|
if ($end < strlen($content)) {
|
|
$snippet = $snippet . '...';
|
|
}
|
|
|
|
// Surligner tous les termes de recherche (supporte les recherches multiples)
|
|
$search_terms = explode(' ', $search_term);
|
|
foreach ($search_terms as $term) {
|
|
if (strlen(trim($term)) > 2) { // Ignorer les mots trop courts
|
|
$term_escaped = preg_quote(trim($term), '/');
|
|
$snippet = preg_replace(
|
|
'/(' . $term_escaped . ')/i',
|
|
'<mark class="search-highlight">$1</mark>',
|
|
$snippet
|
|
);
|
|
}
|
|
}
|
|
|
|
return $snippet;
|
|
}
|