plugin_name = $plugin_name; $this->version = $version; } /** * Register front-end CSS (enqueued on demand by the shortcode/widget). */ public function enqueue_styles() { wp_register_style( $this->plugin_name, PEPTIDE_NEWS_PLUGIN_URL . 'public/css/public-style.css', array(), $this->version, 'all' ); } /** * Register front-end JS (enqueued on demand by the shortcode/widget). */ public function enqueue_scripts() { wp_register_script( $this->plugin_name, PEPTIDE_NEWS_PLUGIN_URL . 'public/js/public-script.js', array( 'jquery' ), $this->version, true ); wp_localize_script( $this->plugin_name, 'peptideNewsPublic', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'peptide_news_track' ), 'session_id' => $this->get_or_create_session_id(), ) ); } /** * Register the shortcode. */ public function register_shortcode() { add_shortcode( 'peptide_news', array( $this, 'render_shortcode' ) ); } /** * Register the widget. */ public function register_widget() { register_widget( 'Peptide_News_Widget' ); } /** * Render the [peptide_news] shortcode. * * @param array $atts Shortcode attributes. * @return string HTML output. */ public function render_shortcode( $atts ) { $atts = shortcode_atts( array( 'count' => get_option( 'peptide_news_articles_count', 10 ), 'layout' => 'list', // card | compact | list ), $atts, 'peptide_news' ); $articles = $this->get_articles( absint( $atts['count'] ) ); if ( empty( $articles ) ) { return '

' . esc_html__( 'No peptide news articles available yet. Check back soon!', 'peptide-news' ) . '

'; } // Enqueue assets only when the shortcode is actually rendered. wp_enqueue_style( $this->plugin_name ); wp_enqueue_script( $this->plugin_name ); $fallback_thumb = get_option( 'peptide_news_thumbnail_fallback', '' ); $layout = sanitize_key( $atts['layout'] ); ob_start(); ?>
thumbnail_url ) && '_no_image' !== $article->thumbnail_url ) { $thumb = $article->thumbnail_url; } elseif ( ! empty( $article->thumbnail_local ) ) { $upload_dir = wp_upload_dir(); $thumb = $upload_dir['baseurl'] . '/' . $article->thumbnail_local; } if ( empty( $thumb ) ) { $thumb = $fallback_thumb; } $date = wp_date( 'M j, Y', strtotime( $article->published_at ) ); ?>
<?php echo esc_attr( $article->title ); ?>

title ); ?>

ai_summary ) ? $article->ai_summary : ( $article->excerpt ?? '' ); if ( ! empty( $display_text ) ) : ?>

author ) ) : ?> author ); ?> tags ) ? $article->tags : ( $article->categories ?? '' ); if ( ! empty( $tag_source ) ) : ?>
true ) ); } set_transient( $throttle_key, 1, 5 ); $meta = array( 'ip' => '', 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '', 'referrer' => isset( $_POST['referrer'] ) ? esc_url_raw( wp_unslash( $_POST['referrer'] ) ) : '', 'page_url' => isset( $_POST['page_url'] ) ? esc_url_raw( wp_unslash( $_POST['page_url'] ) ) : '', 'session_id' => $session_id, ); $result = Peptide_News_Analytics::record_click( $article_id, $meta ); if ( $result ) { wp_send_json_success(); } else { wp_send_json_error( 'Failed to record click.' ); } } /** * Get active articles from the database. * * @param int $count Number of articles. * @return array */ private function get_articles( $count ) { global $wpdb; $table = $wpdb->prefix . 'peptide_news_articles'; // Use transient cache to avoid DB hits on every page load. $cache_key = 'peptide_news_articles_' . $count; $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } $articles = $wpdb->get_results( $wpdb->prepare( "SELECT id, source, source_url, title, excerpt, ai_summary, author, thumbnail_url, thumbnail_local, published_at, categories, tags FROM {$table} WHERE is_active = 1 ORDER BY published_at DESC LIMIT %d", $count ) ); // Cache for 5 minutes. set_transient( $cache_key, $articles, 5 * MINUTE_IN_SECONDS ); return $articles; } /** * Generate or retrieve a session ID for visitor tracking. * * Sets the cookie in PHP if it doesn't exist so the value * is consistent between the server-side localized script data * and the client-side cookie. * * @return string */ private function get_or_create_session_id() { if ( isset( $_COOKIE['pn_session_id'] ) ) { return sanitize_text_field( wp_unslash( $_COOKIE['pn_session_id'] ) ); } $session_id = wp_generate_uuid4(); // Set a 30-minute session cookie (matches JS-side behaviour). if ( ! headers_sent() ) { setcookie( 'pn_session_id', $session_id, array( 'expires' => time() + 1800, 'path' => '/', 'secure' => is_ssl(), 'httponly' => false, // JS needs read access for sendBeacon. 'samesite' => 'Lax', ) ); } return $session_id; } } /** * Peptide News Widget. * * @since 1.0.0 */ class Peptide_News_Widget extends WP_Widget { public function __construct() { parent::__construct( 'peptide_news_widget', __( 'Peptide News', 'peptide-news' ), array( 'description' => __( 'Displays the latest peptide research news.', 'peptide-news' ), 'classname' => 'peptide-news-widget', ) ); } public function widget( $args, $instance ) { $title = apply_filters( 'widget_title', $instance['title'] ?? __( 'Peptide News', 'peptide-news' ) ); $count = isset( $instance['count'] ) ? absint( $instance['count'] ) : 5; echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . esc_html( $title ) . $args['after_title']; } $public = new Peptide_News_Public( 'peptide-news', PEPTIDE_NEWS_VERSION ); echo $public->render_shortcode( array( 'count' => $count, 'layout' => 'compact' ) ); echo $args['after_widget']; } public function form( $instance ) { $title = isset( $instance['title'] ) ? $instance['title'] : __( 'Peptide News', 'peptide-news' ); $count = isset( $instance['count'] ) ? absint( $instance['count'] ) : 5; ?>