Skip to content

Conversation

@bogdan-mainwp
Copy link
Contributor

@bogdan-mainwp bogdan-mainwp commented Nov 17, 2025

Introduces wp_cache_get/wp_cache_set to cache results of various direct database queries across multiple classes, including MySQL version lookups, WooCommerce sales/top seller stats, attachment lookups, and PageSpeed/BackWPup/TimeCapsule metadata. This reduces redundant queries for static or infrequently changing data, improving performance and scalability. Dynamic or real-time queries (such as activity logs or backup progress) are left uncached to ensure up-to-date results. Also adds comments and improves query safety by whitelisting allowed values and clarifying phpcs:ignore reasons.

All Submissions:

Changes proposed in this Pull Request:

Closes # .

How to test the changes in this Pull Request:

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?

Changelog entry

Enter a summary of all changes on this Pull Request. This will appear in the changelog if accepted.

Summary by CodeRabbit

  • Refactor
    • Added caching across multiple server- and report-related features to reduce repeated database queries and speed responses.
    • Preserved existing behavior and public interfaces while optimizing internal data retrieval.
    • Introduced safer query handling, prepared-statement usage, and clearer inline documentation for database and non-cacheable/transient operations.

Introduces wp_cache_get/wp_cache_set to cache results of various direct database queries across multiple classes, including MySQL version lookups, WooCommerce sales/top seller stats, attachment lookups, and PageSpeed/BackWPup/TimeCapsule metadata. This reduces redundant queries for static or infrequently changing data, improving performance and scalability. Dynamic or real-time queries (such as activity logs or backup progress) are left uncached to ensure up-to-date results. Also adds comments and improves query safety by whitelisting allowed values and clarifying phpcs:ignore reasons.
@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

Walkthrough

Adds WP object-cache reads/writes and phpcs NoCaching annotations across multiple MainWP child classes, replacing repeated direct DB queries (VERSION(), reports, pagespeed fragments, attachment lookups) with cached lookups and per-feature caching layers while preserving external APIs.

Changes

Cohort / File(s) Summary
MySQL Version Caching
class/class-mainwp-child-back-wp-up.php, class/class-mainwp-child-server-information-base.php, class/class-mainwp-child-timecapsule.php
Use wp_cache_get/wp_cache_set with distinct keys/groups and 3600s TTL for MySQL VERSION() lookups; on miss execute SELECT VERSION() and cache the result; output behavior unchanged.
Pagespeed Data Optimization
class/class-mainwp-child-pagespeed.php
Add strategy-based caching (desktop/mobile) for cal_pagespeed_data and filter options, map strategy→score column, compose cache keys from strategy + SQL fragments, use prepared statements for strategy-aware queries, and cache final aggregated result.
WooCommerce Sales Status Filtering & Caching
class/class-mainwp-child-woocommerce-status.php
Introduce allowed_statuses whitelist and sanitized status set, build dynamic IN placeholders, compute date ranges, and add per-query wp_cache_get/wp_cache_set caching (1h) for sales/top_seller queries; query logic preserved.
Utility Attachment Lookup Cache
class/class-mainwp-utility.php
Add private static attachment_lookup_cache, check/store per-request lookup results (keys include md5 of filename/full_guid) instead of returning directly from query; preserves lookup branches but caches results.
Maintenance phpcs Annotations
class/class-mainwp-child-maintenance.php
Reflow comments and move/add phpcs:ignore / NoCaching annotations for revision-related DB queries; SQL and behavior unchanged.
Clone Install Annotations
class/class-mainwp-clone-install.php
Add comments clarifying foreign key checks are session-level and extend phpcs ignores to include NoCaching; SQL commands unchanged.
Timecapsule NoCaching Flags
class/class-mainwp-child-timecapsule.php
Add NoCaching phpcs annotations and comments to transient/real-time DB queries (get_backups, lazy_load_activity_log_wptc, get_display_rows); add caching for MySQL VERSION() in get_system_info as above.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Cache as WP_Object_Cache
    participant DB as Database

    rect rgb(235,245,255)
    Note over Caller,Cache: Cache hit (fast path)
    Caller->>Cache: wp_cache_get(cache_key, group)
    Cache-->>Caller: cached value
    Caller->>Caller: use cached value (return/compute)
    end

    rect rgb(255,245,235)
    Note over Caller,DB: Cache miss (fallback)
    Caller->>Cache: wp_cache_get(cache_key, group)
    Cache-->>Caller: false
    Caller->>DB: Execute query (SELECT VERSION() / reports / pagespeed)
    DB-->>Caller: result
    Caller->>Cache: wp_cache_set(cache_key, result, group, 3600)
    Cache-->>Caller: stored
    Caller->>Caller: compute aggregates / return result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Points to review closely:

  • class/class-mainwp-child-pagespeed.php — cache key composition, prepared-statement fragments, and strategy→column mapping.
  • class/class-mainwp-child-woocommerce-status.php — dynamic IN-clause generation and status sanitization; ensure date-range consistency and cache key entropy.
  • class/class-mainwp-utility.php — correctness and lifecycle of the per-request attachment_lookup_cache static property.
  • Consistency of cache groups, TTLs, and potential stale-data considerations across files.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add caching to database queries for performance' accurately summarizes the main change across all modified files in the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch improvements-by-plugin-checker

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
class/class-mainwp-child-maintenance.php (1)

263-264: LGTM! Appropriate justification for uncached query.

The comment correctly explains why caching is not suitable for this query—revision IDs are fetched for immediate deletion and the data becomes stale instantly. The prepared statement is properly parameterized.

Minor formatting note: Line 264 has a double space after FROM before $wpdb->posts.

Apply this diff to fix the formatting:

-            $results_posts    = $wpdb->get_results( $wpdb->prepare( "SELECT `ID`, `post_modified` FROM  $wpdb->posts WHERE `post_parent`= %d AND `post_type`='revision' ORDER BY `post_modified` ASC", $results[ $i ]->post_parent ) );
+            $results_posts    = $wpdb->get_results( $wpdb->prepare( "SELECT `ID`, `post_modified` FROM $wpdb->posts WHERE `post_parent`= %d AND `post_type`='revision' ORDER BY `post_modified` ASC", $results[ $i ]->post_parent ) );
class/class-mainwp-child-timecapsule.php (1)

1929-1935: MySQL version caching is implemented correctly; optional TTL constant usage

The MySQL version caching via wp_cache_get/wp_cache_set with a dedicated group is sound and avoids repeating the SELECT VERSION() call within the cache window. The strict false === $mysql_version check is also appropriate.

If you want to align with other WordPress code, you could swap the literal 3600 for HOUR_IN_SECONDS, but that’s purely stylistic.

class/class-mainwp-child-pagespeed.php (1)

530-552: Pagespeed caching + filter-option hardening look good; minor refactor opportunities

  • Caching the computed Pagespeed aggregates per strategy via wp_cache_get/wp_cache_set using a key derived from $strategy and get_filter_options( 'all' ) is a solid approach that avoids re-running the heavy queries on every sync.
  • The $allowed_score_columns mapping and selection of $score_column from the validated strategy prevent arbitrary column injection into the SQL.
  • Both gpi_page_stats and gpi_page_reports queries rely on the get_filter_options() contract (fragment in [0], values in [1] with %s placeholders), and the updated docblock plus the custom‑post‑type comment make that safety argument explicit.
  • Caching custom_url_types via wp_cache_get/wp_cache_set in get_filter_options() is a nice touch to cut down on repeated SELECT DISTINCT type FROM ...gpi_custom_urls calls, especially on larger datasets.

If you want to tighten things further without changing behavior:

  • Consider calling get_filter_options( 'all' ) only once in cal_pagespeed_data() and reusing the result for both $data_typestocheck and $reports_typestocheck to avoid duplicated work.
  • For consistency with other caches in this PR, you might also standardize TTL usage on HOUR_IN_SECONDS everywhere (where you aren’t already).

These are polish-level suggestions; the current implementation is functionally sound.

Also applies to: 557-568, 617-620, 652-653, 680-685

class/class-mainwp-child-woocommerce-status.php (1)

134-181: Status whitelisting and cached monthly sales/top-seller queries look safe; consider normalizing the cache key time component

Positives:

  • The woocommerce_reports_order_statuses result is intersected with an explicit $allowed_statuses list before being used in the IN ( {$placeholders} ) clause, which is a good hardening step.
  • Dynamic placeholders plus array_merge( $safe_statuses, array( $month_start, $month_end ) ) keep the query parameterized and avoid any injection via statuses or dates.
  • Caching both sales and top_seller results for the “current month” in the mainwp_woocommerce group with a 1‑hour TTL aligns with the PR’s performance goal.

One nuance: the cache keys include $month_end formatted as Y-m-d H:i:s. Because that value changes every second, repeated calls to sync_data() will almost always compute a new cache key, so cache hits will be rare and you may accumulate many short‑lived entries under a persistent object cache.

If you want the cache to be effective while still reflecting near‑real‑time stats, you could normalize the time part used in the key to an hour (or a day) while still using the full $month_end in the query, for example:

-        $month_start = date( 'Y-m-01' ); // phpcs:ignore -- local time.
-        $month_end   = date( 'Y-m-d H:i:s' ); // phpcs:ignore -- local time.
+        $month_start = date( 'Y-m-01' ); // phpcs:ignore -- local time.
+        $month_end   = date( 'Y-m-d H:i:s' ); // phpcs:ignore -- local time.
+        $cache_window = date( 'Y-m-d H:00:00' ); // Normalize to hour for cache key.

-        $cache_key = 'wc_sales_' . md5( implode( '_', $safe_statuses ) . '_' . $month_start . '_' . $month_end );
+        $cache_key = 'wc_sales_' . md5( implode( '_', $safe_statuses ) . '_' . $month_start . '_' . $cache_window );

-        $cache_key_top = 'wc_top_seller_' . md5( implode( '_', $safe_statuses ) . '_' . $month_start . '_' . $month_end );
+        $cache_key_top = 'wc_top_seller_' . md5( implode( '_', $safe_statuses ) . '_' . $month_start . '_' . $cache_window );

That keeps the 1‑hour TTL meaningful and avoids effectively “no‑op” caching.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 89069c6 and 16dba94.

