AnonSec Team
Server IP : 62.171.151.215  /  Your IP : 216.73.217.19
Web Server : nginx/1.18.0
System : Linux vmi3128365 5.15.0-176-generic #186-Ubuntu SMP Fri Mar 13 11:01:42 UTC 2026 x86_64
User : alex ( 1000)
PHP Version : 8.4.18
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF
Directory (1777) :  /tmp/plugin-ga-google-analytics/../

[  Home  ][  C0mmand  ][  Upload File  ]

Current File : //tmp/plugin-ga-google-analytics/../wp-aggregator-custom.limit.php
<?php
/**
 * Plugin Name: WP Aggregator (Custom Table + RSS)
 * Description: Stocke des items d'agrégation dans une table dédiée (anti-doublon) + endpoint REST + flux RSS conforme.
 * Version: 2.1.1
 * Author: Hermes
 */

if (!defined('ABSPATH')) exit;

global $wpdb;
define('AGG_TABLE', $wpdb->prefix . 'agg_items');

if (!defined('AGG_TOKEN')) {
    $token_file = '/etc/camarche-agg-token';
    $token = is_readable($token_file) ? trim((string) file_get_contents($token_file)) : '';
    define('AGG_TOKEN', $token !== '' ? $token : 'change-moi-en-long-aleatoire');
}
if (!defined('AGG_FEED_LIMIT')) {
    define('AGG_FEED_LIMIT', 300);
}

function agg_install_table() {
    global $wpdb;
    $charset_collate = $wpdb->get_charset_collate();
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    $sql = "CREATE TABLE " . AGG_TABLE . " (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        url TEXT NOT NULL,
        url_hash CHAR(32) NOT NULL,
        title TEXT NOT NULL,
        teaser TEXT NULL,
        tags TEXT NULL,
        date_gmt DATETIME NULL,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY  (id),
        UNIQUE KEY uniq_url_hash (url_hash),
        KEY idx_date_gmt (date_gmt)
    ) $charset_collate;";
    dbDelta($sql);
    update_option('agg_db_version', '1');
    flush_rewrite_rules(false);
}
register_activation_hook(__FILE__, 'agg_install_table');

function agg_ensure_table_exists() {
    global $wpdb;
    $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', AGG_TABLE));
    if ($exists !== AGG_TABLE) {
        agg_install_table();
    }
}
add_action('plugins_loaded', 'agg_ensure_table_exists');

function agg_norm_url(string $url): string {
    $url = trim($url);
    if ($url === '') return '';
    $parts = wp_parse_url($url);
    if (!$parts || empty($parts['scheme']) || empty($parts['host'])) return $url;
    $scheme = $parts['scheme'];
    $host   = $parts['host'];
    $path   = $parts['path'] ?? '';
    return $scheme . '://' . $host . $path;
}
function agg_url_hash(string $url): string { return md5($url); }

