Deploy 6c57f6c from main
This commit is contained in:
@@ -709,6 +709,131 @@ class Peptide_News_Admin {
|
||||
echo '<p><strong>' . esc_html__( 'Widget:', 'peptide-news' ) . '</strong> ' .
|
||||
esc_html__( 'Add "Peptide News" from Appearance > Widgets.', 'peptide-news' ) . '</p>';
|
||||
|
||||
// ── Plugin Log Viewer ──────────────────────────────────────────
|
||||
echo '<h2>' . esc_html__( 'Plugin Log', 'peptide-news' ) . '</h2>';
|
||||
echo '<p class="description">' . esc_html__( 'Recent plugin activity — fetches, AI processing, errors, and admin actions.', 'peptide-news' ) . '</p>';
|
||||
|
||||
// Filters row.
|
||||
echo '<div style="margin:10px 0;display:flex;gap:8px;align-items:center;">';
|
||||
echo '<select id="peptide-log-level">';
|
||||
echo '<option value="">' . esc_html__( 'All Levels', 'peptide-news' ) . '</option>';
|
||||
echo '<option value="info">Info</option>';
|
||||
echo '<option value="warning">Warning</option>';
|
||||
echo '<option value="error">Error</option>';
|
||||
echo '<option value="debug">Debug</option>';
|
||||
echo '</select>';
|
||||
echo '<select id="peptide-log-context">';
|
||||
echo '<option value="">' . esc_html__( 'All Contexts', 'peptide-news' ) . '</option>';
|
||||
echo '<option value="fetch">Fetch</option>';
|
||||
echo '<option value="llm">LLM / AI</option>';
|
||||
echo '<option value="cron">Cron</option>';
|
||||
echo '<option value="admin">Admin</option>';
|
||||
echo '<option value="general">General</option>';
|
||||
echo '</select>';
|
||||
echo '<button type="button" id="peptide-log-refresh" class="button">' . esc_html__( 'Refresh', 'peptide-news' ) . '</button>';
|
||||
echo '<button type="button" id="peptide-log-clear" class="button" style="color:#a00;">' . esc_html__( 'Clear Log', 'peptide-news' ) . '</button>';
|
||||
echo '<span id="peptide-log-status" style="margin-left:8px;color:#666;"></span>';
|
||||
echo '</div>';
|
||||
|
||||
// Log table.
|
||||
echo '<div id="peptide-log-container" style="max-height:400px;overflow-y:auto;border:1px solid #c3c4c7;background:#fff;">';
|
||||
echo '<table class="widefat striped" style="margin:0;">';
|
||||
echo '<thead><tr>';
|
||||
echo '<th style="width:140px;">' . esc_html__( 'Time', 'peptide-news' ) . '</th>';
|
||||
echo '<th style="width:70px;">' . esc_html__( 'Level', 'peptide-news' ) . '</th>';
|
||||
echo '<th style="width:70px;">' . esc_html__( 'Context', 'peptide-news' ) . '</th>';
|
||||
echo '<th>' . esc_html__( 'Message', 'peptide-news' ) . '</th>';
|
||||
echo '</tr></thead>';
|
||||
echo '<tbody id="peptide-log-body"><tr><td colspan="4" style="text-align:center;color:#999;">' . esc_html__( 'Loading...', 'peptide-news' ) . '</td></tr></tbody>';
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
|
||||
// Pagination.
|
||||
echo '<div id="peptide-log-pagination" style="margin:8px 0;display:flex;gap:8px;align-items:center;"></div>';
|
||||
|
||||
?>
|
||||
<script>
|
||||
(function($) {
|
||||
var currentPage = 1;
|
||||
|
||||
function levelBadge(level) {
|
||||
var colors = { info: '#2271b1', warning: '#dba617', error: '#d63638', debug: '#8c8f94' };
|
||||
var bg = { info: '#f0f6fc', warning: '#fcf9e8', error: '#fcf0f1', debug: '#f0f0f1' };
|
||||
return '<span style="display:inline-block;padding:2px 8px;border-radius:3px;font-size:12px;font-weight:500;color:' +
|
||||
(colors[level] || '#666') + ';background:' + (bg[level] || '#f0f0f1') + ';">' + level + '</span>';
|
||||
}
|
||||
|
||||
function loadLogs(page) {
|
||||
currentPage = page || 1;
|
||||
var $body = $('#peptide-log-body');
|
||||
$body.html('<tr><td colspan="4" style="text-align:center;color:#999;">Loading...</td></tr>');
|
||||
|
||||
$.post(peptideNewsAdmin.ajax_url, {
|
||||
action: 'peptide_news_get_logs',
|
||||
nonce: peptideNewsAdmin.admin_nonce,
|
||||
page: currentPage,
|
||||
level: $('#peptide-log-level').val(),
|
||||
context: $('#peptide-log-context').val()
|
||||
}, function(response) {
|
||||
if (!response.success) {
|
||||
$body.html('<tr><td colspan="4" style="color:#d63638;">Error loading logs.</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = response.data.rows;
|
||||
if (rows.length === 0) {
|
||||
$body.html('<tr><td colspan="4" style="text-align:center;color:#999;">No log entries.</td></tr>');
|
||||
$('#peptide-log-pagination').empty();
|
||||
$('#peptide-log-status').text('0 entries');
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
$.each(rows, function(i, row) {
|
||||
html += '<tr>';
|
||||
html += '<td style="font-size:12px;white-space:nowrap;">' + $('<span>').text(row.created_at).html() + '</td>';
|
||||
html += '<td>' + levelBadge(row.level) + '</td>';
|
||||
html += '<td style="font-size:12px;">' + $('<span>').text(row.context).html() + '</td>';
|
||||
html += '<td style="font-size:13px;word-break:break-word;">' + $('<span>').text(row.message).html() + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
$body.html(html);
|
||||
|
||||
// Pagination.
|
||||
var totalPages = response.data.total_pages;
|
||||
var pagHtml = '<span style="color:#666;font-size:12px;">Page ' + currentPage + ' of ' + totalPages + ' (' + response.data.total + ' entries)</span>';
|
||||
if (currentPage > 1) {
|
||||
pagHtml += ' <button type="button" class="button button-small peptide-log-page" data-page="' + (currentPage - 1) + '">« Prev</button>';
|
||||
}
|
||||
if (currentPage < totalPages) {
|
||||
pagHtml += ' <button type="button" class="button button-small peptide-log-page" data-page="' + (currentPage + 1) + '">Next »</button>';
|
||||
}
|
||||
$('#peptide-log-pagination').html(pagHtml);
|
||||
$('#peptide-log-status').text(response.data.total + ' entries');
|
||||
});
|
||||
}
|
||||
|
||||
// Bindings.
|
||||
$('#peptide-log-refresh').on('click', function() { loadLogs(1); });
|
||||
$('#peptide-log-level, #peptide-log-context').on('change', function() { loadLogs(1); });
|
||||
$(document).on('click', '.peptide-log-page', function() { loadLogs($(this).data('page')); });
|
||||
|
||||
$('#peptide-log-clear').on('click', function() {
|
||||
if (!confirm('<?php echo esc_js( __( 'Clear all log entries?', 'peptide-news' ) ); ?>')) return;
|
||||
$.post(peptideNewsAdmin.ajax_url, {
|
||||
action: 'peptide_news_clear_logs',
|
||||
nonce: peptideNewsAdmin.admin_nonce
|
||||
}, function(response) {
|
||||
if (response.success) loadLogs(1);
|
||||
});
|
||||
});
|
||||
|
||||
// Initial load.
|
||||
loadLogs(1);
|
||||
})(jQuery);
|
||||
</script>
|
||||
<?php
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ class Peptide_News_Activator {
|
||||
self::create_tables();
|
||||
self::set_default_options();
|
||||
self::schedule_cron();
|
||||
|
||||
// Create the log table (idempotent via dbDelta).
|
||||
if ( class_exists( 'Peptide_News_Logger' ) ) {
|
||||
Peptide_News_Logger::create_table();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,11 +44,13 @@ class Peptide_News_Fetcher {
|
||||
// Prevent overlapping fetch jobs.
|
||||
$lock_key = 'peptide_news_fetch_lock';
|
||||
if ( get_transient( $lock_key ) ) {
|
||||
$this->log_error( 'Fetch already in progress. Skipping this cycle.' );
|
||||
Peptide_News_Logger::warning( 'Fetch already in progress — skipping this cycle.', 'fetch' );
|
||||
return;
|
||||
}
|
||||
set_transient( $lock_key, true, 300 ); // 5-minute lock.
|
||||
|
||||
Peptide_News_Logger::info( 'Fetch cycle started.', 'fetch' );
|
||||
|
||||
$articles = array();
|
||||
|
||||
// Fetch from RSS feeds.
|
||||
@@ -89,6 +91,11 @@ class Peptide_News_Fetcher {
|
||||
'new_stored' => $stored,
|
||||
) );
|
||||
|
||||
Peptide_News_Logger::info( sprintf(
|
||||
'Fetch complete: %d found, %d filtered out, %d new stored.',
|
||||
$pre_filter_count, $filtered_out, $stored
|
||||
), 'fetch' );
|
||||
|
||||
// Run AI analysis on newly fetched articles (keywords + summary).
|
||||
if ( class_exists( 'Peptide_News_LLM' ) && Peptide_News_LLM::is_enabled() ) {
|
||||
$llm_batch_size = absint( get_option( 'peptide_news_llm_max_articles', 10 ) );
|
||||
|
||||
@@ -75,14 +75,15 @@ class Peptide_News_LLM {
|
||||
if ( ! is_wp_error( $keywords_result ) ) {
|
||||
$results['keywords'] = self::sanitize_keywords( $keywords_result );
|
||||
$any_success = true;
|
||||
Peptide_News_Logger::debug( 'Keywords extracted for article #' . $article->id, 'llm' );
|
||||
} else {
|
||||
$err_msg = 'Keywords (' . $keywords_model . '): ' . $keywords_result->get_error_message();
|
||||
$results['errors'][] = $err_msg;
|
||||
self::log_error( 'Keyword extraction failed for article ' . $article->id . ': ' . $keywords_result->get_error_message() );
|
||||
Peptide_News_Logger::error( 'Keyword extraction failed for article #' . $article->id . ': ' . $keywords_result->get_error_message(), 'llm' );
|
||||
}
|
||||
} elseif ( $need_keywords ) {
|
||||
$results['errors'][] = 'Invalid keywords model ID: ' . $keywords_model;
|
||||
self::log_error( 'Invalid keywords model ID: ' . $keywords_model );
|
||||
Peptide_News_Logger::error( 'Invalid keywords model ID: ' . $keywords_model, 'llm' );
|
||||
}
|
||||
|
||||
// --- Summarization ---
|
||||
@@ -97,14 +98,15 @@ class Peptide_News_LLM {
|
||||
// Strip all HTML tags from LLM output to prevent XSS.
|
||||
$results['summary'] = sanitize_textarea_field( wp_strip_all_tags( $summary_result ) );
|
||||
$any_success = true;
|
||||
Peptide_News_Logger::info( 'AI summary generated for article #' . $article->id . ': ' . mb_substr( $article->title, 0, 60 ), 'llm' );
|
||||
} else {
|
||||
$err_msg = 'Summary (' . $summary_model . '): ' . $summary_result->get_error_message();
|
||||
$results['errors'][] = $err_msg;
|
||||
self::log_error( 'Summarization failed for article ' . $article->id . ': ' . $summary_result->get_error_message() );
|
||||
Peptide_News_Logger::error( 'Summarization failed for article #' . $article->id . ': ' . $summary_result->get_error_message(), 'llm' );
|
||||
}
|
||||
} elseif ( $need_summary ) {
|
||||
$results['errors'][] = 'Invalid summary model ID: ' . $summary_model;
|
||||
self::log_error( 'Invalid summary model ID: ' . $summary_model );
|
||||
Peptide_News_Logger::error( 'Invalid summary model ID: ' . $summary_model, 'llm' );
|
||||
}
|
||||
|
||||
$results['success'] = $any_success;
|
||||
@@ -198,6 +200,12 @@ class Peptide_News_LLM {
|
||||
'errors' => $last_errors,
|
||||
) );
|
||||
|
||||
if ( $processed > 0 ) {
|
||||
Peptide_News_Logger::info( sprintf( 'AI batch complete: %d/%d articles processed.', $processed, count( $articles ) ), 'llm' );
|
||||
} elseif ( count( $articles ) > 0 ) {
|
||||
Peptide_News_Logger::warning( sprintf( 'AI batch: 0/%d articles succeeded.', count( $articles ) ), 'llm' );
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
@@ -407,6 +415,8 @@ class Peptide_News_LLM {
|
||||
wp_send_json_error( 'AI Analysis is not enabled. Please enable it and set an OpenRouter API key in Peptide News → Settings.' );
|
||||
}
|
||||
|
||||
Peptide_News_Logger::info( 'Bulk AI summary generation triggered by admin.', 'llm' );
|
||||
|
||||
// Auto-correct known broken model names to a working free model.
|
||||
$broken_models = array(
|
||||
'qwen/qwen3.6-plus:free',
|
||||
|
||||
254
includes/class-peptide-news-logger.php
Normal file
254
includes/class-peptide-news-logger.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Logger.
|
||||
*
|
||||
* Provides structured logging to a custom database table with levels,
|
||||
* contexts, and an admin-facing viewer with AJAX pagination and clear.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
class Peptide_News_Logger {
|
||||
|
||||
/** Log level constants. */
|
||||
const LEVEL_INFO = 'info';
|
||||
const LEVEL_WARNING = 'warning';
|
||||
const LEVEL_ERROR = 'error';
|
||||
const LEVEL_DEBUG = 'debug';
|
||||
|
||||
/** Maximum rows kept in the log table (auto-pruned on write). */
|
||||
const MAX_ROWS = 2000;
|
||||
|
||||
/**
|
||||
* Return the log table name.
|
||||
*/
|
||||
public static function table() {
|
||||
global $wpdb;
|
||||
return $wpdb->prefix . 'peptide_news_log';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Writing
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Write a log entry.
|
||||
*
|
||||
* @param string $level One of the LEVEL_* constants.
|
||||
* @param string $message Human-readable message.
|
||||
* @param string $context Category tag (fetch, llm, admin, cron, general).
|
||||
*/
|
||||
public static function log( $level, $message, $context = 'general' ) {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->insert(
|
||||
self::table(),
|
||||
array(
|
||||
'level' => $level,
|
||||
'context' => $context,
|
||||
'message' => $message,
|
||||
'created_at' => current_time( 'mysql' ),
|
||||
),
|
||||
array( '%s', '%s', '%s', '%s' )
|
||||
);
|
||||
|
||||
// Auto-prune old entries.
|
||||
self::maybe_prune();
|
||||
}
|
||||
|
||||
/** Convenience: info level. */
|
||||
public static function info( $message, $context = 'general' ) {
|
||||
self::log( self::LEVEL_INFO, $message, $context );
|
||||
}
|
||||
|
||||
/** Convenience: warning level. */
|
||||
public static function warning( $message, $context = 'general' ) {
|
||||
self::log( self::LEVEL_WARNING, $message, $context );
|
||||
}
|
||||
|
||||
/** Convenience: error level. */
|
||||
public static function error( $message, $context = 'general' ) {
|
||||
self::log( self::LEVEL_ERROR, $message, $context );
|
||||
}
|
||||
|
||||
/** Convenience: debug level (only writes when WP_DEBUG is on). */
|
||||
public static function debug( $message, $context = 'general' ) {
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
self::log( self::LEVEL_DEBUG, $message, $context );
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Reading
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Retrieve log entries.
|
||||
*
|
||||
* @param array $args {
|
||||
* Optional. Query arguments.
|
||||
*
|
||||
* @type string $level Filter by level.
|
||||
* @type string $context Filter by context.
|
||||
* @type int $limit Number of rows.
|
||||
* @type int $offset Pagination offset.
|
||||
* }
|
||||
* @return array { 'rows' => array, 'total' => int }
|
||||
*/
|
||||
public static function get_logs( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$defaults = array(
|
||||
'level' => '',
|
||||
'context' => '',
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
);
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
$table = self::table();
|
||||
$where = array( '1=1' );
|
||||
$vals = array();
|
||||
|
||||
if ( ! empty( $args['level'] ) ) {
|
||||
$where[] = 'level = %s';
|
||||
$vals[] = $args['level'];
|
||||
}
|
||||
if ( ! empty( $args['context'] ) ) {
|
||||
$where[] = 'context = %s';
|
||||
$vals[] = $args['context'];
|
||||
}
|
||||
|
||||
$where_sql = implode( ' AND ', $where );
|
||||
|
||||
// Total count.
|
||||
$count_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}";
|
||||
$total = empty( $vals )
|
||||
? (int) $wpdb->get_var( $count_sql )
|
||||
: (int) $wpdb->get_var( $wpdb->prepare( $count_sql, $vals ) );
|
||||
|
||||
// Rows.
|
||||
$order_sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY id DESC LIMIT %d OFFSET %d";
|
||||
$vals[] = absint( $args['limit'] );
|
||||
$vals[] = absint( $args['offset'] );
|
||||
$rows = $wpdb->get_results( $wpdb->prepare( $order_sql, $vals ) );
|
||||
|
||||
return array(
|
||||
'rows' => $rows ? $rows : array(),
|
||||
'total' => $total,
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Maintenance
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Remove rows beyond the MAX_ROWS cap.
|
||||
*/
|
||||
private static function maybe_prune() {
|
||||
global $wpdb;
|
||||
$table = self::table();
|
||||
|
||||
// Only prune every ~50 writes (probabilistic).
|
||||
if ( wp_rand( 1, 50 ) !== 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
|
||||
|
||||
if ( $count > self::MAX_ROWS ) {
|
||||
$delete_count = $count - self::MAX_ROWS;
|
||||
$wpdb->query( $wpdb->prepare(
|
||||
"DELETE FROM {$table} ORDER BY id ASC LIMIT %d",
|
||||
$delete_count
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all log entries.
|
||||
*/
|
||||
public static function clear() {
|
||||
global $wpdb;
|
||||
$wpdb->query( "TRUNCATE TABLE " . self::table() );
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* AJAX handlers
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* AJAX: fetch paginated log entries.
|
||||
*/
|
||||
public static function ajax_get_logs() {
|
||||
check_ajax_referer( 'peptide_news_admin', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( 'Unauthorized', 403 );
|
||||
}
|
||||
|
||||
$page = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
|
||||
$level = isset( $_POST['level'] ) ? sanitize_text_field( $_POST['level'] ) : '';
|
||||
$context = isset( $_POST['context'] ) ? sanitize_text_field( $_POST['context'] ) : '';
|
||||
$per_page = 50;
|
||||
|
||||
$result = self::get_logs( array(
|
||||
'level' => $level,
|
||||
'context' => $context,
|
||||
'limit' => $per_page,
|
||||
'offset' => ( $page - 1 ) * $per_page,
|
||||
) );
|
||||
|
||||
wp_send_json_success( array(
|
||||
'rows' => $result['rows'],
|
||||
'total' => $result['total'],
|
||||
'page' => $page,
|
||||
'total_pages' => ceil( $result['total'] / $per_page ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: clear all log entries.
|
||||
*/
|
||||
public static function ajax_clear_logs() {
|
||||
check_ajax_referer( 'peptide_news_admin', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( 'Unauthorized', 403 );
|
||||
}
|
||||
|
||||
self::clear();
|
||||
self::info( 'Log cleared by admin.', 'admin' );
|
||||
|
||||
wp_send_json_success( array( 'message' => 'Log cleared.' ) );
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Schema
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Create the log table (called from activator / upgrade check).
|
||||
*/
|
||||
public static function create_table() {
|
||||
global $wpdb;
|
||||
|
||||
$table = self::table();
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
$sql = "CREATE TABLE {$table} (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
level VARCHAR(10) NOT NULL DEFAULT 'info',
|
||||
context VARCHAR(30) NOT NULL DEFAULT 'general',
|
||||
message TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_level (level),
|
||||
KEY idx_context (context),
|
||||
KEY idx_created (created_at)
|
||||
) {$charset_collate};";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta( $sql );
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ class Peptide_News {
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-analytics.php';
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-rest-api.php';
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-llm.php';
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-logger.php';
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-content-filter.php';
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'admin/class-peptide-news-admin.php';
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'public/class-peptide-news-public.php';
|
||||
@@ -82,6 +83,8 @@ class Peptide_News {
|
||||
$this->loader->add_filter( 'cron_schedules', $fetcher, 'add_custom_cron_schedules' );
|
||||
$this->loader->add_action( 'wp_ajax_peptide_news_backfill_sources', $fetcher, 'ajax_backfill_sources' );
|
||||
$this->loader->add_action( 'wp_ajax_peptide_news_generate_summaries', 'Peptide_News_LLM', 'ajax_generate_summaries' );
|
||||
$this->loader->add_action( 'wp_ajax_peptide_news_get_logs', 'Peptide_News_Logger', 'ajax_get_logs' );
|
||||
$this->loader->add_action( 'wp_ajax_peptide_news_clear_logs', 'Peptide_News_Logger', 'ajax_clear_logs' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: Peptide News Aggregator
|
||||
* Plugin URI: https://github.com/peptiderepo/peptide-news-plugin
|
||||
* Description: Aggregates and displays the latest peptide research news from multiple sources with click analytics and trend reporting.
|
||||
* Version: 2.0.1
|
||||
* Version: 2.1.0
|
||||
* Author: Peptide News Team
|
||||
* Author URI: https://github.com/peptiderepo
|
||||
* License: GPL-2.0+
|
||||
@@ -22,7 +22,7 @@ if ( ! defined( 'WPINC' ) ) {
|
||||
/**
|
||||
* Current plugin version — follows SemVer.
|
||||
*/
|
||||
define( 'PEPTIDE_NEWS_VERSION', '2.0.1' );
|
||||
define( 'PEPTIDE_NEWS_VERSION', '2.1.0' );
|
||||
define( 'PEPTIDE_NEWS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'PEPTIDE_NEWS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'PEPTIDE_NEWS_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||
@@ -60,6 +60,7 @@ function peptide_news_check_db_upgrade() {
|
||||
$installed_version = get_option( 'peptide_news_db_version', '1.0.0' );
|
||||
if ( version_compare( $installed_version, PEPTIDE_NEWS_VERSION, '<' ) ) {
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-activator.php';
|
||||
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-logger.php';
|
||||
Peptide_News_Activator::activate();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user