Skip to content

Harden title filtering scope and migration notice safety#1

Open
mehrazmorshed wants to merge 2 commits into
mainfrom
codex/analyze-and-improve-hide-titles-plugin
Open

Harden title filtering scope and migration notice safety#1
mehrazmorshed wants to merge 2 commits into
mainfrom
codex/analyze-and-improve-hide-titles-plugin

Conversation

@mehrazmorshed
Copy link
Copy Markdown
Owner

@mehrazmorshed mehrazmorshed commented Apr 30, 2026

Motivation

  • Prevent the plugin from unintentionally blanking titles in feeds, AJAX/JSON responses, non-main queries, or outside the main loop.
  • Avoid saving meta for unsupported post types and stop showing the migration/admin notice to users without plugin-install/activate privileges.

Description

  • Tightened the_title filter in hide_titles_filter_title to bail early for admin contexts, feeds/AJAX/JSON, non-main queries, out-of-loop calls, and non-singular views before reading the per-post _hide_title meta.
  • Restricted hide_titles_save_meta to only run for the supported post types by checking get_post_type( $post_id ) before updating or deleting _hide_title.
  • Hardened the migration/admin notice ht_show_migration_notice by gating it with current_user_can( 'install_plugins' ), safely loading is_plugin_active() via require_once ABSPATH . 'wp-admin/includes/plugin.php' when needed, and escaping the HTML message with wp_kses_post( __( ... ) ).
  • Small readability and consistency fixes (normalized function signatures/spacing and option handling).

Testing

  • php -l hide-titles.php was run and returned no syntax errors (passes).

Codex Task

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved title hiding behavior to work correctly in feeds, AJAX requests, and JSON responses with refined filtering logic.
    • Refined title hiding scope to apply only to singular views for better content control and consistency.
    • Enhanced security with proper capability verification and improved notice sanitization.
    • Added migration notice to guide users through important updates.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@mehrazmorshed has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 35 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8ea2743e-e92c-4eb4-b9d8-5a19ce838db1

📥 Commits

Reviewing files that changed from the base of the PR and between fa5df2d and 08a6fd9.

📒 Files selected for processing (2)
  • css/hide-titles-settings.css
  • hide-titles.php
📝 Walkthrough

Walkthrough

The hide-titles.php file has been rewritten with significant logic modifications. Changes include restricting meta saving to specific post types, refining title filtering to account for feeds/AJAX/JSON requests and main query status, adding security checks to the migration notice function, and registering the notice via the admin_notices hook.

Changes

Cohort / File(s) Summary
Core Functionality Updates
hide-titles.php
Rewrote hide_titles_save_meta to restrict _hide_title meta to post and page types; modified hide_titles_filter_title with additional guards for feeds, AJAX, JSON, and non-main-loop contexts; enhanced ht_show_migration_notice with capability checks and conditional plugin.php requirement; registered migration notice via admin_notices hook; standardized function signature formatting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰✨ A title hides when conditions align,
Posts and pages protected by design,
Security whispers through every line,
Feeds skip the magic, AJAX can't define,
Our burrow of code, refined and fine! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Harden title filtering scope and migration notice safety' directly and accurately summarizes the two main security-focused changes: tightening the title filter scope and hardening the migration notice with proper safety checks.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/analyze-and-improve-hide-titles-plugin

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
Review rate limit: 0/1 reviews remaining, refill in 54 minutes and 35 seconds.

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

Copy link
Copy Markdown

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hide-titles.php`:
- Around line 126-127: The anchor elements using target="_blank" (e.g., the <a
class="link" href="https://profiles.wordpress.org/mehrazmorshed/"
target="_blank"> occurrences in hide-titles.php and the other similar anchors
around the given ranges) should include rel="noopener noreferrer" to prevent
reverse-tabnabbing; update every <a ... target="_blank"> instance (including the
ones mentioned at the other occurrences) to add rel="noopener noreferrer" while
preserving existing attributes and escaping via esc_url/esc_attr as currently
used.
- Around line 168-175: The legacy hide_titles() callback registered with
add_filter('the_title','hide_titles') unconditionally returns false when
get_option('hide-titles-option') == 'all' and thus blanks titles before the
guarded hide_titles_filter_title() can run; remove the entire legacy conditional
block that defines hide_titles() and its add_filter call, then incorporate the
global option check (get_option('hide-titles-option') === 'all') into
hide_titles_filter_title() so that the core filter logic only blanks titles
after the existing admin/feed/AJAX/JSON/main-query guards pass.
- Line 8: The plugin declares compatibility back to WP 4.4 but calls
wp_is_json_request() unguarded; wrap that call in a compatibility guard (e.g.,
if ( function_exists('wp_is_json_request') ? wp_is_json_request() : /* fallback
check */ ) or use function_exists('wp_is_json_request') to only call it when
available) or provide a small polyfill that emulates wp_is_json_request() for
older installs; update the code paths that reference wp_is_json_request() (the
call on Line ~293) to use this guarded call so older WP versions won't fatal.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd311db5-0794-4c9d-bc90-a3d99ab0821d

📥 Commits

Reviewing files that changed from the base of the PR and between fa76a99 and fa5df2d.

📒 Files selected for processing (1)
  • hide-titles.php

Comment thread hide-titles.php
* Plugin URI: https://wordpress.org/plugins/hide-titles/
* Description: Remove Titles from Posts and Single Pages on WordPress.
* Version: 1.10
* Requires at least: 4.4
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In WordPress core, which version introduced the function wp_is_json_request()?

💡 Result:

WordPress core version 5.0.0 introduced the function wp_is_json_request.

Citations:


🏁 Script executed:

fd -type f -name "hide-titles.php"

Repository: mehrazmorshed/hide-titles

Length of output: 240


🏁 Script executed:

fd --type file -name "hide-titles.php"

Repository: mehrazmorshed/hide-titles

Length of output: 300


🏁 Script executed:

fd hide-titles.php

Repository: mehrazmorshed/hide-titles

Length of output: 85


🏁 Script executed:

cat -n hide-titles.php | head -20

Repository: mehrazmorshed/hide-titles

Length of output: 881


🏁 Script executed:

cat -n hide-titles.php | sed -n '290,300p'

Repository: mehrazmorshed/hide-titles

Length of output: 352


🏁 Script executed:

rg "wp_is_json_request" hide-titles.php

Repository: mehrazmorshed/hide-titles

Length of output: 135


Guard wp_is_json_request() for stated minimum WordPress compatibility.

Line 8 declares support down to WordPress 4.4, but Line 293 calls wp_is_json_request() unconditionally, which was introduced in WordPress 5.0.0 and will cause a fatal error on older installs.

Suggested fix
-    if ( is_feed() || wp_doing_ajax() || wp_is_json_request() ) {
+    $is_json_request = function_exists( 'wp_is_json_request' ) && wp_is_json_request();
+    if ( is_feed() || wp_doing_ajax() || $is_json_request ) {
         return $title;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hide-titles.php` at line 8, The plugin declares compatibility back to WP 4.4