function agg_upsert_items(array $items): int {
    global $wpdb;
    $affected = 0;
    foreach ($items as $it) {
        $url   = isset($it['url'])   ? esc_url_raw($it['url']) : '';
        $title = isset($it['title']) ? wp_strip_all_tags($it['title']) : '';
        if (!$url || !$title) { continue; }

        $url_norm = agg_norm_url($url);
        $uhash    = agg_url_hash($url_norm);
        $teaser = isset($it['teaser']) ? wp_strip_all_tags((string) $it['teaser']) : '';
        $tags   = isset($it['tags'])   ? wp_strip_all_tags((string) $it['tags'])   : '';

        $date_gmt = null;
        if (!empty($it['date_gmt'])) {
            $ts = strtotime((string) $it['date_gmt']);
            if ($ts) $date_gmt = gmdate('Y-m-d H:i:s', $ts);
        }

        $sql = "
            INSERT INTO " . AGG_TABLE . " (url, url_hash, title, teaser, tags, date_gmt)
            VALUES (%s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                title = VALUES(title),
                teaser = VALUES(teaser),
                tags = VALUES(tags),
                date_gmt = IFNULL(VALUES(date_gmt), date_gmt),
                updated_at = CURRENT_TIMESTAMP
        ";
        $prepared = $wpdb->prepare(
            $sql,
            $url_norm,
            $uhash,
            $title,
            $teaser !== '' ? $teaser : null,
            $tags   !== '' ? $tags   : null,
            $date_gmt
        );
        $res = $wpdb->query($prepared);
        if ($res === 1 || $res === 2) $affected++;
    }
    return $affected;
}



function agg_fetch_beebom_news_items(int $limit = 40): array {
    $index_url = 'https://beebom.com/sitemap_index.xml';

    $fetch_xml = function(string $url) {
        $response = wp_remote_get($url, [
            'timeout' => 30,
            'headers' => [
                'User-Agent' => 'CaMarcheAggregator/1.1',
                'Accept' => 'application/xml,text/xml,*/*',
            ],
        ]);
        if (is_wp_error($response)) {
            return ['error' => $response->get_error_message(), 'body' => null];
        }
        $code = wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);
        if ($code < 200 || $code >= 300 || !$body) {
            return ['error' => 'bad_response_' . $code, 'body' => null];
        }
        return ['error' => null, 'body' => $body];
    };

    $load_dom = function(string $body) {
        libxml_use_internal_errors(true);
        $dom = new DOMDocument();
        if (!$dom->loadXML($body)) return null;
        return $dom;
    };

    // 1) Lire l'index Yoast Beebom et sélectionner automatiquement les 3 derniers post-sitemapN.xml.
    $idx = $fetch_xml($index_url);
    if (!empty($idx['error'])) return ['error' => 'index_' . $idx['error'], 'items' => [], 'sitemaps' => []];
    $dom = $load_dom($idx['body']);
    if (!$dom) return ['error' => 'invalid_index_xml', 'items' => [], 'sitemaps' => []];

    $xp = new DOMXPath($dom);
    $xp->registerNamespace('sm', 'http://www.sitemaps.org/schemas/sitemap/0.9');
    $post_sitemaps = [];
    foreach ($xp->query('//sm:sitemap') as $sm) {
        $locNode = $xp->query('sm:loc', $sm)->item(0);
        $lastNode = $xp->query('sm:lastmod', $sm)->item(0);
        $loc = $locNode ? trim($locNode->textContent) : '';
        if (preg_match('~/post-sitemap(\d+)\.xml$~', $loc, $m)) {
            $post_sitemaps[] = [
                'num' => intval($m[1]),
                'url' => $loc,
                'lastmod' => $lastNode ? trim($lastNode->textContent) : '',
            ];
        }
    }
    if (!$post_sitemaps) return ['error' => 'no_post_sitemaps_found', 'items' => [], 'sitemaps' => []];

    usort($post_sitemaps, function($a, $b) { return $b['num'] <=> $a['num']; });
    $selected = array_slice($post_sitemaps, 0, 3); // ex: 50,49,48 puis automatiquement 51,50,49 quand 51 apparaît.

    // 2) Parser les URLs des 3 sitemaps sélectionnés, trier par lastmod descendant, limiter.
    $items_by_url = [];
    foreach ($selected as $sm) {
        $res = $fetch_xml($sm['url']);
        if (!empty($res['error'])) continue;
        $sd = $load_dom($res['body']);
        if (!$sd) continue;
        $sx = new DOMXPath($sd);
        $sx->registerNamespace('sm', 'http://www.sitemaps.org/schemas/sitemap/0.9');
        foreach ($sx->query('//sm:url') as $u) {
            $locNode = $sx->query('sm:loc', $u)->item(0);
            $lastNode = $sx->query('sm:lastmod', $u)->item(0);
            $loc = $locNode ? trim($locNode->textContent) : '';
            $lastmod = $lastNode ? trim($lastNode->textContent) : '';
            if (!$loc) continue;

            $path = wp_parse_url($loc, PHP_URL_PATH);
            $slug = $path ? basename(untrailingslashit($path)) : '';
            $title = $slug ? ucwords(str_replace(['-', '_'], ' ', $slug)) : $loc;
            $date_gmt = $lastmod ?: gmdate('c');

            // Si la même URL est présente plusieurs fois, garder la version la plus récente.
            $key = agg_norm_url($loc);
            if (!isset($items_by_url[$key]) || strcmp($date_gmt, $items_by_url[$key]['date_gmt']) > 0) {
                $items_by_url[$key] = [
                    'url' => $loc,
                    'title' => html_entity_decode($title, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
                    'teaser' => '',
                    'tags' => 'beebom,tech,gaming',
                    'date_gmt' => $date_gmt,
                ];
            }
        }
    }

    $items = array_values($items_by_url);
    usort($items, function($a, $b) { return strcmp((string)($b['date_gmt'] ?? ''), (string)($a['date_gmt'] ?? '')); });
    return [
        'error' => null,
        'items' => array_slice($items, 0, max(1, $limit)),
        'sitemaps' => array_map(function($s) { return $s['url']; }, $selected),
    ];
}

add_action('rest_api_init', function () {


    register_rest_route('agg/v1', '/sync-beebom', [
        'methods'  => ['POST', 'GET'],
        'callback' => function (WP_REST_Request $request) {
            $token = $request->get_header('X-Agg-Token');
            if (!AGG_TOKEN || !$token || !hash_equals(AGG_TOKEN, $token)) {
                return new WP_Error('forbidden', 'Bad token', ['status' => 403]);
            }
            $limit = intval($request->get_param('limit') ?: 40);
            $result = agg_fetch_beebom_news_items($limit);
            if (!empty($result['error'])) {
                return new WP_Error('sync_error', $result['error'], ['status' => 502]);
            }
            $items = $result['items'];
            $n = agg_upsert_items($items);
            return ['ok' => true, 'source' => 'beebom_post_sitemaps_latest3', 'sitemaps' => $result['sitemaps'] ?? [], 'received' => count($items), 'affected' => $n, 'first' => $items[0] ?? null];
        },
        'permission_callback' => '__return_true',
    ]);

    register_rest_route('agg/v1', '/bulk', [
        'methods'  => 'POST',
        'callback' => function (WP_REST_Request $request) {
            $token = $request->get_header('X-Agg-Token');
            if (!AGG_TOKEN || !$token || !hash_equals(AGG_TOKEN, $token)) {
                return new WP_Error('forbidden', 'Bad token', ['status' => 403]);
            }
            $items = $request->get_json_params();
            if (!is_array($items)) {
                return new WP_Error('bad_request', 'JSON array attendu', ['status' => 400]);
            }
            $n = agg_upsert_items($items);
            return ['ok' => true, 'received' => count($items), 'affected' => $n];
        },
        'permission_callback' => '__return_true',
    ]);
});

add_action('init', function () {
    add_feed('agg', 'agg_render_feed');
    add_feed('aggregator', 'agg_render_feed');
});
add_action('do_feed_agg', 'agg_render_feed', 10, 1);
add_action('do_feed_aggregator', 'agg_render_feed', 10, 1);

function agg_render_feed($for_comments = false) {
    global $wpdb;
    if (function_exists('ob_get_level')) {
        while (ob_get_level()) { ob_end_clean(); }
    }
    nocache_headers();
    header('Content-Type: application/rss+xml; charset=' . get_option('blog_charset'), true);
    status_header(200);

    $limit = intval(AGG_FEED_LIMIT);
    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT url, title, teaser, tags, COALESCE(date_gmt, updated_at) AS pub, updated_at, created_at
         FROM " . AGG_TABLE . " ORDER BY pub DESC LIMIT %d",
        $limit
    ));

    $site_name = get_bloginfo('name');
    $site_url  = home_url('/');
    $charset   = get_option('blog_charset') ?: 'UTF-8';
    $lang      = get_locale() ? str_replace('_', '-', get_locale()) : 'fr-FR';
    $self_url  = home_url(add_query_arg('feed', (get_query_var('feed') ?: 'agg'), ''));

    echo '<?xml version="1.0" encoding="' . esc_attr($charset) . '"?>' . "\n";
    ?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title><?php echo esc_html($site_name); ?> – Aggregator</title>
    <link><?php echo esc_url($site_url); ?></link>
    <description><?php echo esc_html('Flux agrégé personnalisé (table dédiée, anti-doublon)'); ?></description>
    <language><?php echo esc_html($lang); ?></language>
    <generator>WP Aggregator (Custom Table) 2.1.1</generator>
    <ttl>10</ttl>
    <lastBuildDate><?php echo esc_html(gmdate('r')); ?></lastBuildDate>
    <atom:link href="<?php echo esc_url($self_url); ?>" rel="self" type="application/rss+xml" />
