diff --git a/admin/partials/articles-list.php b/admin/partials/articles-list.php
index 8b898bf..d0c8d6c 100644
--- a/admin/partials/articles-list.php
+++ b/admin/partials/articles-list.php
@@ -6,7 +6,7 @@
*/
if ( ! defined( 'ABSPATH' ) ) {
- exit;
+ exit;
}
global $wpdb;
@@ -16,80 +16,200 @@ $page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1;
$per_page = 25;
$offset = ( $page - 1 ) * $per_page;
+// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$total = $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
+// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$articles = $wpdb->get_results( $wpdb->prepare(
- "SELECT * FROM {$table} ORDER BY published_at DESC LIMIT %d OFFSET %d",
- $per_page,
- $offset
+ "SELECT * FROM {$table} ORDER BY published_at DESC LIMIT %d OFFSET %d",
+ $per_page,
+ $offset
) );
$total_pages = ceil( $total / $per_page );
?>
-
()
+
()
-
-
-
- | ID |
- |
- |
- |
- |
- |
- |
- |
-
-
-
-
- |
-
-
-
- | id ); ?> |
-
-
- title, 12 ) ); ?>
-
- |
- source ); ?> |
-
- tags ) ) : ?>
- tags, 10 ) ); ?>
-
-
-
- |
-
- ai_summary ) ) : ?>
- ai_summary, 15 ) ); ?>
-
-
-
- |
- published_at ) ) ); ?> |
- fetched_at ) ) ); ?> |
- is_active ? '✓' : '✗'; ?> |
-
-
-
-
-
+
- 1 ) : ?>
-
-
- add_query_arg( 'paged', '%#%' ),
- 'format' => '',
- 'current' => $page,
- 'total' => $total_pages,
- ) );
- ?>
-
-
-
+
+
+ 1 ) : ?>
+
+
+ add_query_arg( 'paged', '%#%' ),
+ 'format' => '',
+ 'current' => $page,
+ 'total' => $total_pages,
+ ) );
+ ?>
+
+
+
+
+
diff --git a/includes/class-peptide-news-rest-api.php b/includes/class-peptide-news-rest-api.php
index 6135b09..c63a6ee 100644
--- a/includes/class-peptide-news-rest-api.php
+++ b/includes/class-peptide-news-rest-api.php
@@ -377,4 +377,58 @@ class Peptide_News_Rest_API {
'count' => count( $data ),
) );
}
+
+ /**
+ * AJAX handler: delete one or more articles by ID.
+ *
+ * Expects POST with 'ids' (comma-separated article IDs) and 'nonce'.
+ * Cascading foreign keys on the clicks and daily_stats tables handle
+ * related analytics cleanup automatically.
+ *
+ * @since 2.1.0
+ */
+ public static function ajax_delete_articles() {
+ check_ajax_referer( 'peptide_news_admin', 'nonce' );
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( 'Unauthorized.', 403 );
+ }
+
+ $raw_ids = isset( $_POST['ids'] ) ? sanitize_text_field( wp_unslash( $_POST['ids'] ) ) : '';
+ if ( empty( $raw_ids ) ) {
+ wp_send_json_error( 'No article IDs provided.' );
+ }
+
+ $ids = array_filter( array_map( 'absint', explode( ',', $raw_ids ) ) );
+ if ( empty( $ids ) ) {
+ wp_send_json_error( 'Invalid article IDs.' );
+ }
+
+ global $wpdb;
+ $table = $wpdb->prefix . 'peptide_news_articles';
+ $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $deleted = $wpdb->query(
+ $wpdb->prepare(
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ "DELETE FROM {$table} WHERE id IN ({$placeholders})",
+ $ids
+ )
+ );
+
+ if ( false === $deleted ) {
+ wp_send_json_error( 'Database error.' );
+ }
+
+ // Clear article cache so the frontend reflects changes.
+ delete_transient( 'peptide_news_articles_1_10' );
+
+ Peptide_News_Logger::info( 'Deleted ' . $deleted . ' article(s): IDs ' . implode( ', ', $ids ), 'admin' );
+
+ wp_send_json_success( array(
+ 'deleted' => $deleted,
+ 'ids' => $ids,
+ ) );
+ }
}
diff --git a/includes/class-peptide-news.php b/includes/class-peptide-news.php
index 669cbb0..5c74fdd 100644
--- a/includes/class-peptide-news.php
+++ b/includes/class-peptide-news.php
@@ -85,6 +85,7 @@ class Peptide_News {
$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' );
+ $this->loader->add_action( 'wp_ajax_peptide_news_delete_articles', 'Peptide_News_Rest_API', 'ajax_delete_articles' );
}
/**