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, - ) ); - ?> -
-
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + 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, + ) ); + ?> +
+
+
+ + 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' ); } /**