-
Notifications
You must be signed in to change notification settings - Fork 0
Adds customizable search for protected content #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Adds customizable search for protected content #1
Conversation
This update introduces a new setting in the options page to allow users to include protected content in search results, along with visual indicators and warnings for non-members. The search filter logic has been adjusted to respect this setting, ensuring that protected content titles and excerpts are displayed appropriately while maintaining access control.
This update introduces new options in the settings page to control the display of disclaimers in search result excerpts for protected content. Users can now specify where the "Sign up to access" link should direct, with options for custom signup and login URLs. Additionally, nonce verification has been added for security when saving changes.
This update introduces new settings for customizing the display of premium content indicators and disclaimers in search results. Users can now specify the premium label, disclaimer text, and the text for signup and login links directly from the options page, enhancing the flexibility of the search experience for protected content.
This update introduces a dedicated search settings page, allowing users to configure how protected content appears in search results. Users can now enable or disable the inclusion of protected content, customize disclaimer messages, and set specific signup and login link texts. Additionally, new options for managing the visibility of these settings have been added, enhancing the overall user experience.
WalkthroughAdds an optional “include protected content in search” feature: new admin UI and options, toggles search query filtering, decorates search results for protected posts (title badge and excerpt disclaimer with signup/login links), loads a new module, and provides a settings tab and helper URL. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant WP as WordPress
participant MF as Memberful Plugin
participant ACL as Access Check
rect rgba(230,240,255,0.5)
note over User,WP: Search flow (include_protected_in_search ON/OFF)
User->>WP: Submit search query
WP->>MF: pre_get_posts (search_filter)
alt include_protected_in_search = TRUE
MF-->>WP: Do not add post__not_in (allow protected posts)
else include_protected_in_search = FALSE
MF->>WP: Set post__not_in = disallowed IDs (exclude protected)
end
WP->>MF: the_title / the_excerpt filters for each result
loop per result
MF->>ACL: memberful_can_user_access_post(post)
alt No access AND is_search() AND option enabled
MF-->>WP: Append "Premium" badge to title
MF-->>WP: Append disclaimer block with signup/login links to excerpt
else
MF-->>WP: Return original title/excerpt
end
end
WP-->>User: Render search results
end
sequenceDiagram
autonumber
actor Admin
participant WPAdmin as WP Admin UI
participant MF as Memberful Plugin Admin
participant Store as Options Store
Admin->>WPAdmin: Open Memberful > Search Settings
WPAdmin->>MF: Render search_settings view
MF->>Store: Load current option values
MF-->>WPAdmin: Render HTML form (nonce + fields)
Admin->>WPAdmin: Submit Save Changes
WPAdmin->>MF: POST with nonce
MF->>MF: Verify nonce
alt nonce valid
MF->>Store: Sanitize and save option values
MF-->>WPAdmin: Redirect/refresh with updated settings
else nonce invalid
MF-->>WPAdmin: Abort / error (no save)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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
🧹 Nitpick comments (8)
wordpress/wp-content/plugins/memberful-wp/src/urls.php (1)
79-83
: Avoid duplicating settings URL constructionReuse the existing helper to keep behaviour consistent and reduce duplication.
Apply:
-function memberful_wp_plugin_search_settings_url($no_header = FALSE) { - $header_parameter = $no_header === TRUE ? "&noheader=true" : ""; - - return admin_url('options-general.php?page=memberful_options&subpage=search_settings'.$header_parameter); -} +function memberful_wp_plugin_search_settings_url($no_header = FALSE) { + return memberful_wp_plugin_settings_url($no_header, 'search_settings'); +}wordpress/wp-content/plugins/memberful-wp/src/search_filter.php (2)
24-41
: Limit to main query to avoid side effects on secondary queriespre_get_posts runs for all queries. Guarding to the main query prevents accidental filtering of widgets/secondary loops.
You can add this near the top of memberful_wp_protect_search():
// Only affect the main query if ( ! $query->is_main_query() ) { return; }
24-41
: Add SearchWP filters to exclude protected posts
- Use the
searchwp\post__not_in
filter to mergememberful_wp_user_disallowed_post_ids()
whenmemberful_include_protected_in_search
is false.- For per-user or per-search control, hook
searchwp\query\args
orsearchwp\query\results
to remove posts the current user can’t access.- Place this code in a standalone “SearchWP Customisations” plugin with proper capability checks.
wordpress/wp-content/plugins/memberful-wp/views/search_settings.php (2)
7-9
: Internationalise UI stringsMost strings aren’t wrapped in i18n helpers, unlike other views. This blocks localisation.
Example pattern:
-<h1>Search Settings</h1> -<p>Configure how protected content appears in WordPress search results.</p> +<h1><?php echo esc_html__( 'Search Settings', 'memberful' ); ?></h1> +<p><?php echo esc_html__( 'Configure how protected content appears in WordPress search results.', 'memberful' ); ?></p> -<h3>Search Protection</h3> +<h3><?php echo esc_html__( 'Search Protection', 'memberful' ); ?></h3> -<span class="memberful-label__text--multiline">Include protected content in search results (with access warnings).</span> +<span class="memberful-label__text--multiline"><?php echo esc_html__( 'Include protected content in search results (with access warnings).', 'memberful' ); ?></span>Apply similarly to the remaining labels/help text.
Also applies to: 14-23, 28-51, 55-85, 89-91
17-19
: Use checked() for checkbox statePrefer WordPress’ checked() helper for clarity and to avoid attribute typos.
-<input ... type="checkbox" name="memberful_include_protected_in_search" <?php if( $include_protected_in_search): ?>checked="checked"<?php endif; ?>> +<input ... type="checkbox" name="memberful_include_protected_in_search" <?php checked( (bool) $include_protected_in_search ); ?>> -<input ... type="checkbox" name="memberful_show_search_disclaimer" <?php if( $show_search_disclaimer): ?>checked="checked"<?php endif; ?>> +<input ... type="checkbox" name="memberful_show_search_disclaimer" <?php checked( (bool) $show_search_disclaimer ); ?>>Also applies to: 31-33
wordpress/wp-content/plugins/memberful-wp/src/admin.php (1)
270-274
: Duplicate nonce verificationNonce is already verified at Lines 243–245 for all POSTs. This second check is redundant.
- // Verify nonce for security - if ( ! memberful_wp_valid_nonce( 'memberful_options' ) ) { - return; - }wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php (2)
45-71
: Avoid shadowing $post and rely on the global safelyThe parameter name $post conflicts with the global $post you read later. Use a different local var to prevent confusion.
-function memberful_wp_add_protection_warning_to_excerpt( $excerpt, $post = null ) { +function memberful_wp_add_protection_warning_to_excerpt( $excerpt, $post_arg = null ) { ... - if ( $post && is_object( $post ) ) { - $post_id = $post->ID; - } elseif ( is_numeric( $post ) ) { - $post_id = $post; + if ( $post_arg && is_object( $post_arg ) ) { + $post_id = $post_arg->ID; + } elseif ( is_numeric( $post_arg ) ) { + $post_id = $post_arg; } else { - global $post; - $post_id = $post ? $post->ID : null; + global $post; + $post_id = isset( $post->ID ) ? $post->ID : null; }
11-13
: Scope enhancements to frontend search onlyTo avoid affecting admin searches and non-loop contexts, consider early bails for is_admin() and in_the_loop().
Example:
add_filter( 'the_title', 'memberful_wp_add_protection_indicator_to_title', 10, 2 ); add_filter( 'get_the_excerpt', 'memberful_wp_add_protection_warning_to_excerpt', 10, 2 ); function memberful_wp_add_protection_indicator_to_title( $title, $post_id = null ) { - // Only apply to search results and when the option is enabled - if ( ! is_search() || ! get_option( 'memberful_include_protected_in_search', FALSE ) ) { + if ( is_admin() || ! in_the_loop() || ! is_search() || ! get_option( 'memberful_include_protected_in_search', FALSE ) ) { return $title; }And similarly guard memberful_wp_search_result_styles() with is_admin() if desired.
Also applies to: 127-153
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (8)
wordpress/wp-content/plugins/memberful-wp/memberful-wp.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/src/admin.php
(4 hunks)wordpress/wp-content/plugins/memberful-wp/src/options.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/src/search_filter.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/src/urls.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/views/option_tabs.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/views/search_settings.php
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
wordpress/wp-content/plugins/memberful-wp/src/admin.php (1)
wordpress/wp-content/plugins/memberful-wp/src/core-ext.php (2)
memberful_wp_valid_nonce
(11-13)memberful_wp_render
(62-66)
wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php (2)
wordpress/wp-content/plugins/memberful-wp/src/acl.php (1)
memberful_can_user_access_post
(289-344)wordpress/wp-content/plugins/memberful-wp/src/urls.php (2)
memberful_registration_page_url
(28-30)memberful_sign_in_url
(7-9)
wordpress/wp-content/plugins/memberful-wp/views/option_tabs.php (1)
wordpress/wp-content/plugins/memberful-wp/src/urls.php (1)
memberful_wp_plugin_search_settings_url
(79-83)
wordpress/wp-content/plugins/memberful-wp/views/search_settings.php (2)
wordpress/wp-content/plugins/memberful-wp/src/core-ext.php (2)
memberful_wp_render
(62-66)memberful_wp_nonce_field
(15-17)wordpress/wp-content/plugins/memberful-wp/src/urls.php (1)
memberful_wp_plugin_search_settings_url
(79-83)
wordpress/wp-content/plugins/memberful-wp/src/search_filter.php (1)
wordpress/wp-content/plugins/memberful-wp/src/acl.php (1)
memberful_wp_user_disallowed_post_ids
(12-37)
🪛 PHPMD (2.15.0)
wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php
78-78: Avoid unused local variables such as '$link_destination'. (undefined)
(UnusedLocalVariable)
🔇 Additional comments (5)
wordpress/wp-content/plugins/memberful-wp/memberful-wp.php (1)
50-50
: LGTM: module load addedIncluding search_result_enhancements.php in the bootstrap looks correct and aligns with the new feature.
wordpress/wp-content/plugins/memberful-wp/views/option_tabs.php (1)
8-12
: LGTM: new “Search Settings” tabTab wiring and URL helper usage look correct.
wordpress/wp-content/plugins/memberful-wp/views/search_settings.php (1)
10-12
: LGTM: form target and nonceAction URL and nonce usage are correct.
wordpress/wp-content/plugins/memberful-wp/src/options.php (1)
28-35
: LGTM: sane defaults for new optionsDefaults are sensible and align with the feature’s safest posture (disallowed by default).
wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php (1)
17-37
: memberful_can_user_access_post expects an integer user ID
get_current_user_id() correctly provides the required ID; substituting a WP_User object would break the ACL lookup.Likely an incorrect or invalid review comment.
update_option( 'memberful_include_protected_in_search', isset( $_POST['memberful_include_protected_in_search'] ) ); | ||
update_option( 'memberful_show_search_disclaimer', isset( $_POST['memberful_show_search_disclaimer'] ) ); | ||
update_option( 'memberful_search_custom_signup_url', esc_url_raw( $_POST['memberful_search_custom_signup_url'] ) ); | ||
update_option( 'memberful_search_custom_login_url', esc_url_raw( $_POST['memberful_search_custom_login_url'] ) ); | ||
update_option( 'memberful_search_premium_label', sanitize_text_field( $_POST['memberful_search_premium_label'] ) ); | ||
update_option( 'memberful_search_disclaimer_text', sanitize_textarea_field( $_POST['memberful_search_disclaimer_text'] ) ); | ||
update_option( 'memberful_search_signup_text', sanitize_text_field( $_POST['memberful_search_signup_text'] ) ); | ||
update_option( 'memberful_search_login_text', sanitize_text_field( $_POST['memberful_search_login_text'] ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid undefined index notices when saving from other subpages
These fields may be missing when saving from non–Search Settings forms, causing PHP notices. Guard with isset and default to empty string.
- update_option( 'memberful_search_custom_signup_url', esc_url_raw( $_POST['memberful_search_custom_signup_url'] ) );
- update_option( 'memberful_search_custom_login_url', esc_url_raw( $_POST['memberful_search_custom_login_url'] ) );
- update_option( 'memberful_search_premium_label', sanitize_text_field( $_POST['memberful_search_premium_label'] ) );
- update_option( 'memberful_search_disclaimer_text', sanitize_textarea_field( $_POST['memberful_search_disclaimer_text'] ) );
- update_option( 'memberful_search_signup_text', sanitize_text_field( $_POST['memberful_search_signup_text'] ) );
- update_option( 'memberful_search_login_text', sanitize_text_field( $_POST['memberful_search_login_text'] ) );
+ update_option( 'memberful_search_custom_signup_url', esc_url_raw( isset( $_POST['memberful_search_custom_signup_url'] ) ? $_POST['memberful_search_custom_signup_url'] : '' ) );
+ update_option( 'memberful_search_custom_login_url', esc_url_raw( isset( $_POST['memberful_search_custom_login_url'] ) ? $_POST['memberful_search_custom_login_url'] : '' ) );
+ update_option( 'memberful_search_premium_label', sanitize_text_field( isset( $_POST['memberful_search_premium_label'] ) ? $_POST['memberful_search_premium_label'] : '' ) );
+ update_option( 'memberful_search_disclaimer_text', sanitize_textarea_field( isset( $_POST['memberful_search_disclaimer_text'] ) ? $_POST['memberful_search_disclaimer_text'] : '' ) );
+ update_option( 'memberful_search_signup_text', sanitize_text_field( isset( $_POST['memberful_search_signup_text'] ) ? $_POST['memberful_search_signup_text'] : '' ) );
+ update_option( 'memberful_search_login_text', sanitize_text_field( isset( $_POST['memberful_search_login_text'] ) ? $_POST['memberful_search_login_text'] : '' ) );
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
update_option( 'memberful_include_protected_in_search', isset( $_POST['memberful_include_protected_in_search'] ) ); | |
update_option( 'memberful_show_search_disclaimer', isset( $_POST['memberful_show_search_disclaimer'] ) ); | |
update_option( 'memberful_search_custom_signup_url', esc_url_raw( $_POST['memberful_search_custom_signup_url'] ) ); | |
update_option( 'memberful_search_custom_login_url', esc_url_raw( $_POST['memberful_search_custom_login_url'] ) ); | |
update_option( 'memberful_search_premium_label', sanitize_text_field( $_POST['memberful_search_premium_label'] ) ); | |
update_option( 'memberful_search_disclaimer_text', sanitize_textarea_field( $_POST['memberful_search_disclaimer_text'] ) ); | |
update_option( 'memberful_search_signup_text', sanitize_text_field( $_POST['memberful_search_signup_text'] ) ); | |
update_option( 'memberful_search_login_text', sanitize_text_field( $_POST['memberful_search_login_text'] ) ); | |
update_option( 'memberful_include_protected_in_search', isset( $_POST['memberful_include_protected_in_search'] ) ); | |
update_option( 'memberful_show_search_disclaimer', isset( $_POST['memberful_show_search_disclaimer'] ) ); | |
update_option( 'memberful_search_custom_signup_url', esc_url_raw( isset( $_POST['memberful_search_custom_signup_url'] ) ? $_POST['memberful_search_custom_signup_url'] : '' ) ); | |
update_option( 'memberful_search_custom_login_url', esc_url_raw( isset( $_POST['memberful_search_custom_login_url'] ) ? $_POST['memberful_search_custom_login_url'] : '' ) ); | |
update_option( 'memberful_search_premium_label', sanitize_text_field( isset( $_POST['memberful_search_premium_label'] ) ? $_POST['memberful_search_premium_label'] : '' ) ); | |
update_option( 'memberful_search_disclaimer_text', sanitize_textarea_field( isset( $_POST['memberful_search_disclaimer_text'] ) ? $_POST['memberful_search_disclaimer_text'] : '' ) ); | |
update_option( 'memberful_search_signup_text', sanitize_text_field( isset( $_POST['memberful_search_signup_text'] ) ? $_POST['memberful_search_signup_text'] : '' ) ); | |
update_option( 'memberful_search_login_text', sanitize_text_field( isset( $_POST['memberful_search_login_text'] ) ? $_POST['memberful_search_login_text'] : '' ) ); |
🤖 Prompt for AI Agents
In wordpress/wp-content/plugins/memberful-wp/src/admin.php around lines 280 to
287, several update_option calls read $_POST keys directly which can trigger
undefined index notices when the form is submitted from other admin subpages;
guard each $_POST access with isset(...) and default to an empty string (e.g.
$val = isset($_POST['key']) ? sanitize_xxx($_POST['key']) : '') before calling
update_option so sanitizers always receive a string and no PHP notices occur.
update_option( 'memberful_search_signup_text', sanitize_text_field( $_POST['memberful_search_signup_text'] ) ); | ||
update_option( 'memberful_search_login_text', sanitize_text_field( $_POST['memberful_search_login_text'] ) ); | ||
|
||
return wp_redirect( admin_url( 'options-general.php?page=memberful_options' ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Preserve subpage on redirect after Save
After saving on the Search Settings page, the redirect drops the subpage param and lands on the main settings page. Preserve the current subpage for a seamless UX.
- return wp_redirect( admin_url( 'options-general.php?page=memberful_options' ) );
+ $subpage = isset( $_GET['subpage'] ) ? sanitize_text_field( $_GET['subpage'] ) : '';
+ if ( $subpage ) {
+ return wp_redirect( memberful_wp_plugin_settings_url( false, $subpage ) );
+ }
+ return wp_redirect( admin_url( 'options-general.php?page=memberful_options' ) );
🤖 Prompt for AI Agents
In wordpress/wp-content/plugins/memberful-wp/src/admin.php around line 289, the
redirect after saving drops the current subpage query parameter; read the
current subpage/tab param from the request (e.g., $_REQUEST['subpage'] or
$_REQUEST['tab']), build the redirect URL using add_query_arg to include that
param (fall back to no extra param if not present), and return wp_redirect() to
that URL so the user stays on the same subpage after save.
$link_destination = get_option( 'memberful_search_link_destination', 'post' ); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused variable and undefined option
$link_destination is unused and the option isn’t defined. Clean it up.
- $link_destination = get_option( 'memberful_search_link_destination', 'post' );
-
As per static analysis hints.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
$link_destination = get_option( 'memberful_search_link_destination', 'post' ); | |
🧰 Tools
🪛 PHPMD (2.15.0)
78-78: Avoid unused local variables such as '$link_destination'. (undefined)
(UnusedLocalVariable)
🤖 Prompt for AI Agents
In wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php
around lines 78-79, remove the unused $link_destination variable assignment
(get_option('memberful_search_link_destination', 'post')) because the option
isn't defined; delete this line and any other references to $link_destination,
and if the behavior controlled by that option is required instead of removal,
properly register and define the option via the plugin settings API
(register_setting/add_option) before calling get_option.
This update introduces several improvements to the handling of protected content in search results. Key changes include the addition of a redirection mechanism for subpages in the options settings, a shift from teaser to excerpt for displaying content previews, and the implementation of a comprehensive disclaimer system. Developers can now customize the appearance and behavior of protection indicators, disclaimers, and associated actions through new hooks and filters, enhancing flexibility and user experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
// Get signup/login URLs | ||
$signup_url = get_option( 'memberful_search_signup_url', '' ); | ||
$login_url = get_option( 'memberful_search_login_url', '' ); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Search Result Options and Filters Issues
The search result enhancements have a few option-related issues. Custom signup and login URLs aren't retrieved because the code uses incorrect option keys (memberful_search_signup_url
and memberful_search_login_url
instead of their _custom_
variants). The search disclaimer setting also has a name mismatch (memberful_search_show_disclaimer
vs memberful_show_search_disclaimer
) and a type mismatch (expecting string 'yes'
but receiving a boolean), which prevents the disclaimer from ever appearing. Additionally, both the_title
and the_content
filters can add disclaimer HTML, potentially causing duplicates.
// Check if we're on a subpage and redirect back to it | ||
if ( ! empty( $_GET['subpage'] ) ) { | ||
return wp_redirect( admin_url( 'options-general.php?page=memberful_options&subpage=' . $_GET['subpage'] ) ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Settings Save Bug Clears Search Config
Saving changes from any settings page clears existing search settings, as all search-related options are unconditionally updated with potentially missing $_POST
values. This also leads to PHP notices from accessing undefined $_POST
keys. Additionally, the $_GET['subpage']
parameter is used unsanitized in redirects, posing an open redirect or XSS risk.
// Get settings | ||
$show_disclaimer = get_option( 'memberful_search_show_disclaimer', 'yes' ); | ||
if ( $show_disclaimer !== 'yes' ) { | ||
return $content; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Boolean vs String Comparison Bug
The memberful_search_show_disclaimer
option is being compared against the string 'yes'
, but its actual value is a boolean TRUE
. This type mismatch causes the conditional checks to fail, preventing the protected content disclaimer from appearing in search results as expected.
Additional Locations (1)
$disclaimer_html = apply_filters( 'memberful_search_disclaimer_html', $disclaimer_html, $post_id, $disclaimer_text ); | ||
|
||
$title .= $disclaimer_html; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: HTML Disclaimer Breaks Post Title Rendering
The memberful_wp_add_protection_indicator_to_title
function appends full HTML disclaimer content directly to the post title. This can break layouts or cause incorrect rendering in contexts expecting plain text, such as HTML title tags, navigation, or feeds.
There was a problem hiding this 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
♻️ Duplicate comments (1)
wordpress/wp-content/plugins/memberful-wp/src/admin.php (1)
280-287
: Guard $_POST access to prevent undefined index notices.These fields may be missing when saving from other admin subpages, causing PHP notices. Each
$_POST
key should be checked withisset()
before sanitisation.Apply this diff to add isset checks:
- update_option( 'memberful_search_custom_signup_url', esc_url_raw( $_POST['memberful_search_custom_signup_url'] ) ); - update_option( 'memberful_search_custom_login_url', esc_url_raw( $_POST['memberful_search_custom_login_url'] ) ); - update_option( 'memberful_search_premium_label', sanitize_text_field( $_POST['memberful_search_premium_label'] ) ); - update_option( 'memberful_search_disclaimer_text', sanitize_textarea_field( $_POST['memberful_search_disclaimer_text'] ) ); - update_option( 'memberful_search_signup_text', sanitize_text_field( $_POST['memberful_search_signup_text'] ) ); - update_option( 'memberful_search_login_text', sanitize_text_field( $_POST['memberful_search_login_text'] ) ); + update_option( 'memberful_search_custom_signup_url', esc_url_raw( isset( $_POST['memberful_search_custom_signup_url'] ) ? $_POST['memberful_search_custom_signup_url'] : '' ) ); + update_option( 'memberful_search_custom_login_url', esc_url_raw( isset( $_POST['memberful_search_custom_login_url'] ) ? $_POST['memberful_search_custom_login_url'] : '' ) ); + update_option( 'memberful_search_premium_label', sanitize_text_field( isset( $_POST['memberful_search_premium_label'] ) ? $_POST['memberful_search_premium_label'] : '' ) ); + update_option( 'memberful_search_disclaimer_text', sanitize_textarea_field( isset( $_POST['memberful_search_disclaimer_text'] ) ? $_POST['memberful_search_disclaimer_text'] : '' ) ); + update_option( 'memberful_search_signup_text', sanitize_text_field( isset( $_POST['memberful_search_signup_text'] ) ? $_POST['memberful_search_signup_text'] : '' ) ); + update_option( 'memberful_search_login_text', sanitize_text_field( isset( $_POST['memberful_search_login_text'] ) ? $_POST['memberful_search_login_text'] : '' ) );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
wordpress/wp-content/plugins/memberful-wp/src/admin.php
(4 hunks)wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php
(1 hunks)wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css
🚧 Files skipped from review as they are similar to previous changes (1)
- wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php
🧰 Additional context used
🧬 Code graph analysis (1)
wordpress/wp-content/plugins/memberful-wp/src/admin.php (1)
wordpress/wp-content/plugins/memberful-wp/src/core-ext.php (2)
memberful_wp_valid_nonce
(11-13)memberful_wp_render
(62-66)
⏰ 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: Cursor Bugbot
🔇 Additional comments (6)
wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php (2)
55-67
: LGTM! Excerpt generation logic is sound.The implementation correctly prioritises manual excerpts and falls back to auto-generated excerpts using
wp_trim_words
. The 30-word limit with ellipsis provides a reasonable preview length.
75-75
: LGTM! Return structure is correct.The concatenation of wrapped excerpt and marketing content provides the expected paywall preview experience.
wordpress/wp-content/plugins/memberful-wp/src/admin.php (4)
270-273
: Nonce verification correctly added.The nonce check at the start of the
save_changes
block provides CSRF protection for the form submission.
318-319
: LGTM! Search settings navigation integrated correctly.The new case correctly routes to the search settings subpage handler.
331-360
: LGTM! Search options properly retrieved and passed to template.All new search-related options are fetched and passed to the render function with appropriate variable names.
363-386
: LGTM! Search settings subpage function is well-structured.The
memberful_wp_search_settings
function correctly retrieves all search options and renders the dedicated search settings view.
// Check if we're on a subpage and redirect back to it | ||
if ( ! empty( $_GET['subpage'] ) ) { | ||
return wp_redirect( admin_url( 'options-general.php?page=memberful_options&subpage=' . $_GET['subpage'] ) ); | ||
} | ||
|
||
return wp_redirect( admin_url( 'options-general.php?page=memberful_options' ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sanitise subpage parameter before redirect.
Whilst the subpage preservation logic is good, the $_GET['subpage']
value should be sanitised before use in the redirect URL to prevent open redirect or injection issues.
Apply this diff to sanitise the subpage parameter:
// Check if we're on a subpage and redirect back to it
if ( ! empty( $_GET['subpage'] ) ) {
- return wp_redirect( admin_url( 'options-general.php?page=memberful_options&subpage=' . $_GET['subpage'] ) );
+ $subpage = sanitize_key( $_GET['subpage'] );
+ return wp_redirect( admin_url( 'options-general.php?page=memberful_options&subpage=' . $subpage ) );
}
🤖 Prompt for AI Agents
In wordpress/wp-content/plugins/memberful-wp/src/admin.php around lines 289 to
294, the code directly uses $_GET['subpage'] in the redirect URL which can allow
open-redirect or injection; sanitize the value first (use wp_unslash +
sanitize_text_field) and encode it for a URL (rawurlencode or urlencode) before
appending to admin_url, then use the sanitized variable in the redirect call so
the subpage parameter is safe.
$wrapped_excerpt = "<div class='memberful-global-teaser-content'>$excerpt</div>"; | ||
|
||
if ( $has_teaser && ! did_action( 'memberful_teaser_css' ) ) { | ||
$wrapped_teaser .= apply_filters( 'memberful_teaser_css', memberful_get_teaser_css() ); | ||
if ( $has_excerpt && ! did_action( 'memberful_teaser_css' ) ) { | ||
$wrapped_excerpt .= apply_filters( 'memberful_teaser_css', memberful_get_teaser_css() ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
Verify CSS injection occurs only once per request.
The did_action('memberful_teaser_css')
check prevents duplicate CSS injection, which is good. However, ensure the action hook memberful_teaser_css
is fired by apply_filters
on line 72, as did_action
checks for actions, not filters.
Run the following script to verify if memberful_teaser_css
is used as an action elsewhere:
🏁 Script executed:
#!/bin/bash
# Search for do_action or add_action calls with memberful_teaser_css
rg -n "do_action\s*\(\s*['\"]memberful_teaser_css['\"]" --type=php
rg -n "add_action\s*\(\s*['\"]memberful_teaser_css['\"]" --type=php
Length of output: 0
Replace did_action with did_filter for ‘memberful_teaser_css’
did_action only tracks do_action hooks, so it never returns true for apply_filters. As a result, the CSS snippet will be appended on every excerpt. Use did_filter('memberful_teaser_css') to prevent duplicate injections.
🤖 Prompt for AI Agents
In wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php around
lines 69 to 73, the conditional uses did_action('memberful_teaser_css') but
apply_filters was used to append the CSS, so replace did_action with
did_filter('memberful_teaser_css') to correctly detect previous filter runs and
prevent duplicate CSS injections; keep the rest of the condition ($has_excerpt
&& ! did_filter(...)) unchanged.
Adds a feature to include protected content in search results with customizable display options.
Summary by CodeRabbit
New Features
Style
Security