61 Commits

Author SHA1 Message Date
opencode
cae4853696 docs: clarify GET requires query token, POST accepts header token in permission docblock 2026-08-03 21:47:05 +08:00
opencode
42833e96b2 fix: require token as query param on GET /worker/pending to close LiteSpeed cache bypass 2026-08-03 21:38:33 +08:00
opencode
aa9e12faef fix: prevent LiteSpeed caching of /worker/* REST responses 2026-08-03 20:53:22 +08:00
opencode
7d2f795f99 feat: v2.6.0 3-step AI pipeline, VPS worker, REST worker routes
- 3-step OpenRouter pipeline: Gatekeeper scoring/classification, PubMed+Google URL extraction, Markdown summary generation
- REST worker routes /wp-json/peptide-news/v1/worker/* with X-Peptide-News-Token auth
- Standalone Coolify worker (tools/vps-worker) with Dockerfile + docker-compose
- CLI commands, content extractor, pubmed extractor, google url resolver
- 89 PHPUnit tests passing
2026-08-03 19:20:47 +08:00
peptiderepo
ff474326fb Adopt reusable CI standard (#30)
Squash-merged from claude/adopt-ci-20260616.

QA-approved; all CI checks green on 3b4e3f04.

Agent-Session: deploy-ci-rollout-news-plugin-2026-06-16
2026-06-16 21:30:24 +07:00
peptiderepo
04b90e2e2d refactor(admin): extract filter field renderers (325 → 249 + 103 lines)
Move 7 content-filter settings field renderers into dedicated
Peptide_News_Admin_Filter_Field_Renderers class. Main field
renderers file drops from 325 to 249 lines with backward-
compatible one-liner proxies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 22:43:50 +07:00
peptiderepo
159a36eeb7 refactor(analytics): split 365-line analytics into recording + reports
Extract 6 read-only reporting queries (top articles, click trends,
popular topics, device breakdown, source performance, CSV export) into
Peptide_News_Analytics_Reports (196 lines). Main class retains click
recording, daily stats aggregation, IP handling, and device detection
(186 lines). Backward-compatible proxies preserved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 22:42:06 +07:00
peptiderepo
59d469ba0f refactor(rest): split 435-line REST API into router + articles handler
Extract article listing endpoint and source-suffix stripping into
Peptide_News_Rest_Articles (195 lines). Main REST API retains route
registration, analytics endpoint wrappers, AJAX delete handler, and
permission/validation helpers (210 lines).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 22:40:34 +07:00
peptiderepo
6060aa3ce1 refactor(filter): split 536-line content filter into orchestrator + rules
Extract keyword data, domain blocklists, and rule evaluation into
Peptide_News_Content_Filter_Rules (195 lines). Main class retains
orchestration, LLM classification, and stats (234 lines). Backward-
compatible proxies preserved on Peptide_News_Content_Filter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 22:38:34 +07:00
peptiderepo
f57bf15b9b refactor(cost): split 550-line cost tracker into tracker + reporter
Extract read-only reporting queries (get_cost_summary, get_daily_costs,
get_recent_calls, ajax_get_cost_data) into Peptide_News_Cost_Reporter
(224 lines). Core tracker retains logging, budget enforcement, pricing,
table creation, and pruning (276 lines). Backward-compatible proxies
preserved on Peptide_News_Cost_Tracker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 22:36:36 +07:00
peptiderepo
64e715372a refactor(fetcher): split 611-line fetcher into 3 focused files
Extract source-resolution and RSS-specific logic from the monolithic
Peptide_News_Fetcher:

- class-peptide-news-source-resolver.php (229 lines) — aggregator
  source detection, redirect resolution, title parsing, backfill
- class-peptide-news-rss-source.php (118 lines) — RSS/Atom feed
  fetching and SimplePie item processing
- class-peptide-news-fetcher.php (271 lines) — orchestrator with
  NewsAPI, storage, pruning, caching, and AI post-processing

Also consolidated duplicate clear_article_cache/invalidate_rest_api_cache
methods (they were identical) into a single clear_article_cache().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 22:30:50 +07:00
peptiderepo
69cb00063f refactor(llm): split 694-line LLM class into 4 focused files
The monolithic Peptide_News_LLM class mixed HTTP transport, prompt
construction, batch orchestration, and AJAX handling. Split into:

- class-peptide-news-llm-client.php (199 lines) — OpenRouter HTTP
  transport with retry logic and response parsing
- class-peptide-news-llm-prompt-builder.php (142 lines) — keyword
  and summary prompt construction, article text fetching
- class-peptide-news-llm-ajax.php (87 lines) — bulk-generate AJAX
  handler
- class-peptide-news-llm.php (300 lines) — orchestrator with
  backward-compatible proxies for is_valid_model(), call_openrouter(),
  and call_openrouter_with_usage()

Extracted run_llm_task() helper to deduplicate the keyword/summary
processing blocks. All public API preserved — no test changes needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 22:22:45 +07:00
peptiderepo
0f3adf0c5d refactor(admin): split 1089-line admin class into 7 focused files (#7)
Addresses excessive line count in Peptide_News_Admin (1089 lines)
by extracting responsibilities into single-purpose classes:

- class-pn-admin-assets.php (88 lines): CSS/JS enqueuing
- class-pn-admin-menu.php (98 lines): Menu & submenu registration
- class-pn-admin-field-renderers.php (325 lines): 25+ field renderers (static)
- class-pn-admin-settings.php (289 lines): Settings registration & sanitization
- class-pn-admin-log-viewer.php (155 lines): Plugin log viewer UI & AJAX
- class-pn-admin-settings-page.php (214 lines): Settings page + manual buttons
- class-pn-admin-dashboard-pages.php (109 lines): Dashboard/articles/cost pages
- class-peptide-news-admin.php (143 lines): Thin orchestrator (delegator)

All files under 300 lines. Original public interface preserved exactly
so no consumer code or tests break. Updated ARCHITECTURE.md.

Co-authored-by: peptiderepo <peptiderepo@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 16:50:26 +07:00
Terence Ong
9e6c5660b2 fix: remove object type hints from Loader to support string class names
The add_action(), add_filter(), and add() methods used `object` type
hints for the $component parameter, but several hooks pass string class
names (for static method callbacks). PHP 8.3 strict typing rejects
strings where `object` is declared, causing a fatal TypeError that
crashes the entire site.

Removed the `object` type hint to accept both object instances and
string class names, which is required for WordPress static callbacks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:00:19 +07:00
Terence
b92dc4b0d8 fix: resolve PHPCS errors — remove short ternaries and fix docblock placement
- Replace 4 short ternary operators (?:) with explicit conditionals in Cost_Tracker
- Move declare(strict_types=1) after file-level docblocks in 3 template files
  (PHPCS requires docblock immediately after opening PHP tag)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 04:21:56 +07:00
Terence
b7da4f42b9 feat: add LLM cost tracking with budget enforcement and admin dashboard
- New Cost_Tracker class: logs every API call (model, tokens, cost), enforces
  monthly budgets with hard-stop mode, provides aggregated reporting
- New llm_costs database table with indexes on created_at, model, operation
- Modified LLM class: call_openrouter_with_usage() extracts token usage from
  OpenRouter responses; budget gate checks before each API call
- Admin settings: monthly budget (USD), enforcement mode (disabled/warn/hard-stop),
  cost log retention period
- Admin cost dashboard: budget progress bar, daily cost chart (Chart.js),
  per-model and per-operation breakdowns, recent API call log
- PHPUnit tests: 16 tests covering cost calculation, pricing lookups,
  budget enforcement logic, and model validation
- Updated ARCHITECTURE.md with new table schema, data flow, and key decisions
- Updated CONVENTIONS.md with "how to add cost tracking for a new operation"
- Version bump to 2.4.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 04:18:30 +07:00
peptiderepo
f44c86a949 fix: resolve remaining PHPCS errors (4 violations across 3 files)
- Downgrade Generic.Files.OneObjectStructurePerFile to warning
  (widget class shares file with public class — split in future cleanup)
- Add translators comment for __() with %s placeholder in REST API
- Remove blank line after control structure in REST API
- Fix return-newline in content filter (extract to $prompt variable)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 17:26:26 +07:00
peptiderepo
c885ebf98c fix: convert all PHP files from space indentation to tabs (PHPCS)
WordPress coding standards require tab indentation. All 12 PHP files
in includes/, admin/, and public/ were using 4-space indentation.
Converted to tabs to pass the Generic.WhiteSpace.DisallowSpaceIndent
PHPCS rule. Whitespace-only change — no logic modifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 17:03:55 +07:00
Terence Ong
b05913ed9a feat: add article delete functionality to admin UI
Add bulk and per-row article deletion with AJAX handler, confirmation
dialogs, and cascading cleanup of click analytics via foreign keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:22:33 +07:00
Terence Ong
71f7a35eb3 fix: fetch article page content when RSS provides only a title
Google News RSS items often have no description or content — just a
title. This left the LLM with insufficient context, resulting in empty
ai_summary fields.

When the stored content is sparse (<100 chars or matches the title),
build_summary_prompt() now fetches the actual article page via
wp_remote_get(), extracts text from <article>/<main> tags, and feeds
it to the LLM. Gracefully falls back to title-only if the fetch fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:01:10 +07:00
Terence Ong
221a46a60a fix: remove wp_kses_post() HTML escaping from REST API JSON output
The security audit added wp_kses_post()/esc_html() to article fields
before returning them via the REST API. This caused double-encoding
(e.g., & → &amp;) because REST responses are JSON, not HTML —
WordPress's rest_ensure_response() already handles JSON encoding.
The frontend is responsible for output escaping at render time.

Removes the 4 escaping lines from get_articles() to fix broken
summaries and truncated content on the live site.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 12:34:02 +07:00
Terence Ong
32e21bc59d fix: comprehensive code quality remediation from audit findings
Security (7 fixes):
- SEC-001: Use wpdb->prepare() for COUNT query in REST API
- SEC-002: Set httponly=true on session cookie to prevent XSS
- SEC-003: Mask API keys in admin settings (show last 4 chars only)
- SEC-004: Check capability before nonce in manual fetch handler
- SEC-005: Validate redirect domains to prevent open redirects
- SEC-006: Escape all article fields before REST API response
- SEC-007: Add capability check to AJAX summary handler

Infrastructure (7 fixes):
- WPS-002: Clear cron schedule on plugin deactivation
- WPS-003: Bundle Chart.js locally instead of CDN without SRI
- WPS-004: Add uninstall.php for clean plugin removal
- DB-001: Add foreign key constraints with CASCADE deletes
- DB-002: Clamp pagination to valid page range
- CI-001: Use StrictHostKeyChecking=accept-new in deploy
- CI-002: Add security scanning step to CI pipeline
- CI-003: Add rollback workflow via workflow_dispatch

Performance & Quality (8 fixes):
- PHP-001: Validate widget $instance as array before access
- PHP-002: Return null from get_client_ip() for invalid IPs
- PHP-003: Process RSS feeds in chunked batches of 10
- PHP-004: Wire up clear_article_cache() in fetch cycle
- PERF-001: Cache source names in transient for strip_source_suffix
- PERF-002: Add server-side transient caching for REST API
- ARCH-002: Make LLM API URL configurable via WordPress options

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 11:47:43 +07:00
Terence Ong
44cc4f56a3 fix: add second-pass nbsp cleanup directly in get_articles()
The strip_source_suffix() fallbacks aren't catching all nbsp-separated
publisher names. Adding a direct strrpos-based cleanup pass in the
get_articles() method itself as a safety net. This operates on the
already-decoded text returned by strip_source_suffix().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 11:02:28 +07:00
Terence Ong
0a550cabfe fix: use preg_match with capture groups for nbsp publisher stripping
Previous strrpos-based approach wasn't catching all cases. Switch to a
preg_match with /u flag and capture groups to reliably split text at
nbsp separator boundaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 10:53:53 +07:00
Terence Ong
aeceab2ae4 fix: use string search instead of regex for nbsp publisher stripping
The previous regex approach with \x{00A0} wasn't reliably catching all
nbsp-separated publisher names (e.g., "UC Santa Cruz" on article 19).
Switched to strrpos-based string search which is simpler and avoids
potential PCRE/UTF-8 edge cases with multibyte characters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 10:49:48 +07:00
Terence Ong
d6b111ffc8 fix: add fallback to strip nbsp-separated publisher names from excerpts
Google News RSS excerpts use double non-breaking spaces followed by the
publisher name (e.g. "...habitat  News-Medical"). When the source field
is "news.google.com" rather than the actual publisher, the exact-match
logic can't strip it. This adds a regex fallback that catches trailing
nbsp-separated capitalized names.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 10:44:01 +07:00
Terence Ong
8fe3d828cd fix: strip source names from excerpts using nbsp separators
Google News RSS excerpts use &nbsp;&nbsp; (double non-breaking spaces)
to separate the excerpt text from the source name. Added html_entity_decode
and nbsp-based separators to handle this pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 10:38:06 +07:00
Terence Ong
672cdacd60 fix: apply tag removal and source stripping to root-level plugin files
The deploy workflow reads from the repo root (admin/, includes/, public/),
not the peptide-news-plugin/ subdirectory. Applied the same changes:
- Removed Tag component and tag rendering from ArticleCard
- Removed all tag-related CSS (variables, styles, skeleton, responsive)
- Added strip_source_suffix() to REST API for cleaning source names
  from titles/summaries with case-insensitive and domain-based matching

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 10:26:43 +07:00
Terence Ong
6c57f6ca11 feat: add structured plugin logging system with admin log viewer
Introduces Peptide_News_Logger class with leveled logging (info, warning,
error, debug) to a custom DB table, auto-pruning at 2000 rows. Adds a
filterable, paginated log viewer to the admin settings page. Instruments
the LLM and Fetcher classes with structured log calls for fetch cycles,
AI processing, and error tracking. Version bumped to 2.1.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:58:51 +07:00
Terence Ong
0cea96fd16 Switch to google/gemma-3-27b-it:free model for AI summaries
The qwen/qwen3.6-plus-preview:free model returns "No endpoint"
errors. Switch to google/gemma-3-27b-it:free which is confirmed
working on OpenRouter's free tier.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:24:46 +07:00
Terence Ong
67f932a9e5 Add error reporting to bulk AI summary generation
Surface actual API error messages in the admin UI instead of just
showing a generic failure count. This helps diagnose issues with
the OpenRouter API configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:21:49 +07:00
Terence Ong
99aa2ca06d Fix incorrect OpenRouter model ID (missing -preview suffix)
The configured model qwen/qwen3.6-plus:free doesn't exist on
OpenRouter — the correct ID is qwen/qwen3.6-plus-preview:free.
Add auto-correction in the bulk generation handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:18:18 +07:00
Terence Ong
359b001e6d Reduce bulk generation to 1 article per AJAX request
Free OpenRouter models are too slow for batch processing on shared
hosting. Reduce batch size from 5 to 1, API timeout from 30s to 15s,
and max retries from 3 to 1 to stay within PHP max_execution_time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:11:57 +07:00
Terence Ong
245b24b854 fix: reduce bulk summary batch to 5 per request
The previous batch size of 50 exceeded PHP max_execution_time on
Hostinger shared hosting (30s). Each article needs ~3s for the
OpenRouter API call, so 5 articles per AJAX request keeps each
batch under 20s. The JS loop still processes all articles
automatically — just in more, smaller batches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:04:16 +07:00
Terence Ong
ab56ec7e97 feat: add bulk AI summary generation button to admin
Adds a "Generate AI Summaries" button to the Peptide News settings page
that processes all articles missing summaries in batches of 50. The UI
loops automatically until every article has been summarized, showing
real-time progress. The admin batch overrides the per-cycle limit so it
isn't capped at 10 — the existing 120-second timeout guard still applies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 14:59:18 +07:00
peptiderepo
99ef0c9638 fix: add no-cache headers to REST API, include ai_summary in response
LiteSpeed Cache was serving stale REST API responses with 0 articles, causing the React frontend to show "No articles" despite 50+ articles in the DB.

- Add Cache-Control and X-LiteSpeed-Cache-Control: no-cache headers to /articles endpoint
- Include ai_summary and thumbnail_local columns in the REST API query (needed by React frontend)
2026-04-07 13:20:29 +07:00
Terence Ong
1ae831e183 feat: remove AI thumbnails, rewrite frontend with React (v1.3.0)
Remove all AI thumbnail generation infrastructure (too expensive,
poor results) and rewrite the public-facing article feed as a React
SPA mounted via WordPress shortcode.

Changes:
- Remove thumbnail generation, OG scraping, and image download code
  from fetcher, LLM, and admin classes
- Replace server-rendered PHP templates with React component
  (PeptideNewsFeed.jsx) using WordPress-bundled React/ReactDOM
- Add Babel build pipeline for JSX transpilation
- Update REST API to serve ai_summary instead of thumbnail fields
- Redesign public CSS: clean card layout, loading skeletons,
  dark mode, responsive, no thumbnail imagery
- Remove dead admin settings (llm_image_model, thumbnail_fallback)
- Fix call_openrouter() visibility for Content Filter cross-class use
- Bump version to 1.3.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 09:45:12 +07:00
Terence Ong
cd8eee11b8 Replace broken DDG search with AI thumbnail generation fallback
DuckDuckGo's HTML search endpoint now requires vqd session tokens,
making server-side search-based URL resolution unreliable. Simplified
the thumbnail pipeline:

- OG scrape only for non-aggregator URLs (direct publisher pages)
- AI image generation via OpenRouter for aggregator articles (Google News)
- Added Image Model settings field in admin UI
- Added thumbnail_local column for AI-generated images
- Updated public/admin display to check thumbnail_local as fallback
- Removed all DuckDuckGo search code (find_article_url_via_search,
  extract_ddg_url, resolve_article_for_scraping)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 20:29:32 +07:00
Terence Ong
de35f2d684 Fix thumbnail scraping: use search-based URL resolution for Google News articles
Google News RSS stores encrypted article URLs that cannot be resolved
server-side (JS SPA, no HTTP redirects, encrypted base64 IDs). Replace
the failed decode/parse strategies with DuckDuckGo search-based lookup
using article title + publisher name to find actual article pages, then
scrape OG images from those pages.

- Add find_article_url_via_search() using DuckDuckGo HTML endpoint
- Add resolve_article_for_scraping() combining redirect + search fallback
- Mark failed lookups with '_no_image' sentinel to avoid repeated retries
- Backfill button resets sentinels to allow manual re-scraping
- Clean up verbose debug instrumentation from prior attempts
- Update frontend to handle '_no_image' sentinel gracefully

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 20:01:25 +07:00
Terence Ong
acf7e61b90 Switch to multi-strategy HTML parsing for Google News URL resolution
Replace batchexecute API with direct fetch + HTML parsing strategies:
data-n-au attributes, anchor href extraction, meta refresh, JS redirect.
Add verbose debug output to diagnose server-side response from Google News.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 19:47:10 +07:00
Terence Ong
f828cec229 Use Google batchexecute API to resolve Google News article URLs
Replace broken base64 decoding with Google's internal batchexecute API
endpoint to resolve encrypted article IDs to actual publisher URLs.
This is the approach used by known Google News decoder libraries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 19:42:22 +07:00
Terence Ong
2c09ee4664 Add diagnostic debug output to thumbnail backfill for troubleshooting
Temporary debug version that returns query counts, URL resolution results,
and OG scraping outcomes to identify why thumbnail scraping returns 0.
Also adds Google News URL decoder (base64/protobuf approach).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 19:39:26 +07:00
Terence Ong
5918a7d446 Fix missing thumbnails by resolving Google News redirect URLs before OG scraping
The OG image scraper was using Google News redirect URLs (source_url) directly,
which don't serve og:image tags. Added resolve_scrape_url() to follow redirects
(including meta-refresh and JS redirects) to reach the actual publisher page
before scraping. Also added a backfill AJAX endpoint and admin button to
re-scrape thumbnails for existing articles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 19:32:09 +07:00
Terence Ong
18158a3b81 fix: resolve actual article source instead of showing aggregator domain
Google News RSS feeds report every article as coming from
"news.google.com" because the source was extracted from the feed URL
rather than the individual article. This adds a multi-strategy
resolver that extracts the real publisher name via:

1. SimplePie <source> element (Google News provides this)
2. Title suffix parsing ("Headline - Source" format)
3. HTTP HEAD redirect resolution as a fallback

Also adds a one-click "Fix Article Sources" backfill button on the
settings page to correct existing articles in the database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 19:11:14 +07:00
Peptide News Team
d99c505d1c Add missing Loader class and public CSS
The Peptide_News_Loader class was referenced by class-peptide-news.php
but never committed, causing a fatal error on plugin activation.
Also adds public/css/public-style.css which is enqueued by the public
class.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 16:32:42 +07:00
Terence Ong
c9be4ceb39 Fix pre-existing syntax errors in analytics and REST API classes
- analytics.php line 38: mismatched quotes on session_id array key
- analytics.php line 246: invalid escape before concatenation operator
- analytics.php line 250: COuNT typo → COUNT
- analytics.php line 252: NULIF typo → NULLIF
- rest-api.php line 54: stray # commenting out foreach variable

These bugs predate the content filter feature but were caught by the
new CI pipeline's PHP lint job.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 14:19:15 +07:00
Terence Ong
e6887e810f Add content filter to block ads, press releases, and promotional content
Two-tier filtering during article fetch: keyword/pattern rules as a fast
first pass, then optional LLM classification for borderline cases via
OpenRouter. Configurable sensitivity (strict/moderate/lenient), editable
keyword lists, blocked domain list, and admin stats display. Includes
30 unit tests covering domain blocking, title/body keywords, URL paths,
edge cases, and prompt structure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 14:14:16 +07:00
Terence Ong
4367d68202 fix: security hardening for LLM thumbnail feature (QA-002,004,005,006)
- Use $wpdb->prepare() with esc_like() in clear_article_cache()
- Add strict base64_decode() validation with error handling
- Add isset()/is_array()/is_string() checks for nested array access
- Add wp_http_validate_url() SSRF prevention on all image downloads
- Add limit_response_size (5MB) on image URL downloads
- Set chmod 0644 after file_put_contents()
2026-04-06 10:19:53 +07:00
peptiderepo
01a794cd5f fix: security hardening for thumbnail feature (QA-001,003,004,006,008)
- Use $wpdb->prepare() with esc_like() in clear_article_cache()
- Add wp_http_validate_url() SSRF prevention in download_thumbnail()
- Add limit_response_size (5MB) on image downloads
- Set chmod 0644 after file_put_contents()
- Add transient-based concurrent fetch lock
2026-04-06 10:16:16 +07:00
peptiderepo
17e8247d2a fix(llm): add 429 retry, timeout guard, model validation, XSS protection, batch cache clear 2026-04-06 01:01:19 +07:00