📒 Files selected for processing (8)
  • class/class-mainwp-child-back-wp-up.php (1 hunks)
  • class/class-mainwp-child-maintenance.php (2 hunks)
  • class/class-mainwp-child-pagespeed.php (6 hunks)
  • class/class-mainwp-child-server-information-base.php (2 hunks)
  • class/class-mainwp-child-timecapsule.php (4 hunks)
  • class/class-mainwp-child-woocommerce-status.php (2 hunks)
  • class/class-mainwp-clone-install.php (2 hunks)
  • class/class-mainwp-utility.php (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (8)
class/class-mainwp-child-maintenance.php (1)

227-228: LGTM! Clear documentation for uncached query.

The added comment appropriately explains why this revision query should not be cached—the data is transient and changes immediately after cleanup. The prepared statement correctly parameterizes the query for security.

class/class-mainwp-clone-install.php (2)

345-346: LGTM - Appropriate documentation for session configuration.

The added comment correctly clarifies that this is a MySQL session setting rather than a data query, and the NoCaching phpcs:ignore flag is justified since session configuration commands don't produce cacheable results.


431-432: LGTM - Consistent documentation pattern.

The re-enabling of foreign key checks follows the same correct documentation pattern as the disabling statement, with appropriate clarification that caching is not applicable for session configuration.

class/class-mainwp-child-server-information-base.php (2)

456-466: LGTM - Effective caching for static server metadata.

The MySQL version caching implementation is sound:

  • Checks cache before querying
  • Uses appropriate 1-hour TTL for static server metadata
  • Correctly retrieves the value column from SHOW VARIABLES

564-582: LGTM - Consistent caching implementation.

The SQL mode caching follows the same effective pattern as the MySQL version caching. Static server metadata is an ideal candidate for caching with a 1-hour TTL.

class/class-mainwp-child-back-wp-up.php (1)

626-631: LGTM - Clean caching implementation for BackWPup integration.

The MySQL version caching is well-implemented:

  • Uses SELECT VERSION() which is a standard approach
  • Properly isolated in 'mainwp_backwpup' cache group
  • Includes clear documentation in the phpcs:ignore comment
class/class-mainwp-child-timecapsule.php (1)

455-463: Clarifying NoCaching annotations on dynamic WPTC queries looks correct

The added comments and WordPress.DB.DirectDatabaseQuery.NoCaching ignores clearly justify why these three queries stay uncached (backup list, lazy‑loaded activity log, and per‑row activity details are all transient / real‑time). The wpdb::prepare() usage and placeholder types also look safe in each case.

Nothing to change here from a behavior perspective.

Also applies to: 794-795, 845-847

class/class-mainwp-child-woocommerce-status.php (1)

285-363: Report-range sales/top-seller caching with status whitelist is consistent and correct

For the date‑range report path:

  • The same $allowed_statuses + array_intersect() pattern is used before building the IN ( {$placeholders} ) clause, which keeps the filtered statuses constrained to known values.
  • $start_date and $end_date are converted to Y-m-d H:i:s and then passed through prepare() along with the whitelisted statuses, so the query remains fully parameterized.
  • Caching sales and top_seller in mainwp_woocommerce under keys derived from the normalized date strings and status set should give good hit rates whenever the same range is requested repeatedly from the dashboard.

This block looks solid and aligned with the intent of reducing repeated heavy queries over identical ranges.

Added NOSONAR comments to clarify that MD5 is used for cache key generation only and not for cryptographic purposes in pagespeed and WooCommerce status classes.
Added a NOSONAR comment to clarify that MD5 is used for cache key generation only and not for cryptographic purposes, addressing static analysis warnings.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
class/class-mainwp-child-pagespeed.php (1)

597-608: Fix undefined $data when no filter options are returned (still present)

When get_filter_options( 'all' ) returns null (no types enabled), the if ( ! is_null( $reports_typestocheck ) ) block is skipped, $data is never defined, and is_array( $data[0] ) will emit PHP notices. This was flagged in an earlier review and remains unresolved.

Consider guarding the last‑modified computation with an explicit default variable, e.g.:

-        if ( ! is_null( $reports_typestocheck ) ) {
-            $gpi_page_stats = $wpdb->prefix . 'gpi_page_stats'; // @wordpress-security:ignore UnescapedDBParameter -- Table name is safe: wpdb->prefix + constant.
-				$data           = $wpdb->get_results( $wpdb->prepare( "SELECT $_select FROM $gpi_page_stats WHERE ( $reports_typestocheck[0] ) AND $nullcheck", $reports_typestocheck[1] ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $_select is hardcoded from switch statement (lines 589, 593), $gpi_page_stats is safe constant (line 598), $reports_typestocheck[0] is validated SQL fragment (line 557), $nullcheck is hardcoded (lines 588, 592).
-        }
-
-        $result = array(
-            'last_modified' => is_array( $data[0] ) && isset( $data[0]['last_modified'] ) ? $data[0]['last_modified'] : 0,
+        $last_modified = 0;
+        if ( ! is_null( $reports_typestocheck ) ) {
+            $gpi_page_stats = $wpdb->prefix . 'gpi_page_stats'; // @wordpress-security:ignore UnescapedDBParameter -- Table name is safe: wpdb->prefix + constant.
+            $data           = $wpdb->get_results(
+                $wpdb->prepare(
+                    "SELECT $_select FROM $gpi_page_stats WHERE ( $reports_typestocheck[0] ) AND $nullcheck",
+                    $reports_typestocheck[1]
+                ),
+                ARRAY_A
+            ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $_select is hardcoded, $gpi_page_stats is safe, $reports_typestocheck is validated.
+            if ( ! empty( $data[0]['last_modified'] ) ) {
+                $last_modified = $data[0]['last_modified'];
+            }
+        }
+
+        $result = array(
+            'last_modified' => $last_modified,
             'average_score' => $average_score,
             'total_pages'   => $total_pages,
         );

Please rerun with a configuration where get_filter_options( 'all' ) returns null (e.g., all types unchecked) to confirm the notices are gone.

🧹 Nitpick comments (4)
class/class-mainwp-child-woocommerce-status.php (2)

134-216: Status whitelist + cached sales/top‑seller query look sound

The status handling (filter → intersect with $allowed_statuses → fallback to ['completed']), dynamic IN‑clause placeholders, and cache keys tied to statuses + month range all look correct and safe; the queries remain parameterized and benefit from caching.

If you want to reduce duplication and future drift, consider extracting a small helper like get_safe_order_statuses_and_placeholders() that returns both $safe_statuses and $placeholders, and reuse it in both sync_data() and report_data(). Please also sanity‑check that the status slugs here match the versions of WooCommerce you still support on this legacy code path.


285-363: Report data caching mirrors sync logic correctly

The report‑range sales and top‑seller queries reuse the same status‑whitelisting and placeholder pattern, with cache keys bound to statuses + explicit start/end dates; that’s consistent with sync_data() and should avoid redundant DB work across identical report requests.

You might want to share a tiny internal helper for building the cache key (statuses + date window) so sync_data() and report_data() can’t diverge subtly over time; verify via a quick test that different permutations of the same statuses (e.g., ['completed','processing'] vs ['processing','completed']) don’t need to share a cache entry for your use case.

class/class-mainwp-child-pagespeed.php (2)

557-571: Reports query correctly scopes by strategy; consider reusing filter options

The updated reports query properly constrains by r.strategy = %s and passes the strategy plus the validated filter‑options array into $wpdb->prepare, keeping the SQL safe while enabling per‑strategy aggregation.

Since both $data_typestocheck and $reports_typestocheck are derived from get_filter_options( 'all' ), you could call it once, assign to a local variable, and reuse it for both queries to avoid duplicated option reads/DB lookups (the new custom_url_types caching already makes the underlying query cheap, but this would still simplify control flow).


617-619: Filter‑options contract and custom URL type caching look good

The updated docblock for get_filter_options() clearly documents the [0] SQL fragment / [1] parameters contract, and the new custom_url_types caching (via wp_cache_get/set in the mainwp_pagespeed group) is a simple, safe way to avoid repeated DISTINCT queries against gpi_custom_urls.

If you find yourself using different restrict_type values frequently, you might later extend the custom_url_types cache key to include $restrict_type or split caches by use‑case, but the current global key is fine given the single underlying query; a quick runtime test with/without custom URLs will verify that behavior matches expectations.

Also applies to: 652-652, 680-685

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 16dba94 and d61799f.

📒 Files selected for processing (3)
  • class/class-mainwp-child-pagespeed.php (6 hunks)
  • class/class-mainwp-child-woocommerce-status.php (2 hunks)
  • class/class-mainwp-utility.php (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (2)
class/class-mainwp-utility.php (1)

518-536: Attachment lookup caching looks correct

Cache key derivation, cache group usage, and the 1‑hour TTL are consistent and safe; query behavior on hits/misses is unchanged apart from avoiding duplicate DB work. No correctness issues spotted here.

If you want extra confidence, you can add a quick unit/integration test that calls get_maybe_existed_attached_id() twice with the same $filename/$full_guid and asserts the second call does not hit the DB (e.g., via a stubbed $wpdb or object cache).

class/class-mainwp-child-pagespeed.php (1)

530-552: Result caching and score column whitelisting are implemented safely

Using get_filter_options( 'all' ) as part of the cache key and whitelisting the score column via $allowed_score_columns ensures both correct cache partitioning and protection against SQL injection when interpolating $score_column into the query; the rest of the query remains parameterized via $wpdb->prepare.

If you’d like, add a small test that feeds bogus $strategy values into cal_pagespeed_data() and confirms they’re rejected, and that switching between desktop/mobile yields different cache keys and results.

Replaces persistent caching with request-scoped static caching in get_maybe_existed_attached_id to avoid unnecessary cache pollution and improve performance. Also initializes the $data variable in MainWP_Child_Pagespeed to prevent undefined variable issues.
@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
class/class-mainwp-child-pagespeed.php (1)

597-605: Still possible undefined offset on $data[0] when no rows are returned

Even though $data is now initialized to an empty array, the expression is_array( $data[0] ) && isset( $data[0]['last_modified'] ) will still trigger a PHP notice when $data is empty, because $data[0] is an undefined offset. This can happen if:

  • get_filter_options( 'all' ) returns null (no types enabled) → the query is skipped and $data stays array(), or
  • The query runs but returns zero rows for the selected types.

You can avoid this by computing $last_modified separately and only reading $data[0]['last_modified'] when it exists, as previously suggested:

-        $data = array();
-
-        if ( ! is_null( $reports_typestocheck ) ) {
-            $gpi_page_stats = $wpdb->prefix . 'gpi_page_stats'; // @wordpress-security:ignore UnescapedDBParameter -- Table name is safe: wpdb->prefix + constant.
-            $data           = $wpdb->get_results( $wpdb->prepare( "SELECT $_select FROM $gpi_page_stats WHERE ( $reports_typestocheck[0] ) AND $nullcheck", $reports_typestocheck[1] ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $_select is hardcoded from switch statement (lines 589, 593), $gpi_page_stats is safe constant (line 598), $reports_typestocheck[0] is validated SQL fragment (line 557), $nullcheck is hardcoded (lines 588, 592).
-        }
-
-        $result = array(
-            'last_modified' => is_array( $data[0] ) && isset( $data[0]['last_modified'] ) ? $data[0]['last_modified'] : 0,
+        $last_modified = 0;
+        if ( ! is_null( $reports_typestocheck ) ) {
+            $gpi_page_stats = $wpdb->prefix . 'gpi_page_stats'; // @wordpress-security:ignore UnescapedDBParameter -- Table name is safe: wpdb->prefix + constant.
+            $data           = $wpdb->get_results(
+                $wpdb->prepare(
+                    "SELECT $_select FROM $gpi_page_stats WHERE ( $reports_typestocheck[0] ) AND $nullcheck",
+                    $reports_typestocheck[1]
+                ),
+                ARRAY_A
+            ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $_select is hardcoded, $gpi_page_stats and $reports_typestocheck are validated.
+            if ( ! empty( $data[0]['last_modified'] ) ) {
+                $last_modified = $data[0]['last_modified'];
+            }
+        }
+
+        $result = array(
+            'last_modified' => $last_modified,
             'average_score' => $average_score,
             'total_pages'   => $total_pages,
         );

This keeps behavior the same when data exists but removes undefined-offset notices when it does not.

🧹 Nitpick comments (2)
class/class-mainwp-child-pagespeed.php (2)

530-535: Caching strategy looks sound; consider avoiding duplicate filter computation

The new cache layer keyed by strategy and the serialized filter fragment ($data_typestocheck) is reasonable, and using the mainwp_pagespeed group with HOUR_IN_SECONDS TTL is consistent with the rest of the PR. The whitelisting of $score_column via $allowed_score_columns keeps the dynamic column safe.

You can shave some work and keep the logic more obviously consistent by reusing the first get_filter_options( 'all' ) result instead of recomputing it for $reports_typestocheck, e.g.:

-        $data_typestocheck = self::get_filter_options( 'all' );
+        $data_typestocheck = self::get_filter_options( 'all' );
         ...
-        $reports_typestocheck = self::get_filter_options( 'all' );
+        $reports_typestocheck = $data_typestocheck;

(Or rename to an appropriate shared variable.) This is optional, but reduces one DB-backed call per execution path.

Also applies to: 537-552, 557-571, 609-610


537-542: Guard against future misuse of $strategy when indexing $allowed_score_columns

Right now $strategy is validated earlier to be either 'desktop' or 'mobile', so $allowed_score_columns[ $strategy ] is safe. If this function ever gets called from another path without that guard, it could emit a notice.

A small defensive tweak keeps it robust:

-        $score_column = $allowed_score_columns[ $strategy ];
+        $score_column = isset( $allowed_score_columns[ $strategy ] )
+            ? $allowed_score_columns[ $strategy ]
+            : 'desktop_score'; // or return false here if you prefer strictness

This is not required today but makes the function safer against future refactors.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d61799f and 8ed723b.

📒 Files selected for processing (2)
  • class/class-mainwp-child-pagespeed.php (6 hunks)
  • class/class-mainwp-utility.php (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (4)
class/class-mainwp-child-pagespeed.php (1)

619-621: get_filter_options() return contract and custom URL caching look good

The clarified docblock explaining the two-element return structure ([0] SQL fragment, [1] values array) matches the actual usage elsewhere in the file. The added comment around custom post types being validated via get_post_types() + whitelist is also accurate.

The new custom_url_types object-cache layer correctly:

  • Uses a stable key in the mainwp_pagespeed group.
  • Caches the DISTINCT type list for an hour.
  • Preserves behavior by only iterating when the cached value is non-empty.

No issues here from a correctness standpoint.

Also applies to: 654-654, 682-687

class/class-mainwp-utility.php (3)

34-39: LGTM! Per-request cache avoids stale data issues.

The per-request caching approach using a static property is a solid solution. Unlike persistent caching (e.g., wp_cache_set with TTL), this approach eliminates concerns about serving stale data when attachments are modified between requests, while still providing performance benefits within a single request.


513-513: Good documentation fix.

The return type documentation now correctly reflects that the function returns an array of attachment post objects from $wpdb->get_results(), not a single integer ID.


524-540: LGTM! Caching implementation is correct.

The caching logic is well-structured:

  • Cache keys properly differentiate between full GUID and suffix lookups
  • Both query branches store results before returning
  • phpcs:ignore comments accurately explain why direct queries are acceptable here

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.

2 participants