<?php if ($rows) : foreach ($rows as $r) :
    $link   = esc_url($r->url);
    $title  = wp_strip_all_tags((string) $r->title);
    $teaser = (string) ($r->teaser ?? '');
    $tags   = (string) ($r->tags ?? '');
    $guid   = 'agg-' . md5($r->url);
    $pub_ts = strtotime($r->pub ?: $r->updated_at ?: $r->created_at ?: 'now');
    $pub_r  = gmdate('r', $pub_ts);
    $cats = array_filter(array_map('trim', $tags !== '' ? explode(',', $tags) : []));
?>
    <item>
      <title><?php echo esc_html($title); ?></title>
      <link><?php echo $link; ?></link>
      <guid isPermaLink="false"><?php echo esc_html($guid); ?></guid>
      <pubDate><?php echo esc_html($pub_r); ?></pubDate>
<?php if ($teaser !== ''): ?>
      <description><![CDATA[<?php echo $teaser; ?>]]></description>
      <content:encoded><![CDATA[<?php echo $teaser; ?>]]></content:encoded>
<?php endif; ?>
<?php foreach ($cats as $cat): ?>
      <category><?php echo esc_html($cat); ?></category>
<?php endforeach; ?>
    </item>
<?php endforeach; endif; ?>
  </channel>
</rss>
<?php
    exit;
}

AnonSec - 2021