Skip to content

fix: audit improvements — bugs, security, PHP 8.1 types, PHP 8.4 compat#6

Draft
tccengineering wants to merge 14 commits into
mainfrom
fix/audit-improvements
Draft

fix: audit improvements — bugs, security, PHP 8.1 types, PHP 8.4 compat#6
tccengineering wants to merge 14 commits into
mainfrom
fix/audit-improvements

Conversation

@tccengineering
Copy link
Copy Markdown

@tccengineering tccengineering commented May 12, 2026

Summary

  • Bug fixes — CommentModelFactory typo/undefined variable, Config::get() default overwrite, Route strtok/match check, REST extract() removal, EmailView subject param never reaching template
  • SecurityREQUEST_URI sanitised via sanitize_url(wp_unslash(...)), WP_ENV restricted to [a-zA-Z0-9_-], preg_replace backreference injection closed via preg_replace_callback, _doing_it_wrong() warning when REST permission_callback is omitted
  • PHP 8.1 type declarations — Typed properties, parameter types, and return types added across the entire framework
  • PHP 8.4 compatibility — Dynamic properties addressed with #[\AllowDynamicProperties] on model classes; Templater dynamic property bug fixed; untyped static properties resolved
  • ArchitectureWPModelFactory gains wrap_models() and apply_meta_fields() base methods (duplicate code removed from all four factories); AdminAjax::$auth_only parameter and verify_nonce() helper added
  • Performance & logic fixes — Three controller setup loops collapsed into one; redundant REST endpoint scan eliminated; EmailView::send() multi-recipient return value fixed

Commits (13)

  1. Fix four bugs in CommentModelFactory
  2. Fix Config::get() default value and glob() safety
  3. Fix Route request URI handling and match check
  4. Fix REST: remove extract() and warn on missing permission_callback
  5. Fix EmailView filter leak and preg_replace backreference injection
  6. Add $auth_only parameter to AdminAjax and verify_nonce() helper
  7. Replace gettype() checks with instanceof across all models
  8. Replace isset/ternary patterns with null coalescing operator
  9. Extract wrap_models() and apply_meta_fields() to WPModelFactory base
  10. Remove dead code: phantom ControllerSetup import and commented asserts
  11. Add PHP 8.1 type declarations throughout the framework
  12. Fix PHP 8.4 compatibility issues
  13. Fix performance issues and logic bugs

Test plan

  • Run PHPCS against the changed files with the TCC standard
  • Verify CommentModelFactory::get_by_id(), create_comment(), delete_comment() against a real WordPress install
  • Confirm Config::get() returns $default when key is absent
  • Send an email via EmailView and confirm {{ subject }} is interpolated in the body template
  • Register a REST endpoint without permission_callback and confirm _doing_it_wrong() fires
  • Register an AdminAjax endpoint with $auth_only = true and confirm unauthenticated requests 403
  • Confirm no E_DEPRECATED dynamic-property notices under PHP 8.4
  • Register a REST endpoint and confirm handle_perm_callback + handle_callback both resolve correctly with a single set_current_endpoint() call
  • Pass an array of addresses to EmailView::send() and confirm the return value reflects actual send results

🤖 Generated with Claude Code

zhickson and others added 12 commits May 12, 2026 23:31
- Typo $commend → $comment caused undefined variable return from get_by_id()
- update_comment() discarded get_by_id() result, always returned null
- create_comment() called set_comment_meta() without the required $comment argument
- delete_comment() used $comment->ID instead of $comment->comment_ID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- get() previously overrode \$default immediately with the empty config array;
  now returns \$default correctly when the config name does not exist
- glob() can return false on failure; use ?: [] to ensure a safe iterable
- Sanitise WP_ENV before interpolating into a filesystem glob path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sanitise REQUEST_URI with wp_unslash/sanitize_url before use
- Replace strtok() (stateful) with parse_url() to safely strip query string
- Check the return value of preg_match() directly instead of checking
  whether the \$matches array is non-empty; the old check silently swallowed
  regex errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace extract() in register_endpoints() and build_route() with explicit
  array key access; extract() on arbitrary data pollutes the local namespace
- Emit _doing_it_wrong() when an endpoint is registered without a
  permission_callback so developers are notified rather than silently
  getting public access by default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- endpoint() now accepts an optional \$auth_only flag (default false);
  when true, only the wp_ajax_ hook is registered so the endpoint is
  restricted to logged-in users — existing callers are unaffected
- Add static verify_nonce() helper so callbacks have a one-liner to
  validate nonces and die with 403 on failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- wp_mail filters were registered anew on every EmailView instantiation and
  could never be removed; use a static flag so they are registered once
- shortcodes() used preg_replace() where the replacement string is parsed for
  \$1/\\1 backreferences; replace with preg_replace_callback() returning the
  value via closure so special characters in parameter values are treated
  literally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gettype() === 'object' accepts any object; instanceof checks the exact
expected type and is more readable and type-safe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use ?? throughout rather than the verbose isset() ? x : y form.
Also simplify Templater constructor by assigning directly to properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both methods were copy-pasted verbatim into all four concrete factories.
Defining them once in WPModelFactory eliminates the duplication; each
factory's set_*_meta() method now delegates to apply_meta_fields().
Also declare abstract wrap() on WPModelFactory to enforce the contract.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ControllerSetup does not exist in the codebase; remove the use statement
- 13 commented-out assert() calls scattered across model and view files
  served no purpose and added noise; removed entirely

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Typed properties on all classes (string, array, ?Config, ?\WP_Post, etc.)
- Parameter and return types on every public/protected method
- WPMeta and WPTaxonomyTerms interfaces updated with typed signatures;
  all concrete implementations updated to match
- Templater: fix bug introduced in null-coalescing commit where \$slug
  local variable was removed but still referenced in template_slug assignment
- CommentModel::update() also fixed to use comment_ID key (not ID)
- UserModelFactory PHPDoc @param corrected from WP_Post to WP_User
- Render return type expressed as string|false|null to reflect actual
  behaviour: outputs directly when \$output=true (returns null), returns
  string|false when \$output=false

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dynamic properties are deprecated since PHP 8.2 and remain E_DEPRECATED
in PHP 8.4. Five model classes use __set() to intentionally forward
unknown property writes as dynamic properties on the model — mark them
with #[\AllowDynamicProperties] to make the intent explicit and silence
the deprecation. PHP 9 will require this attribute for any class that
creates dynamic properties.

Also fixes two issues in EmailView:
- Static property $filters_registered lacked a type declaration
  (untyped static properties are deprecated in PHP 8.4)
- $content_templater->subject = $subject wrote a dynamic property onto
  Templater (same deprecation) and was also a latent bug: the value was
  never passed into $this->params so it was invisible to extract() inside
  the template. Pass it via the params array at construction instead.

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

coderabbitai Bot commented May 12, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: TheCodeCompany/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 57b5d0c9-32ac-4cc8-bf1f-bff4d48a27df

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-improvements

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@zhickson zhickson self-requested a review May 12, 2026 14:15
zhickson and others added 2 commits May 13, 2026 00:16
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Application: collapse three controller setup loops into one pass,
  cutting apply_filters() calls from 6N to 6 per controller and fixing
  a bug where filter-replaced instances were lost between loops
- REST: remove redundant current_request assignment and set_current_endpoint()
  call in handle_callback(); handle_perm_callback() always runs first so the
  work was already done, wasting a full endpoint regex scan per request
- EmailView: fix send() returning false for all array-recipient calls
  regardless of outcome; now returns true only if every recipient succeeds

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.

2 participants