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 )
? $article->thumbnail_url
: $fallback_thumb;
$date = wp_date( 'M j, Y', strtotime( $article->published_at ) );
?>
source ); ?>
·
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,
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;
?>