Skip to content

Add extra_data column and contrast data collection (DB 1.0.8) - #1802

Open
pattonwebz wants to merge 4 commits into
developfrom
william/pro-668-extra-data-column-and-collection
Open

Add extra_data column and contrast data collection (DB 1.0.8)#1802
pattonwebz wants to merge 4 commits into
developfrom
william/pro-668-extra-data-column-and-collection

Conversation

@pattonwebz

@pattonwebz pattonwebz commented Jun 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Bumps DB schema to 1.0.8 and adds extra_data text NULL column to wp_accessibility_checker
  • Extends Insert_Rule_Data::insert() with an optional $extra_data array parameter; JSON-encoded before storage, updated on rescans
  • Page scanner now extracts color contrast metadata (fg/bg color, contrast ratio, font size/weight) from axe-core results for color_contrast_failure violations and sends it as extraData in the scan payload
  • REST API set_post_scan_results() reads violation['extraData'] and passes it through to insert()

Intentionally excluded from this PR (remains in PR #1589):

  • Highlighter color swatch display (src/frontendHighlighterApp/index.js, .scss)
  • data-extra-data HTML attribute output (admin/class-ajax.php, admin/class-frontend-highlight.php)
  • IssueDetailsModal changes

This is a prerequisite for the Manual Issues feature, which will store its user-authored fields (manual_title, manual_description, manual_why_it_matters, manual_how_to_fix, screenshot_id) as JSON keys inside extra_data rather than adding dedicated columns.

Test plan

  • Activate plugin on a site running MySQL 5.7+ — confirm extra_data column is added on plugin load
  • Run a page scan that includes a color contrast failure — confirm extra_data column is populated with JSON containing fgColor, bgColor, contrastRatio, expectedContrastRatio, fontSize, fontWeight
  • Rescan the same page — confirm extra_data is updated, not duplicated
  • Confirm non-contrast issues have extra_data = NULL

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Color contrast failure results can now include additional contrast and font-related metadata when available.
    • Scan records now support optional per-violation extra metadata, which is captured for richer issue reporting.
  • Bug Fixes
    • Updated database versioning and added persistent storage for the new per-violation extra metadata, ensuring it’s correctly saved and maintained during updates/upgrades.

Extracts the infrastructure-only portion of PR #1589: adds `extra_data text NULL`
to `wp_accessibility_checker`, stores JSON metadata during scanner inserts, and
collects color contrast values (fg/bg color, ratio, font size/weight) from axe-core
results for `color_contrast_failure` violations.

Output/display side (highlighter swatches, admin HTML attributes, IssueDetailsModal
changes) is intentionally excluded and remains in PR #1589.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a6260db1-9280-42ed-81ef-215b1e5a2777

📥 Commits

Reviewing files that changed from the base of the PR and between 967245f and 0c61427.

📒 Files selected for processing (3)
  • admin/class-insert-rule-data.php
  • admin/class-update-database.php
  • src/pageScanner/index.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • admin/class-update-database.php
  • src/pageScanner/index.js
  • admin/class-insert-rule-data.php

📝 Walkthrough

Walkthrough

Adds an extra_data nullable text column to accessibility_checker, threads optional extra_data through the REST API and Insert_Rule_Data::insert(), and populates it from processViolation() for color_contrast_failure results.

Changes

extra_data persistence pipeline

Layer / File(s) Summary
DB schema and version bump
accessibility-checker.php, admin/class-update-database.php
EDAC_DB_VERSION is updated to 1.0.8, and the accessibility_checker table schema adds a nullable extra_data column; upgrade notes mention the new column.
JS scanner extraData extraction
src/pageScanner/index.js
processViolation now builds a local result object and attaches extraData for color_contrast_failure violations from color-contrast check data.
REST API extraData handoff
includes/classes/class-rest-api.php
set_post_scan_results() reads optional extraData from each violation array and passes it to Insert_Rule_Data::insert().
Insert_Rule_Data extra_data persistence
admin/class-insert-rule-data.php
insert() accepts optional ?array $extra_data, resolves filter-provided values, encodes them, and includes them in the update and insert payloads.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • SteveJonesDev

Poem

🐇 A tiny blob of extra hue,
Hopped from the scanner, through and through.
Contrast clues and font details,
Now ride along in tidy trails.
The rabbit grins: “Saved data too!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the schema change and contrast data collection added in DB 1.0.8.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch william/pro-668-extra-data-column-and-collection

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an extra_data column to the database schema (version 1.0.8) to store arbitrary JSON metadata, such as color contrast values, alongside accessibility violations. It updates the Insert_Rule_Data class, the REST API, and the frontend page scanner to process and store this new metadata. The review feedback identifies three critical issues: a potential Stored XSS vulnerability due to unsanitized $extra_data being stored in the database, potential JSON corruption from applying sanitize_text_field() directly to a JSON string, and a MySQL syntax error in the migration script caused by using IF NOT EXISTS in an ALTER TABLE statement (which is redundant as dbDelta already handles this).

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread admin/class-insert-rule-data.php Outdated
'rule' => $rule,
'ruletype' => $ruletype,
'object' => esc_attr( $rule_obj ),
'extra_data' => $extra_data ? wp_json_encode( $extra_data ) : null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The $extra_data array is encoded and stored directly in the database on updates without any sanitization. Since this data originates from the REST API (user input), this creates a Stored XSS vulnerability if a user with edit permissions submits a malicious payload.

To prevent this, we should recursively sanitize the $extra_data array before JSON encoding it.

			'extra_data'        => $extra_data ? ( function( $data ) {
				array_walk_recursive( $data, function( &$val ) {
					$val = sanitize_text_field( $val );
				} );
				return wp_json_encode( $data );
			} )( $extra_data ) : null,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. A private static encode_extra_data( ?array $data ): ?string helper was added that recursively walks the array and applies sanitize_text_field() to every string value before JSON-encoding. It is called in both the UPDATE and INSERT paths so all writes go through the same sanitization gate.

Introduced in f27bd5e ("Address CodeRabbit/Gemini review comments") and further refined in 967245f (made private static) and 0c61427 (rubber-duck review: INSERT and UPDATE paths now both call encode_extra_data() on the raw PHP array, not on a pre-encoded JSON string).

Comment thread admin/class-insert-rule-data.php Outdated
'rule' => sanitize_text_field( $rule_data['rule'] ),
'ruletype' => sanitize_text_field( $rule_data['ruletype'] ),
'object' => esc_attr( $rule_data['object'] ),
'extra_data' => isset( $rule_data['extra_data'] ) ? sanitize_text_field( $rule_data['extra_data'] ) : null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using sanitize_text_field() directly on a JSON string is highly problematic. It strips HTML tags, converts < characters, and removes line breaks, which will corrupt the JSON structure if any values contain HTML-like characters (such as CSS selectors containing > or future manual descriptions containing HTML).

Instead, we should decode the JSON, recursively sanitize the individual string values, and then re-encode it.

				'extra_data'        => isset( $rule_data['extra_data'] ) ? ( function( $json ) {
					$decoded = json_decode( $json, true );
					if ( is_array( $decoded ) ) {
						array_walk_recursive( $decoded, function( &$val ) {
							$val = sanitize_text_field( $val );
						} );
						return wp_json_encode( $decoded );
					}
					return null;
				} )( $rule_data['extra_data'] ) : null,
References
  1. When ensuring a variable is an array, prefer using an if...then conditional check over (array) type casting to maintain consistency with existing code patterns.
  2. When the contract for a variable is a specific type (e.g., an array of commands), prefer a stricter type check (e.g., is_array) over a more general one (e.g., is_iterable) to enforce that contract.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The original approach of calling sanitize_text_field() on an already-encoded JSON string is gone. The encode_extra_data() helper now receives the raw PHP array, sanitizes string values within it, and then encodes — so sanitize_text_field never sees a JSON string. In the sanitized-copy block (INSERT path), the field is decoded from whatever the filter returned before being passed to encode_extra_data(), handling the edge case where a filter sets the field to a JSON string.

Fixed in f27bd5e ("Address CodeRabbit/Gemini review comments"), with the encode-before-filter design flaw corrected in 0c61427 ("Fix bugs found in rubber-duck review").

Comment thread admin/class-update-database.php Outdated
Comment on lines +93 to +97
// Add extra_data column for storing arbitrary JSON metadata alongside issues.
if ( version_compare( $db_version, '1.0.8', '<' ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder -- One-time schema migration; %i is a valid identifier placeholder since WP 6.2.
$wpdb->query( $wpdb->prepare( 'ALTER TABLE %i ADD COLUMN IF NOT EXISTS extra_data text NULL', $table_name ) );
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

MySQL does not support the IF NOT EXISTS clause for ALTER TABLE ADD COLUMN (this is a MariaDB-only extension). Running this query on standard MySQL servers will result in a database syntax error, preventing the database upgrade from completing successfully.

Since dbDelta( $sql ) is already called on line 81 with the updated schema containing extra_data text NULL, dbDelta will automatically and safely add the column if it does not exist. Therefore, this manual ALTER TABLE query is both redundant and broken, and should be removed.

// Schema updates for 1.0.8 (adding extra_data column) are handled automatically by dbDelta() above.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The ALTER TABLE $table_name ADD COLUMN IF NOT EXISTS extra_data text NULL block was removed entirely. As the comment correctly identified, dbDelta() already handles this: it compares the declared CREATE TABLE SQL (which now includes the extra_data column) against the actual table structure and issues a plain ADD COLUMN only when the column is absent. Since extra_data is nullable, no data backfill is required — existing rows automatically receive NULL.

Removed in f27bd5e ("Address CodeRabbit/Gemini review comments").

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@admin/class-insert-rule-data.php`:
- Line 70: The `extra_data` value is being JSON-encoded without consistent
recursive sanitization across both insert and update paths in
`class-insert-rule-data.php`. Centralize normalization and sanitization in the
rule data flow (for example, in the insert/update handling methods around
`extra_data`) so rescans and new inserts both pass through the same safe
preprocessing before `wp_json_encode`. Ensure the shared sanitizing step is
applied before persisting `extra_data` in every path that writes it, not just in
one branch.

In `@admin/class-update-database.php`:
- Around line 93-97: The migration in class-update-database.php uses an ALTER
TABLE ADD COLUMN IF NOT EXISTS for extra_data, which is not safe on MySQL 5.7.
Update the upgrade path inside the db_version check in the database updater to
first detect whether the extra_data column already exists, then run a plain ADD
COLUMN only when it is missing. Keep the change within the existing
wpdb->prepare / $wpdb->query flow in the database migration logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 02db9734-3d49-407c-9224-d5e090382438

📥 Commits

Reviewing files that changed from the base of the PR and between 40a0b82 and 1da53dc.

📒 Files selected for processing (5)
  • accessibility-checker.php
  • admin/class-insert-rule-data.php
  • admin/class-update-database.php
  • includes/classes/class-rest-api.php
  • src/pageScanner/index.js

Comment thread admin/class-insert-rule-data.php Outdated
Comment thread admin/class-update-database.php Outdated
pattonwebz and others added 3 commits June 28, 2026 00:08
- Remove ALTER TABLE ADD COLUMN IF NOT EXISTS block: MySQL 5.7 does not support
  this syntax (it's MariaDB-only). dbDelta() already handles adding the extra_data
  column when upgrading from < 1.0.8, so the explicit ALTER is redundant.

- Fix sanitize_text_field() on JSON string: applying that function directly to an
  encoded JSON value corrupts it (strips < and > characters). Replace with a private
  encode_extra_data() helper that recursively sanitizes string values before encoding
  and is called in both the insert and update paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- encode_extra_data: handle wp_json_encode() returning false (returns null instead
  of propagating false into DB as empty string)
- encode_extra_data: make private static since it doesn't reference $this; update
  call sites to use self::
- UPDATE SQL: remove stray double space before WHERE clause

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extra_data in UPDATE path: switch from raw wpdb->prepare('%s', null) to
  wpdb->update(), which routes through process_fields() and emits SQL NULL for
  null values. prepare('%s', null) coerces null to '' causing a divergence between
  first-scan INSERT (NULL) and rescan UPDATE ('').

- extra_data pre-encoded before filter: keep extra_data as a raw PHP array in
  $rule_data so edac_filter_insert_rule_data receives native PHP types, consistent
  with every other field. Encoding now happens at point of persistence. The sanitize
  block handles the edge case where a filter sends a JSON string.

- index.js: add console.warn when color-contrast check is found but has no data,
  so silent drops are visible during debugging.

- class-update-database.php: add comment explaining why 1.0.8 has no migration
  guard (dbDelta handles it), consistent with the 1.0.5 and 1.0.7 comments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant