Skip to content

Conversation

Coraxium-development
Copy link

@Coraxium-development Coraxium-development commented Sep 26, 2025

Adds a feature to include protected content in search results with customizable display options.

  • Introduces a new "Search Settings" page in the admin panel.
  • Allows admins to choose whether to include protected content in search results.
  • Provides options to customize the display of protected content in search results, including a "premium" label, disclaimer text, and custom signup/login URLs and labels.
  • Implements visual indicators and warnings for protected content in search results.

Summary by CodeRabbit

  • New Features

    • Added a Search Settings page and tab in plugin options.
    • Option to include protected content in site search results.
    • Visual indicators for premium/protected search results (title badge and disclaimer block).
    • Customisable labels/messages and custom signup/login URLs for those indicators.
    • Settings only affect search results for non‑privileged users.
  • Style

    • Minor admin UI spacing adjustment for multiline labels.
  • Security

    • Safer settings save with nonce protection.

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.
Copy link

coderabbitai bot commented Sep 26, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Bootstrap & wiring
wordpress/wp-content/plugins/memberful-wp/memberful-wp.php
Imports src/search_result_enhancements.php to load new search result filters and styles.
Options registry
wordpress/wp-content/plugins/memberful-wp/src/options.php
Adds new options with defaults: memberful_include_protected_in_search, memberful_show_search_disclaimer, custom signup/login URLs, premium label, disclaimer text, signup/login labels.
Search query filtering
wordpress/wp-content/plugins/memberful-wp/src/search_filter.php
Gates exclusion of protected posts by the new option; when enabled protected posts are not removed from search results, otherwise existing exclusion by disallowed IDs remains.
Search result decorations
wordpress/wp-content/plugins/memberful-wp/src/search_result_enhancements.php
New filters to append a premium badge to titles and a warning block to excerpts for protected posts in searches; constructs signup/login links (customisable); injects minimal CSS; all behavior gated by option and access checks.
Admin controller
wordpress/wp-content/plugins/memberful-wp/src/admin.php
Verifies nonce on save, persists new search-related settings, exposes them to views, adds memberful_wp_search_settings() subview and routes subpage=search_settings.
Admin URLs helper
wordpress/wp-content/plugins/memberful-wp/src/urls.php
Adds memberful_wp_plugin_search_settings_url($no_header = FALSE) to produce the Search Settings admin URL.
Admin navigation tab
wordpress/wp-content/plugins/memberful-wp/views/option_tabs.php
Adds “Search Settings” tab linked via the new URL helper.
Admin view (search settings)
wordpress/wp-content/plugins/memberful-wp/views/search_settings.php
New settings page with main toggle, conditional display options, customizable labels/messages and optional signup/login URLs, nonce-protected form and client-side toggle script.
Global marketing excerpt
wordpress/wp-content/plugins/memberful-wp/src/global_marketing.php
Reworks teaser handling to use excerpts (existing or trimmed content) and adjusts CSS/wrapping logic to operate on excerpt output.
Admin stylesheet
wordpress/wp-content/plugins/memberful-wp/stylesheets/admin.css
Minor UI spacing change: .memberful-label__text--multiline margin-left changed from 25px to 0px.

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
Loading
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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I nudge the burrows of search by night,
A “Premium” star to catch the sight— ✨
Toggle the gate, keep warnings bright,
Links to sign or log appear in light.
A careful hop, a helpful nudge—happy trails, from me, the judge. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request includes unrelated modifications to global_marketing.php’s excerpt logic and a minor admin stylesheet adjustment that are not part of the search-inclusion feature defined in MR-2. These changes concern global marketing content presentation and label styling rather than protected content search functionality. Including them in this branch detracts from the focus of the search feature. Please remove the excerpt refactoring in global_marketing.php and the unrelated admin stylesheet change from this branch and move them to a dedicated PR focused on marketing content and label styling so that the search feature PR remains focused and easier to review.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 succinctly and accurately captures the primary change of adding search customisation for protected content by referencing both search inclusion and the customisable elements without extraneous detail or ambiguity.
Linked Issues Check ✅ Passed The pull request implements the MR-2 objectives by reintroducing an admin toggle for including protected content in search results, providing clear UI warnings and a break-glass override, and restricting exposure to titles and excerpts only as a mitigation against past exploits. It adds all required options on the settings page, integrates nonce checks for security, and does not appear to disrupt SearchWP compatibility or reintroduce prior vulnerabilities. All key coding requirements from the linked issue have been met.
✨ 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 feature/MR-2-include-protected-content-in-search

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.

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

🧹 Nitpick comments (8)
wordpress/wp-content/plugins/memberful-wp/src/urls.php (1)

79-83: Avoid duplicating settings URL construction

Reuse 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 queries

pre_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 merge memberful_wp_user_disallowed_post_ids() when memberful_include_protected_in_search is false.
  • For per-user or per-search control, hook searchwp\query\args or searchwp\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 strings

Most 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 state

Prefer 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 verification

Nonce 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 safely

The 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 only

To 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.

📥 Commits

Reviewing files that changed from the base of the PR and between f0fd714 and 90d9ad1.

📒 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 added

Including 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” tab

Tab wiring and URL helper usage look correct.

wordpress/wp-content/plugins/memberful-wp/views/search_settings.php (1)

10-12: LGTM: form target and nonce

Action URL and nonce usage are correct.

wordpress/wp-content/plugins/memberful-wp/src/options.php (1)

28-35: LGTM: sane defaults for new options

Defaults 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.

Comment on lines +280 to +287
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'] ) );
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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' ) );
Copy link

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.

Comment on lines 78 to 79
$link_destination = get_option( 'memberful_search_link_destination', 'post' );

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
$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.
Copy link

@cursor cursor bot left a 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', '' );

Copy link

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.

Fix in Cursor Fix in Web

// 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'] ) );
}
Copy link

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.

Fix in Cursor Fix in Web

// Get settings
$show_disclaimer = get_option( 'memberful_search_show_disclaimer', 'yes' );
if ( $show_disclaimer !== 'yes' ) {
return $content;
Copy link

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)

Fix in Cursor Fix in Web

$disclaimer_html = apply_filters( 'memberful_search_disclaimer_html', $disclaimer_html, $post_id, $disclaimer_text );

$title .= $disclaimer_html;
}
Copy link

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.

Fix in Cursor Fix in Web

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

♻️ 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 with isset() 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 90d9ad1 and b173c39.

📒 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.

Comment on lines +289 to 294
// 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' ) );
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +69 to 73
$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() );
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

❓ 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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant