diff --git a/admin/class-peptide-news-admin.php b/admin/class-peptide-news-admin.php new file mode 100644 index 0000000..080143d --- /dev/null +++ b/admin/class-peptide-news-admin.php @@ -0,0 +1,401 @@ +plugin_name = $plugin_name; + $this->version = $version; + } + + /** + * Enqueue admin CSS. + */ + public function enqueue_styles( $hook ) { + if ( strpos( $hook, 'peptide-news' ) === false ) { + return; + } + wp_enqueue_style( + $this->plugin_name . '-admin', + PEPTIDE_NEWS_PLUGIN_URL . 'admin/css/admin-style.css', + array(), + $this->version, + 'all' + ); + } + + /** + * Enqueue admin JS. + */ + public function enqueue_scripts( $hook ) { + if ( strpos( $hook, 'peptide-news' ) === false ) { + return; + } + + // Chart.js for analytics dashboard. + wp_enqueue_script( + 'chartjs', + 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js', + array(), + '4.4.0', + true + ); + + wp_enqueue_script( + $this->plugin_name . '-admin', + PEPTIDE_NEWS_PLUGIN_URL . 'admin/js/admin-script.js', + array( 'jquery', 'chartjs' ), + $this->version, + true + ); + + wp_localize_script( $this->plugin_name . '-admin', 'peptideNewsAdmin', array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'rest_url' => rest_url( 'peptide-news/v1/' ), + 'nonce' => wp_create_nonce( 'wp_rest' ), + ) ); + } + + /** + * Add admin menu pages. + */ + public function add_admin_menu() { + // Main menu — Analytics Dashboard. + add_menu_page( + __( 'Peptide News', 'peptide-news' ), + __( 'Peptide News', 'peptide-news' ), + 'manage_options', + 'peptide-news-dashboard', + array( $this, 'render_dashboard_page' ), + 'dashicons-rss', + 30 + ); + + // Sub-menu — Settings. + add_submenu_page( + 'peptide-news-dashboard', + __( 'Settings', 'peptide-news' ), + __( 'Settings', 'peptide-news' ), + 'manage_options', + 'peptide-news-settings', + array( $this, 'render_settings_page' ) + ); + + // Sub-menu — Articles. + add_submenu_page( + 'peptide-news-dashboard', + __( 'Articles', 'peptide-news' ), + __( 'Articles', 'peptide-news' ), + 'manage_options', + 'peptide-news-articles', + array( $this, 'render_articles_page' ) + ); + } + + /** + * Register all settings. + */ + public function register_settings() { + + // --- General section --- + add_settings_section( + 'peptide_news_general', + __( 'General Settings', 'peptide-news' ), + function () { + echo '

' . esc_html__( 'Configure how and when peptide news articles are fetched.', 'peptide-news' ) . '

'; + }, + 'peptide-news-settings' + ); + + $this->add_setting( 'fetch_interval', __( 'Fetch Interval', 'peptide-news' ), 'render_fetch_interval_field', 'peptide_news_general' ); + $this->add_setting( 'articles_count', __( 'Articles to Display', 'peptide-news' ), 'render_articles_count_field', 'peptide_news_general' ); + $this->add_setting( 'search_keywords', __( 'Search Keywords', 'peptide-news' ), 'render_search_keywords_field', 'peptide_news_general' ); + + // --- RSS section --- + add_settings_section( + 'peptide_news_rss', + __( 'RSS Feed Sources', 'peptide-news' ), + function () { + echo '

' . esc_html__( 'Add RSS feed URLs, one per line.', 'peptide-news' ) . '

'; + }, + 'peptide-news-settings' + ); + + $this->add_setting( 'rss_enabled', __( 'Enable RSS Feeds', 'peptide-news' ), 'render_rss_enabled_field', 'peptide_news_rss' ); + $this->add_setting( 'rss_feeds', __( 'RSS Feed URLs', 'peptide-news' ), 'render_rss_feeds_field', 'peptide_news_rss' ); + + // --- NewsAPI section --- + add_settings_section( + 'peptide_news_newsapi', + __( 'NewsAPI.org', 'peptide-news' ), + function () { + echo '

' . esc_html__( 'Optional. Get a free API key at newsapi.org.', 'peptide-news' ) . '

'; + }, + 'peptide-news-settings' + ); + + $this->add_setting( 'newsapi_enabled', __( 'Enable NewsAPI', 'peptide-news' ), 'render_newsapi_enabled_field', 'peptide_news_newsapi' ); + $this->add_setting( 'newsapi_key', __( 'API Key', 'peptide-news' ), 'render_newsapi_key_field', 'peptide_news_newsapi' ); + + // --- Analytics section --- + add_settings_section( + 'peptide_news_analytics_section', + __( 'Analytics', 'peptide-news' ), + function () { + echo '

' . esc_html__( 'Configure click tracking and data retention.', 'peptide-news' ) . '

'; + }, + 'peptide-news-settings' + ); + + $this->add_setting( 'article_retention', __( 'Article Retention (days)', 'peptide-news' ), 'render_article_retention_field', 'peptide_news_analytics_section' ); + $this->add_setting( 'analytics_retention', __( 'Analytics Data Retention (days)', 'peptide-news' ), 'render_retention_field', 'peptide_news_analytics_section' ); + $this->add_setting( 'anonymize_ip', __( 'Anonymize IPs', 'peptide-news' ), 'render_anonymize_ip_field', 'peptide_news_analytics_section' ); + } + + /** + * Helper: register a single setting with a field callback. + */ + private function add_setting( $key, $label, $callback, $section ) { + $option_name = "peptide_news_{$key}"; + + register_setting( 'peptide_news_settings_group', $option_name ); + + add_settings_field( + $option_name, + $label, + array( $this, $callback ), + 'peptide-news-settings', + $section + ); + } + + // --- Field renderers --- + + public function render_fetch_interval_field() { + $value = get_option( 'peptide_news_fetch_interval', 'twicedaily' ); + $options = array( + 'every_fifteen_minutes' => __( 'Every 15 minutes', 'peptide-news' ), + 'every_thirty_minutes' => __( 'Every 30 minutes', 'peptide-news' ), + 'hourly' => __( 'Hourly', 'peptide-news' ), + 'every_four_hours' => __( 'Every 4 hours', 'peptide-news' ), + 'every_six_hours' => __( 'Every 6 hours', 'peptide-news' ), + 'twicedaily' => __( 'Twice daily', 'peptide-news' ), + 'daily' => __( 'Daily', 'peptide-news' ), + ); + + echo ''; + echo '

' . esc_html__( 'How often to check for new articles. After changing, deactivate and reactivate the plugin.', 'peptide-news' ) . '

'; + } + + public function render_articles_count_field() { + $value = get_option( 'peptide_news_articles_count', 10 ); + printf( + '', + absint( $value ) + ); + echo '

' . esc_html__( 'Number of articles to display in the shortcode/widget.', 'peptide-news' ) . '

'; + } + + public function render_search_keywords_field() { + $value = get_option( 'peptide_news_search_keywords', '' ); + printf( + '', + esc_attr( $value ) + ); + echo '

' . esc_html__( 'Comma-separated keywords for NewsAPI searches.', 'peptide-news' ) . '

'; + } + + public function render_rss_enabled_field() { + $value = get_option( 'peptide_news_rss_enabled', 1 ); + printf( + '', + checked( $value, 1, false ), + esc_html__( 'Fetch articles from RSS feeds', 'peptide-news' ) + ); + } + + public function render_rss_feeds_field() { + $value = get_option( 'peptide_news_rss_feeds', '' ); + printf( + '', + esc_textarea( $value ) + ); + } + + public function render_newsapi_enabled_field() { + $value = get_option( 'peptide_news_newsapi_enabled', 0 ); + printf( + '', + checked( $value, 1, false ), + esc_html__( 'Fetch articles from NewsAPI.org', 'peptide-news' ) + ); + } + + public function render_newsapi_key_field() { + $value = get_option( 'peptide_news_newsapi_key', '' ); + printf( + '', + esc_attr( $value ) + ); + } + + public function render_article_retention_field() { + $value = get_option( 'peptide_news_article_retention', 90 ); + printf( + '', + absint( $value ) + ); + echo '

' . esc_html__( 'How long to keep fetched articles before deactivating them.', 'peptide-news' ) . '

'; + } + + public function render_retention_field() { + $value = get_option( 'peptide_news_analytics_retention', 365 ); + printf( + '', + absint( $value ) + ); + echo '

' . esc_html__( 'How long to keep click analytics data.', 'peptide-news' ) . '

'; + } + + public function render_anonymize_ip_field() { + $value = get_option( 'peptide_news_anonymize_ip', 1 ); + printf( + '', + checked( $value, 1, false ), + esc_html__( 'Anonymize IP addresses for GDPR compliance', 'peptide-news' ) + ); + } + + // --- Page renderers --- + + public function render_settings_page() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + // Handle manual fetch trigger. + if ( isset( $_POST['peptide_news_fetch_now'] ) && check_admin_referer( 'peptide_news_fetch_now_action' ) ) { + $fetcher = new Peptide_News_Fetcher(); + $fetcher->fetch_all_sources(); + echo '

' . esc_html__( 'Fetch completed!', 'peptide-news' ) . '

'; + } + + echo '
'; + echo '

' . esc_html( get_admin_page_title() ) . '

'; + + // Fetch Now button. + echo '
'; + wp_nonce_field( 'peptide_news_fetch_now_action' ); + submit_button( __( 'Fetch Articles Now', 'peptide-news' ), 'secondary', 'peptide_news_fetch_now' ); + echo '
'; + + // Last fetch info. + $last_fetch = get_option( 'peptide_news_last_fetch' ); + if ( $last_fetch ) { + printf( + '

%s: %s | %s: %d | %s: %d

', + esc_html__( 'Last fetch', 'peptide-news' ), + esc_html( $last_fetch['time'] ), + esc_html__( 'Found', 'peptide-news' ), + $last_fetch['found'], + esc_html__( 'New stored', 'peptide-news' ), + $last_fetch['new_stored'] + ); + } + + // Settings form. + echo '
'; + settings_fields( 'peptide_news_settings_group' ); + do_settings_sections( 'peptide-news-settings' ); + submit_button(); + echo '
'; + + // Shortcode usage info. + echo '

' . esc_html__( 'Usage', 'peptide-news' ) . '

'; + echo '

' . esc_html__( 'Shortcode:', 'peptide-news' ) . ' [peptide_news] ' . + esc_html__( 'or', 'peptide-news' ) . ' [peptide_news count="5"]

'; + echo '

' . esc_html__( 'Widget:', 'peptide-news' ) . ' ' . + esc_html__( 'Add "Peptide News" from Appearance > Widgets.', 'peptide-news' ) . '

'; + + echo '
'; + } + + public function render_dashboard_page() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + // Handle CSV export before any HTML output. + if ( isset( $_GET['export'] ) && 'csv' === $_GET['export'] ) { + check_admin_referer( 'peptide_news_export_csv' ); + $this->export_csv(); + return; + } + + include PEPTIDE_NEWS_PLUGIN_DIR . 'admin/partials/dashboard.php'; + } + + /** + * Stream a CSV export of click analytics data. + */ + private function export_csv() { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'Unauthorized.', 'peptide-news' ) ); + } + + $raw_start = isset( $_GET['start_date'] ) ? sanitize_text_field( wp_unslash( $_GET['start_date'] ) ) : ''; + $raw_end = isset( $_GET['end_date'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date'] ) ) : ''; + $start_date = preg_match( '/^\d{4}-\d{2}-\d{2}$/', $raw_start ) ? $raw_start : gmdate( 'Y-m-d', strtotime( '-30 days' ) ); + $end_date = preg_match( '/^\d{4}-\d{2}-\d{2}$/', $raw_end ) ? $raw_end : gmdate( 'Y-m-d' ); + + $data = Peptide_News_Analytics::export_clicks_csv( $start_date, $end_date ); + + $filename = 'peptide-news-clicks-' . $start_date . '-to-' . $end_date . '.csv'; + + header( 'Content-Type: text/csv; charset=utf-8' ); + header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); + header( 'Pragma: no-cache' ); + header( 'Expires: 0' ); + + $output = fopen( 'php://output', 'w' ); + + if ( ! empty( $data ) ) { + fputcsv( $output, array_keys( $data[0] ) ); + foreach ( $data as $row ) { + fputcsv( $output, $row ); + } +   ( } else { + fputcsv( $output, array( 'No data for selected period' ) ); + } + + fclose( $output ); + exit; + } + + public function render_articles_page() { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + include PEPTIDE_NEWS_PLUGIN_DIR . 'admin/partials/articles-list.php'; + } +}