From 377bf22af33850f9eb44f6e190271882d558330c Mon Sep 17 00:00:00 2001
From: peptiderepo <273788462+peptiderepo@users.noreply.github.com>
Date: Wed, 17 Jun 2026 13:16:36 +0700
Subject: [PATCH] fix: PHP 8.3 TypeError on Articles admin page (v2.5.1) (#32)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Squash-merge of PR #32 — QA APPROVED (2026-06-17 SHA 5c483fdc)
Agent-Session: 2026-06-17-phase1-vps-runner-deploy-500fixes
---
CHANGELOG.md | 6 +++
admin/partials/articles-list.php | 2 +-
admin/partials/dashboard.php | 4 +-
peptide-news-plugin.php | 4 +-
tests/unit/ArticlesListTotalCastTest.php | 34 +++++++++++++++++
tests/unit/DashboardTopArticlesCastTest.php | 41 +++++++++++++++++++++
6 files changed, 86 insertions(+), 5 deletions(-)
create mode 100644 tests/unit/ArticlesListTotalCastTest.php
create mode 100644 tests/unit/DashboardTopArticlesCastTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba2b9af..e47b91d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to Peptide News Plugin are documented here.
Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
Versioning: [Semantic Versioning](https://semver.org/).
+## [2.5.1] - 2026-06-17
+
+### Fixed
+- PHP 8.3 `TypeError` on Articles admin page: `number_format()` rejects the numeric string returned by `$wpdb->get_var()`. Cast `$total` to `(int)` at point of assignment in `admin/partials/articles-list.php` (#32).
+- PHP 8.3 `TypeError` on Analytics Dashboard page: `number_format()` rejects SUM aggregate strings returned by `$wpdb->get_results()`. Cast `$article->total_clicks` and `$article->total_unique` to `(int)` inline in `admin/partials/dashboard.php` (same-class fix, no PR number — follow-on to #32).
+
## [2.5.0] — 2026-06-16
### Changed
diff --git a/admin/partials/articles-list.php b/admin/partials/articles-list.php
index c13b872..9b3ed5c 100755
--- a/admin/partials/articles-list.php
+++ b/admin/partials/articles-list.php
@@ -19,7 +19,7 @@ $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}" );
+$total = (int) $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",
diff --git a/admin/partials/dashboard.php b/admin/partials/dashboard.php
index c79f5ad..395cc2f 100755
--- a/admin/partials/dashboard.php
+++ b/admin/partials/dashboard.php
@@ -105,8 +105,8 @@ $total_articles = count( $top_articles );
title ); ?> |
source ); ?> |
categories ); ?> |
- total_clicks ) ); ?> |
- total_unique ) ); ?> |
+ total_clicks ) ); ?> |
+ total_unique ) ); ?> |
diff --git a/peptide-news-plugin.php b/peptide-news-plugin.php
index dfe77bd..8eb2490 100755
--- a/peptide-news-plugin.php
+++ b/peptide-news-plugin.php
@@ -3,7 +3,7 @@
* Plugin Name: Peptide News Aggregator
* Plugin URI: https://github.com/peptiderepo/peptide-news-plugin
* Description: Aggregates and displays the latest peptide research news from multiple sources with click analytics and trend reporting.
- * Version: 2.5.0
+ * Version: 2.5.1
* Author: peptiderepo
* Author URI: https://github.com/peptiderepo
* License: GPL-2.0+
@@ -24,7 +24,7 @@ if ( ! defined( 'WPINC' ) ) {
/**
* Current plugin version — follows SemVer.
*/
-define( 'PEPTIDE_NEWS_VERSION', '2.5.0' );
+define( 'PEPTIDE_NEWS_VERSION', '2.5.1' );
define( 'PEPTIDE_NEWS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'PEPTIDE_NEWS_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'PEPTIDE_NEWS_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
diff --git a/tests/unit/ArticlesListTotalCastTest.php b/tests/unit/ArticlesListTotalCastTest.php
new file mode 100644
index 0000000..b671fd9
--- /dev/null
+++ b/tests/unit/ArticlesListTotalCastTest.php
@@ -0,0 +1,34 @@
+get_var returning a numeric string does not
+ * cause a TypeError in number_format() (PHP 8.3 strict typing).
+ */
+ public function test_total_cast_prevents_type_error() {
+ $total_as_string = '451'; // what $wpdb->get_var() returns
+ $total = (int) $total_as_string;
+ $this->assertIsInt( $total );
+ $this->assertSame( 451, $total );
+ // This should not throw TypeError on PHP 8.3
+ $formatted = number_format( $total );
+ $this->assertSame( '451', $formatted );
+ }
+
+ /**
+ * Verify null return (missing table) results in 0, not a TypeError.
+ */
+ public function test_null_total_cast_to_zero() {
+ $total = (int) null;
+ $this->assertSame( 0, $total );
+ $formatted = number_format( $total );
+ $this->assertSame( '0', $formatted );
+ }
+}
diff --git a/tests/unit/DashboardTopArticlesCastTest.php b/tests/unit/DashboardTopArticlesCastTest.php
new file mode 100644
index 0000000..10ff37f
--- /dev/null
+++ b/tests/unit/DashboardTopArticlesCastTest.php
@@ -0,0 +1,41 @@
+get_results() do not
+ * cause a TypeError in number_format() (PHP 8.3 strict typing).
+ * Mirrors the fix in admin/partials/dashboard.php lines 108-109.
+ */
+ public function test_total_clicks_cast_prevents_type_error() {
+ $article = (object) array(
+ 'total_clicks' => '1234', // numeric string from $wpdb->get_results()
+ 'total_unique' => '987',
+ );
+ // Simulate the inline cast applied in dashboard.php
+ $formatted_clicks = number_format( (int) $article->total_clicks );
+ $formatted_unique = number_format( (int) $article->total_unique );
+ $this->assertSame( '1,234', $formatted_clicks );
+ $this->assertSame( '987', $formatted_unique );
+ }
+
+ /**
+ * Verify NULL SUM() (no rows) results in 0, not a TypeError.
+ */
+ public function test_null_sum_cast_to_zero() {
+ $article = (object) array(
+ 'total_clicks' => null,
+ 'total_unique' => null,
+ );
+ $formatted_clicks = number_format( (int) $article->total_clicks );
+ $formatted_unique = number_format( (int) $article->total_unique );
+ $this->assertSame( '0', $formatted_clicks );
+ $this->assertSame( '0', $formatted_unique );
+ }
+}