but calls wp_is_json_request() unguarded; wrap that call in a compatibility
guard (e.g., if ( function_exists('wp_is_json_request') ? wp_is_json_request() :
/* fallback check */ ) or use function_exists('wp_is_json_request') to only call
it when available) or provide a small polyfill that emulates
wp_is_json_request() for older installs; update the code paths that reference
wp_is_json_request() (the call on Line ~293) to use this guarded call so older
WP versions won't fatal.

Comment thread hide-titles.php
Comment on lines +126 to +127
<a class="link" href="https://profiles.wordpress.org/mehrazmorshed/" target="_blank">
<img class="center" src="<?php echo esc_url( plugin_dir_url( __FILE__ ) . 'img/author.png' ); ?>" width="128" height="128" alt="">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add rel="noopener noreferrer" to all external target="_blank" links.

Several external links open in a new tab without rel, which leaves a reverse-tabnabbing gap in admin UI.

Suggested fix (pattern)
-<a class="link" href="https://profiles.wordpress.org/mehrazmorshed/" target="_blank">
+<a class="link" href="https://profiles.wordpress.org/mehrazmorshed/" target="_blank" rel="noopener noreferrer">

Also applies to: 132-136, 141-151

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hide-titles.php` around lines 126 - 127, The anchor elements using
target="_blank" (e.g., the <a class="link"
href="https://profiles.wordpress.org/mehrazmorshed/" target="_blank">
occurrences in hide-titles.php and the other similar anchors around the given
ranges) should include rel="noopener noreferrer" to prevent reverse-tabnabbing;
update every <a ... target="_blank"> instance (including the ones mentioned at
the other occurrences) to add rel="noopener noreferrer" while preserving
existing attributes and escaping via esc_url/esc_attr as currently used.

Comment thread hide-titles.php Outdated
Copy link
Copy Markdown

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

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 several hardening measures and security improvements to the plugin, such as restricting title filtering to singular pages within the main query and adding permission checks for migration notices. The review feedback highlights opportunities to further improve the code by applying consistent hardening to the global title filter, enhancing theme compatibility for title detection, and optimizing the post-saving logic to handle revisions and use more idiomatic PHP patterns.

Comment thread hide-titles.php Outdated
Comment on lines +170 to +173
function hide_titles() {

return false;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The hardening logic applied to hide_titles_filter_title (bailing for feeds, AJAX, admin, etc.) should also be applied to this global hide_titles filter. Currently, if the "Hide all titles" option is enabled, titles will be hidden site-wide, including in RSS feeds, AJAX responses, and the admin area, which contradicts the hardening goals of this PR. Additionally, the filter should accept and return the $title argument to follow WordPress standards.

    function hide_titles( $title ) {
        if ( is_admin() || is_feed() || wp_doing_ajax() || wp_is_json_request() ) {
            return $title;
        }

        if ( ! in_the_loop() || ! is_main_query() || ! is_singular() ) {
            return $title;
        }

        return '';
    }

Comment thread hide-titles.php
Comment on lines +297 to +303
if ( ! in_the_loop() || ! is_main_query() ) {
return $title;
}

if ( ! is_singular() ) {
return $title;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using in_the_loop() is a common way to target the main content, but it can be too restrictive for many themes. Some themes output the main title in a hero or header section before the loop starts (i.e., before the_post() is called), which would cause this filter to bail and fail to hide the title.

A more robust and compatible way to target the main title on singular pages is to compare the current post ID with the queried object ID while ensuring it is the main query.

    if ( ! is_singular() || ! is_main_query() || (int) $id !== get_queried_object_id() ) {
        return $title;
    }

Comment thread hide-titles.php
Comment on lines +276 to +278
if ( 'post' !== get_post_type( $post_id ) && 'page' !== get_post_type( $post_id ) ) {
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Calling get_post_type() multiple times is slightly inefficient. Storing the result in a variable is preferred. Additionally, it is best practice to bail early for post revisions to avoid unnecessary meta updates on revision objects during the save process.

    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }

    $post_type = get_post_type( $post_id );
    if ( 'post' !== $post_type && 'page' !== $post_type ) {
        return;
    }

Comment thread hide-titles.php
return;
}

if ( array_key_exists( 'hide_titles_checkbox', $_POST ) ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In WordPress, checkboxes are only present in the $_POST array if they are checked. Using isset() is more idiomatic and slightly more efficient than array_key_exists() for this purpose.

    if ( isset( $_POST['hide_titles_checkbox'] ) ) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant