Skip to content

Fix SQL logic bug and Admin UI hydration - #1

Open
google-labs-jules[bot] wants to merge 3 commits into
mainfrom
bugfix/sql-injection-and-admin-ui-9901016997777217283
Open

google-labs-jules[bot] wants to merge 3 commits into
mainfrom
bugfix/sql-injection-and-admin-ui-9901016997777217283

Conversation

@google-labs-jules

Copy link
Copy Markdown

This PR addresses two main issues:

  1. Critical Logic Bug: The FAQ mapping lookup used LIKE comparisons for IDs. This meant that mapping an FAQ to Post ID 1 would also cause it to appear on Post IDs 11, 10, 100, etc. This has been fixed to use exact = comparison.
  2. Admin UI Bug: When editing an FAQ item with "By Posts" or "By Terms" association, the Select2 input fields would appear empty on page reload because the stored data (IDs) lacked the text labels required for display. The fix hydrates this data on the server side before passing it to the JavaScript initialization.

PR created automatically by Jules for task 9901016997777217283 started by @renderbit-technologies

- In `includes/frontend.php`, changed the SQL query for mapping values from `LIKE` to `=` to prevent partial matches (e.g. Post ID 1 matching Post ID 11).
- In `includes/admin.php`, added hydration logic to fetch post titles and term names for the `fqj_assoc_data` JSON payload, ensuring Select2 inputs are correctly pre-filled on page load.
@google-labs-jules

Copy link
Copy Markdown
Author

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!


For security, I will only act on instructions from the user who triggered this task.

New to Jules? Learn more at jules.google/docs.

- Fix PSR-12 coding standard violations across all files (line length, spacing, indentation).
- Rename `FQJ_CLI` class to `FQJ\FqjCli` and methods to camelCase to comply with PSR-1.
- Fix SQL logic bug in `includes/frontend.php` (changed `LIKE` to `=`) to prevent incorrect mapping matches.
- Fix Admin UI bug in `includes/admin.php` by hydrating Select2 data to display titles instead of IDs.
- Add `squizlabs/php_codesniffer` to composer dev dependencies.
…l bugs

- Rename `includes/wpcli.php` to `includes/class-fqjcli.php` and use `snake_case` methods.
- Update `composer.json` to include `wp-coding-standards/wpcs` and allow installer plugin.
- Fix thousands of formatting issues (spacing, tabs, Yoda conditions, arrays) using `phpcbf`.
- Manually fix logic bugs:
    - Hydrate Admin UI Select2 fields with titles in `includes/admin.php`.
    - Fix SQL logic in `includes/frontend.php` (changed `LIKE` to `=`) and implement secure prepared statements.
    - Add proper escaping (`esc_html`, `esc_attr`, `intval`) and sanitization (`sanitize_text_field`, `wp_unslash`) throughout.
- Add missing docblocks and `@package` tags.
@soham2008xyz
soham2008xyz marked this pull request as ready for review September 14, 2026 17:14
Copilot AI lite review requested due to automatic review settings September 14, 2026 17:14

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e3c05b31a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread includes/admin.php
Comment on lines +96 to +97
'post_type' => 'any',
'posts_per_page' => -1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve unpublished posts while hydrating selections

When an associated post is later moved to draft, private, or trash, get_posts() omits it because its default post_status is publish. The hydrated select therefore no longer contains that ID, and the next ordinary save rewrites fqj_assoc_data_json and the mapping index without the association. Query the relevant statuses, subject to the editor's permissions, or preserve IDs that cannot be hydrated.

Useful? React with 👍 / 👎.

Comment thread includes/admin.php
// Load stored JSON payload for this FAQ.
$data_json = get_post_meta( $post->ID, 'fqj_assoc_data_json', true );
$data_json = $data_json ? $data_json : '{}';
$data = json_decode( $data_json, true );

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back to an object when association JSON is invalid

If existing association metadata contains malformed JSON or the valid JSON value null, json_decode() returns null, which is then encoded into the hidden field as null. The admin script successfully parses that value but immediately dereferences properties such as data.urls, causing the association editor to fail instead of using its previous malformed-data fallback. Normalize a non-array decode result to an empty array before hydration.

Useful? React with 👍 / 👎.

Comment thread includes/frontend.php
// Use exact match for all types to prevent partial matches (e.g. post ID 1 matching 10, 11, etc.)
// Since we store scalar values directly (or serialized which resolves to string), = is safer.
// For 'url', if we wanted partial matches we'd need a different logic, but here we expect exact path match.
$where_clauses[] = '(mapping_type = %s AND mapping_value = %s)';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize URL mappings before exact comparison

For URL-only mappings where url_to_postid() cannot add the redundant post-ID mapping, exact equality now makes common saved URLs ending in / fail to match because the lookup candidate is explicitly stripped of its trailing slash earlier in this function. Query-string mappings have the same mismatch because the candidate also drops its query. Normalize URLs consistently when indexing and looking them up before switching this predicate to =.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are concrete regressions/bugs in the changed admin markup and exact-match URL/term lookup logic that can break existing associations and UI state.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR fixes the FAQ-to-post lookup accuracy by switching mapping table comparisons from partial LIKE matches to exact = matches, and addresses Select2 “empty on reload” behavior by hydrating stored IDs into {id,text} objects server-side before initializing the admin UI.

Changes:

  • Changed frontend mapping table lookup to use exact comparisons to prevent ID substring collisions (e.g., 1 matching 10/11/100).
  • Hydrated stored “By Posts” / “By Terms” association data with display labels so Select2 can render selections after reload.
  • Refactored/standardized multiple files toward WordPress Coding Standards and moved WP-CLI commands into a namespaced class file.
File summaries
File Description
includes/wpcli.php Removed legacy WP-CLI command definitions file.
includes/class-fqjcli.php Added namespaced WP-CLI command class and command registration.
includes/frontend.php Updated mapping lookup logic to use exact matches and refreshed code style.
includes/admin.php Added server-side hydration of Select2 initial data; updated meta save handler sanitization/style.
includes/indexer.php Style/sanitization updates around mapping/index rebuild and invalidation enqueueing.
includes/queue.php Style/doc updates for invalidation queue functions and cron worker.
includes/health.php Style/sanitization updates for Health page and AJAX handlers.
includes/settings.php Improved sanitization and WPCS-style formatting for settings + tools UI.
faq-jsonld.php Updated includes and reformatted plugin header/bootstrapping.
composer.json Enabled PHPCS installer plugin and added WPCS as a dev dependency.
composer.lock Updated lockfile to include newly added dev dependencies.
Review details

Suppressed comments (3)

includes/admin.php:142

  • selected() returns an attribute fragment like selected='selected'. Escaping it with esc_attr() HTML-encodes the quotes, producing invalid markup and preventing the correct option from being selected.
	foreach ( $assoc_types as $k => $label ) {
		$sel = selected( $assoc_type, $k, false );
		echo "<option value='" . esc_attr( $k ) . "' " . esc_attr( $sel ) . '>' . esc_html( $label ) . '</option>';
	}

includes/admin.php:192

  • With the frontend lookup now using exact = comparisons for URL mappings, storing URLs without normalization will cause regressions (e.g., trailing slash or query string differences will no longer match). Normalize URLs on save to the same shape used by the frontend (strip query string, trim trailing slash).
		foreach ( $lines as $l ) {
			$l = trim( $l );
			if ( empty( $l ) ) {
				continue;
			}
			$urls[] = esc_url_raw( $l );
		}
		$payload['urls'] = array_values( array_unique( $urls ) );

includes/frontend.php:84

  • After switching URL mappings to exact = comparisons, the lookup will no longer match mappings saved with a trailing slash (previously they matched via LIKE). Adding both trailing-slash variants keeps exact matching while remaining backward compatible with existing stored URL values.
	$permalink = get_permalink( $current_id );
	if ( $permalink ) {
		$candidate_values[] = array(
			'type'  => 'url',
			'value' => rtrim( strtok( $permalink, '?' ), '/' ),
		);
	}
  • Files reviewed: 10/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread includes/admin.php
Comment on lines +85 to 88
$data_json = get_post_meta( $post->ID, 'fqj_assoc_data_json', true );
$data_json = $data_json ? $data_json : '{}';
$data = json_decode( $data_json, true );

Comment thread includes/frontend.php
Comment on lines +66 to +75
// Terms.
$terms = wp_get_post_terms( $current_id );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
foreach ( $terms as $t ) {
$candidate_values[] = array(
'type' => 'term',
'value' => (string) $t->term_id,
);
}
}
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