Skip to content

Conversation

@mrcasual
Copy link
Collaborator

@mrcasual mrcasual commented Sep 19, 2025

✨ Improved

  • Notices and frontend messages for shortcodes with invalid or missing secret attributes are clearer and can be dismissed globally.
  • Shortcodes referencing the same View they are embedded in no longer require a secret.

🐛 Fixed

  • Entry Link block not working with secure Views requiring a secret.
  • Export widget not working when Views were filtered to show entries created by the logged-in user.
  • Fields linked to Single Entry layouts are now exported as p`xlain text values, not hyperlinks, when using direct CSV/TSV export URLs.
  • Featured entries in the Layout Builder template now display with the intended styling.
  • Single Entry pages not rendering when search filter parameters were present in the URL.

🔧 Updated

Summary by CodeRabbit

  • New Features

    • Entry Link block supports a “secret” attribute (auto-filled) and Views now add template-specific container classes and stable anchors.
    • Improved handling for nested/embedded Views to render correctly.
  • Improvements

    • Clearer, actionable shortcode notices for missing/invalid secrets; notices can be globally dismissed.
  • Bug Fixes

    • CSV/TSV exports produce plain text (no links).
    • Entry Link with secure Views fixed.
    • Export widget and filtering edge-cases fixed.
    • Featured entry styling and Single Entry page filter issues resolved.
  • Chores

    • Version bumped to 2.46.2.

💾 Build file (304428a).

zahardev and others added 27 commits September 15, 2025 10:25
This implements #2460.

Before installing, add the Entry Link block using version 2.46.1 and
confirm that it fails to display the link. Then install the latest build
and test the following:

1) Re-save the page with the existing block (without changing any
options) and confirm that the link now displays correctly.
2) Add a new Entry Link block and confirm that it works as expected.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Entry Link block now supports secure Views by accepting a secret,
ensuring links work with protected content.
* Selecting a View in the editor auto-populates the secret; existing
blocks are backfilled when possible.

* **Bug Fixes**
* Resolved issues where the Entry Link block failed with Views requiring
a secret.

* **Documentation**
* Changelog updated with a development note about the Entry Link fix and
a recent patch release entry.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

💾 [Build
file](https://www.dropbox.com/scl/fi/hg825famnaryy05g85rt6/gravityview-2.46.1-5d5b71a62.zip?rlkey=tfqbi9k4kjenhu4j8cr2bp2cv&dl=1)
(5d5b71a).
…2458)

Previously, the Export Link widget generated nonces as user ID 0 (guest)
when building REST API links. Because WordPress nonces are tied to a user
ID, this caused a mismatch: the nonce was created as guest but validated
against the logged-in user. Advanced Filtering merge tags like {user:ID}
therefore resolved to 0, producing empty CSV exports.

This change ensures that REST API links in the Export widget are generated
with the current user's nonce, restoring proper CSV export functionality
for embed-only Views with user-specific filters.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added consistent CSS classes to the outer containers of Layout
Builder, List, and Table views.
* Improves theme compatibility and makes it easier to target these views
with custom CSS.
* Enables designers to apply precise styling without modifying content
or existing IDs.
* Maintains current behavior and output structure; only the container
classes are newly exposed for styling purposes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

💾 [Build
file](https://www.dropbox.com/scl/fi/a0krpxommyym3yu1cuo7o/gravityview-2.46.1-19071ffa2.zip?rlkey=9aezx8wnuaptxgi4jytbmy7hn&dl=1)
(19071ff).
## Summary
- Fixes missing hook class support for featured entries in Layout
Builder
- Ensures proper class handling for feature-based entries

## Test Plan
- [x] Verify Layout Builder displays featured entries correctly
- [x] Confirm class hooks are properly applied


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Layout Builder views now support per-entry CSS class customization via
filters for greater styling control.
- **Bug Fixes**
  - Featured entries no longer receive the unintended white background.
  - Non-featured entries retain a white background for contrast.
  - Improved vertical spacing between consecutive entries.
- **Documentation**
  - Changelog updated to note the featured-entry styling fix.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

💾 [Build
file](https://www.dropbox.com/scl/fi/hlofcr8ndbs2ps41yqwxf/gravityview-2.46.1-445e1da14.zip?rlkey=y0oah19x1enjue8sztfqox2ba&dl=1)
(445e1da).
This allows us to keep track of which Views are currently being rendered,
including nesting and parent-child relationships. Helper methods make it
easy to check if a View is embedded, determine rendering depth, retrieve
the current stack, and identify the parent of an embedded View.
Implements #2464.

This also adds support for filtering Views embedded within Single Entry
pages, whether inserted using the shortcode in a Custom Content field or
through a GravityView View field.

To test:

* Verify that adding URL filter parameters (for existing or non-existent
fields) does not prevent Single Entry pages from rendering.
* Embed another View in the Single Entry layout and confirm that
filtering works both when accessing the View directly and when it is
embedded in a page or post.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Added rendering-context tracking to detect primary vs embedded views
and handle nested rendering reliably.

- Enhancements
- Improved embedded-view filtering and shortcode handling so embedded
views can apply filters more flexibly while preserving main-view
behavior.

- Bug Fixes
- Fixed Single Entry pages failing to render when search parameters were
present.

- Tests
- Added comprehensive unit tests covering rendering tracking,
nested/embedded scenarios, and filtering behavior.

- Documentation
  - Updated changelog with the Single Entry rendering fix.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

💾 [Build
file](https://www.dropbox.com/scl/fi/9r83i1nn5hhb7b82vcico/gravityview-2.46.1-5ae8ab00a.zip?rlkey=4ymce59qsqgwblc2ny0bl19bm&dl=1)
(5ae8ab0).
@coderabbitai
Copy link

coderabbitai bot commented Sep 19, 2025

Walkthrough

Adds a rendering-tracking API and integrates it into the view renderer with deterministic cleanup, legacy override short-circuits, anchor handling, and multisort/CSV adjustments; wires a secret attribute through the Entry Link block and shortcode; makes the search widget rendering-aware; adds entry-class hook and layout-builder CSS fixes; bumps version and adds tests.

Changes

Cohort / File(s) Summary
Rendering tracking & renderer flow
future/includes/class-gv-view.php, future/includes/class-gv-renderer-view.php
Adds GV\View rendering stack (push/pop/query/reset) and integrates tracking into the renderer. Wraps render in try/finally, ensures output-buffer and Legacy_Context cleanup, supports legacy override short-circuit, anchor_id tracking, multisort normalization, and CSV link suppression.
Search widget rendering-awareness
includes/widgets/search-widget/class-search-widget.php, tests/unit-tests/GravityView_Widget_Search_Test.php
Uses GV\View::is_rendering to gate gf_query_filter and prepare_field_filter behavior for rendering contexts; adds an integration test exercising embedded view filtering (note: duplicate test method present).
Shortcode secret handling
future/includes/class-gv-shortcode.php
Allows bypass of secret when rendering inside same View; builds dynamic shortcode/secret messages and admin notices; returns consistent WP_Error messages for invalid/missing secret.
Gutenberg Entry Link “secret”
future/includes/gutenberg/blocks/entry-link/block.json, future/includes/gutenberg/blocks/entry-link/block.php, future/includes/gutenberg/blocks/entry-link/edit.js, future/includes/gutenberg/build/entry-link.asset.php
Adds secret block attribute, maps it through to [gv_entry_link], auto-populates editor attribute from selected view, and updates build asset version hash.
Layout Builder entry class & styles
future/includes/class-gv-template-view-layout-builder.php, templates/css/layout-builder.css, templates/css/source/layout-builder/_view_entry.scss
Adds public static entry_class(...) to compute per-entry classes with filters; changes SCSS/CSS so non-featured entries get white background while preserving featured-entry styling.
Template wrappers: classes and anchor ids
templates/views/gravityview-layout-builder.php, templates/views/list.php, templates/views/table.php
Computes gv-template-{type} container class and anchor_id once per render and updates wrapper markup to include both id and class.
Export widget nonce & globals
includes/widgets/class-gravityview-widget-export-link.php
Adds a _wpnonce (wp_rest) param to export URL, removes global user-switching during nonce creation, and minor doc/format tweaks.
Compatibility notice
includes/class-gravityview-compatibility.php
Marks PHP-version compatibility notice as globally_dismissible => true.
Version, readme, translations
gravityview.php, readme.txt, translations.pot
Bumps plugin version to 2.46.2, updates changelog, and refreshes POT references/metadata.
Tests: rendering tracking
tests/unit-tests/GV/GV_View_Tracking_Test.php
New unit test suite validating GV\View rendering stack API, nested/embedded render scenarios, and integration with renderer lifecycle.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant WP as WordPress
  participant Shortcode as GV Shortcode
  participant Renderer as GV View Renderer
  participant GV as GV\View
  participant Legacy as Legacy Template
  participant Template as Template Class

  WP->>Shortcode: parse [gravityview] attrs
  Shortcode-->>Shortcode: bypass secret if same-view rendering
  Shortcode->>Renderer: render(view, request)

  rect rgba(200,220,255,0.18)
    note over Renderer: render lifecycle
    Renderer->>GV: push_rendering(view_id)
    Renderer->>Renderer: determine template_slug & entries (hide_until_searched, multisort)
    alt legacy override present
      Renderer->>Legacy: render parts via legacy override
      Legacy-->>Renderer: output
    else normal flow
      Renderer->>Template: resolve class & instantiate
      Renderer->>Renderer: set anchor_id, begin buffering
      Template-->>Renderer: render output
      Renderer->>Renderer: capture buffer
    end
    Renderer->>GV: pop_rendering()
    Renderer-->>WP: return output
  end
Loading
sequenceDiagram
  autonumber
  participant Editor as Block Editor
  participant Block as Entry Link Block
  participant Data as gkGravityViewBlocks.views
  participant Shortcode as [gv_entry_link]

  Editor->>Block: select viewId
  Block->>Data: lookup view (includes secret)
  Data-->>Block: { secret }
  Block-->>Block: setAttributes({ viewId, secret, reset preview })
  Block->>Shortcode: render with mapped attributes (secret => secret)
  Shortcode-->>Editor: produced link/HTML
Loading
sequenceDiagram
  autonumber
  participant Frontend as Frontend Request
  participant SW as Search Widget
  participant GV as GV\View

  Frontend->>SW: gf_query_filter()
  SW->>GV: is_rendering(view_id)?
  alt rendering context
    SW-->>Frontend: apply filters (allow multi-field bypass when rendering)
  else non-rendering (single-entry or real request)
    SW-->>Frontend: skip or apply filters per context rules
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Release 2.46.2" is a short, single-line summary that directly reflects the primary purpose of the changeset (a version/release bump with associated fixes and updates), so it meets the guideline for a concise, relevant PR title.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f6e146 and 304428a.

📒 Files selected for processing (2)
  • future/includes/class-gv-renderer-view.php (2 hunks)
  • future/includes/gutenberg/blocks/entry-link/edit.js (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • future/includes/gutenberg/blocks/entry-link/edit.js
🧰 Additional context used
🧬 Code graph analysis (1)
future/includes/class-gv-renderer-view.php (4)
future/includes/class-gv-view.php (4)
  • push_rendering (947-949)
  • set_anchor_id (2023-2025)
  • get_anchor_id (2034-2043)
  • pop_rendering (960-962)
future/lib/class-gamajo-template-loader.php (1)
  • get_template_part (111-121)
future/includes/class-gv-renderer.php (1)
  • legacy_template_warning (408-412)
future/_mocks.php (3)
  • Legacy_Context (393-755)
  • push (436-439)
  • pop (446-448)
⏰ 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: test_and_package
🔇 Additional comments (10)
future/includes/class-gv-renderer-view.php (10)

41-42: LGTM!

The rendering tracking implementation correctly pushes the view ID to the rendering stack at the start of the render process. This aligns with the new rendering-tracking API from future/includes/class-gv-view.php and enables proper nested rendering detection.


44-82: LGTM!

The template slug determination and entries fetching logic is well-structured with proper filtering and conditional logic. The handling of hide_until_searched and search state is correct, properly gating entry fetching when no search is performed.


100-110: Hook cleanup properly implemented!

The legacy warning callback is now properly stored in a variable and removed after use, addressing the hook leak issue from previous reviews. The implementation correctly scopes the callback and ensures it's cleaned up.


113-127: LGTM!

The template class resolution logic is robust with proper filtering, existence checks, and fallback handling. The logging for missing template classes provides good debugging information.


129-151: LGTM!

The per-view anchor tracking implementation is well-designed:

  • Static counter maintains state across renders per view
  • Clean hook registration for anchor ID updates
  • Proper scoping of variables in closures

This enables consistent anchor generation for multiple renders of the same view.


153-175: LGTM!

The search form action filter implementation is clean and properly gated by the gravityview/widget/search/append_view_id_anchor filter. The anchor escaping with esc_attr() is appropriate for HTML attribute contexts.


177-188: LGTM!

The multisort parameter normalization correctly handles the conversion from array to single values for legacy compatibility. The logic properly checks for array types and uses reset() to get the first value, maintaining backward compatibility with existing filters.


196-218: LGTM!

The Legacy_Context setup is comprehensive and properly structured:

  • Merges view, entries, request data appropriately
  • Conditionally includes paging/sorting and post data when available
  • Uses array_merge for clean data combination
  • The view ID output hook is properly registered

This maintains compatibility with legacy widgets and templates.


220-238: Excellent exception-safe resource management!

The implementation now properly addresses the previous review concerns:

  • Output buffer is properly managed with try/catch/finally
  • ob_end_clean() handles buffer cleanup on exceptions
  • All hooks and filters are removed in the finally block
  • Legacy_Context is always popped regardless of success/failure
  • Exception is re-thrown after cleanup to preserve error propagation

This ensures deterministic resource cleanup on all exit paths.


241-244: LGTM!

The final cleanup in the outer finally block ensures the rendering stack is always maintained correctly, even if exceptions occur during the render process. This guarantees the rendering tracking state remains consistent.


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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
includes/widgets/search-widget/class-search-widget.php (1)

1405-1447: Fatal risk: foreach on $forms when there are no joined forms

When no joins exist, $forms is null/false; foreach ($forms as $form) will trigger “Invalid argument supplied for foreach()”, and $form may remain undefined. Guard and allow the main form as the only allowed target.

Apply this diff:

-        if ( count( $filter_key ) > 1 ) {
+        if ( count( $filter_key ) > 1 ) {
             // form is specified
             [ $field_id, $form_id ] = $filter_key;
-
-            if ( $forms = \GV\View::get_joined_forms( $view->ID ) ) {
-                if ( ! $form = \GV\GF_Form::by_id( $form_id ) ) {
-                    return false;
-                }
-            }
-
-            // form is allowed
-            $found = false;
-            foreach ( $forms as $form ) {
-                if ( $form->ID == $form_id ) {
-                    $found = true;
-                    break;
-                }
-            }
-
-            if ( ! $found ) {
-                return false;
-            }
+            $forms = \GV\View::get_joined_forms( $view->ID );
+            if ( $forms ) {
+                // Joined forms context: ensure the specified form exists and is allowed.
+                if ( ! $form = \GV\GF_Form::by_id( $form_id ) ) {
+                    return false;
+                }
+                $allowed_forms = $forms;
+            } else {
+                // No joins: only the main View form is allowed.
+                $form          = $view->form;
+                $allowed_forms = $form ? [ $form ] : [];
+            }
+
+            $found = false;
+            foreach ( $allowed_forms as $_form ) {
+                if ( $_form && $_form->ID == $form_id ) {
+                    $found = true;
+                    break;
+                }
+            }
+            if ( ! $found ) {
+                return false;
+            }
 
             // form is in searchable fields
             // Skip this check for shortcode-rendered Views with no searchable fields configured.
             if ( ! $is_view_rendering || ! empty( $searchable_fields ) ) {
                 $found = false;
 
-                foreach ( $searchable_fields as $field ) {
-                    if ( $field_id == $field['field'] && $form->ID == $field['form_id'] ) {
+                foreach ( $searchable_fields as $field ) {
+                    if ( $field_id == $field['field'] && $form && $form->ID == $field['form_id'] ) {
                         $found = true;
 
                         break;
                     }
                 }
 
                 if ( ! $found ) {
                     return false;
                 }
             }
         } else {
🧹 Nitpick comments (16)
translations.pot (2)

5-5: Bump Project-Id-Version to 2.46.2

Header still says “GravityView 2.46.1”. Update to match this release.

Apply this diff:

-"Project-Id-Version: GravityView 2.46.1\n"
+"Project-Id-Version: GravityView 2.46.2\n"

1125-1147: Add translator context for new “secret” messages

The new shortcode messages include placeholders like [shortcode], [secret], [page_link]. Add translator comments in source (class-gv-shortcode.php) explaining these placeholders to reduce i18n ambiguity.

future/includes/gutenberg/blocks/entry-link/block.php (1)

55-63: Avoid HTML-escaping when building shortcode attributes

Using esc_attr here can entity‑encode values (e.g., & → &) before shortcode parsing. Prefer only sanitize_text_field (or a stricter sanitizer) plus addslashes for quotes.

Apply this minimal change:

-        $value = esc_attr( sanitize_text_field( $value ) );
+        $value = sanitize_text_field( $value );

And keep the existing quote escaping.

future/includes/gutenberg/blocks/entry-link/block.json (1)

62-65: Default type mismatch for “secret”

Block attribute type is string but default is null. Gutenberg expects default to match the declared type. Use empty string or omit default.

Apply this diff:

-    "secret": {
-      "type": "string",
-      "default": null
-    }
+    "secret": {
+      "type": "string",
+      "default": ""
+    }
future/includes/class-gv-template-view-layout-builder.php (2)

14-26: Replace placeholder version with real version

Docblocks show “@SInCE $ver$”. Set to the released version for traceability.

Apply this diff:

- * @since $ver$
+ * @since 2.46.2

(and the same change for the property doc at Line 22).


28-62: Fix docblock param and deprecation target

  • Add variable name to Template_Context param.
  • Deprecation note points to “table/entry/class”; should reference “layout-builder/entry/class”.

Apply this diff:

- * @param \GV\Template_Context The context.
+ * @param \GV\Template_Context $context The context.
@@
- * @deprecated Use `gravityview/template/table/entry/class`
+ * @deprecated Use `gravityview/template/layout-builder/entry/class`
templates/views/gravityview-layout-builder.php (1)

84-86: Avoid rendering id="" when anchor is empty

If get_anchor_id() returns empty, markup will include id="". Build the wrapper markup conditionally to omit the id attribute when empty.

Apply this diff:

 $class     = gv_container_class( 'gv-template-layout-builder', false, $gravityview );
 $anchor_id = $gravityview->view->get_anchor_id();
 
 /**
  * Modify the wrapper container.
  *
  * @since  2.15
  *
  * @param string $wrapper_container Wrapper container HTML markup
  * @param string $anchor_id         (optional) Unique anchor ID to identify the view.
  * @param \GV\View $view            The View.
  */
 $wrapper_container = apply_filters(
 	'gravityview/view/wrapper_container',
-	'<div id="' . esc_attr( $anchor_id ) . '" class="' . esc_attr( $class ) . '">{content}</div>',
-	$anchor_id,
+	sprintf(
+		'<div%s class="%s">{content}</div>',
+		$anchor_id ? ' id="' . esc_attr( $anchor_id ) . '"' : '',
+		esc_attr( $class )
+	),
+	$anchor_id,
 	$gravityview->view
 );

Also applies to: 98-100

tests/unit-tests/GravityView_Widget_Search_Test.php (1)

2345-2347: Harden the “no results” assertion

“No entries match” is localized and may change; checking only that string or "no-results" risks brittle tests. Include GV’s canonical classes too.

Apply this diff:

-        $has_no_results = strpos( $output, 'No entries match' ) !== false ||
-                           strpos( $output, 'no-results' ) !== false;
+        $has_no_results =
+            strpos( $output, 'No entries match' ) !== false ||
+            strpos( $output, 'no-results' ) !== false ||
+            strpos( $output, 'gv-no-results' ) !== false ||
+            strpos( $output, 'gv-container-no-results' ) !== false;
future/includes/class-gv-shortcode.php (1)

276-279: Remove unused replacement token

[secret] isn’t present in the template here; trimming the substitution avoids confusion.

Apply this diff:

-        $message = strtr( $message_template, [
-            '[shortcode]' => $shortcode_message,
-            '[secret]'    => '<code>secret="' . esc_attr( $view->get_validation_secret() ) . '"</code>',
-        ] );
+        $message = strtr( $message_template, [
+            '[shortcode]' => $shortcode_message,
+        ] );
future/includes/class-gv-renderer-view.php (2)

185-186: Guard against null $view->form.

Accessing $view->form->ID will fatal if form is missing. Use a safe fallback.

-			$parameters = \GravityView_frontend::get_view_entries_parameters( $parameters, $view->form->ID );
+			$form_id   = ( isset( $view->form ) && isset( $view->form->ID ) ) ? $view->form->ID : 0;
+			$parameters = \GravityView_frontend::get_view_entries_parameters( $parameters, $form_id );

191-205: Avoid undefined index notices for paging/sorting in legacy context.

When $parameters lacks these keys, array offsets will raise notices.

-			\GV\Mocks\Legacy_Context::push(
-				array_merge(
-					array(
-						'view'    => $view,
-						'entries' => $entries,
-						'request' => $request,
-					),
-					empty( $parameters ) ? array() : array(
-						'paging'  => $parameters['paging'],
-						'sorting' => $parameters['sorting'],
-					),
-					empty( $post ) ? array() : array(
-						'post' => $post,
-					)
-				)
-			);
+			$legacy_params = array();
+			if ( ! empty( $parameters ) ) {
+				if ( isset( $parameters['paging'] ) )  { $legacy_params['paging']  = $parameters['paging']; }
+				if ( isset( $parameters['sorting'] ) ) { $legacy_params['sorting'] = $parameters['sorting']; }
+			}
+			\GV\Mocks\Legacy_Context::push( array_merge(
+				array(
+					'view'    => $view,
+					'entries' => $entries,
+					'request' => $request,
+				),
+				$legacy_params,
+				empty( $post ) ? array() : array( 'post' => $post )
+			) );
tests/unit-tests/GV/GV_View_Tracking_Test.php (3)

310-388: Clean up hooks and global request to avoid cross-test pollution.

  • Closures added to gravityview/template/before and gravityview/template/after aren’t removed.
  • gravityview()->request is mutated and not restored.
-		add_action( 'gravityview/template/before', function ( $context ) use ( &$tracking_checks, $view ) {
+		$before_cb = function ( $_context ) use ( &$tracking_checks, $view ) {
 			$tracking_checks['before'] = [
 				'is_rendering' => View::is_rendering( $view->ID ),
 				'current'      => View::get_current_rendering(),
 				'is_primary'   => View::is_primary_view( $view->ID ),
 				'is_embedded'  => View::is_embedded_view( $view->ID ),
 			];
-		} );
+		};
+		add_action( 'gravityview/template/before', $before_cb );
 
-		add_action( 'gravityview/template/after', function ( $context ) use ( &$tracking_checks, $view ) {
+		$after_cb = function ( $_context ) use ( &$tracking_checks, $view ) {
 			$tracking_checks['after'] = [
 				'is_rendering' => View::is_rendering( $view->ID ),
 				'current'      => View::get_current_rendering(),
 				'is_primary'   => View::is_primary_view( $view->ID ),
 				'is_embedded'  => View::is_embedded_view( $view->ID ),
 			];
-		} );
+		};
+		add_action( 'gravityview/template/after', $after_cb );
 
-		$output = $renderer->render( $view );
+		$prev_request = gravityview()->request ?? null;
+		try {
+			$output = $renderer->render( $view );
+		} finally {
+			remove_action( 'gravityview/template/before', $before_cb );
+			remove_action( 'gravityview/template/after', $after_cb );
+			if ( isset( $prev_request ) ) {
+				gravityview()->request = $prev_request;
+			}
+		}

Also renamed unused $context to $_context to satisfy static analysis while keeping the signature.


394-512: Remove tracking hook after use and silence unused parameter.

The 'before' hook persists across the suite; make it local and remove it.

-		add_action( 'gravityview/template/before', function ( $context ) use ( &$render_tracking ) {
+		$before_cb = function ( $_context ) use ( &$render_tracking ) {
 			$view_id                 = $context->view->ID;
 			$key                     = 'template_before_' . $view_id;
 			$render_tracking[ $key ] = [
 				'view_id'      => $view_id,
 				'stack'        => View::get_rendering_stack(),
 				'is_rendering' => View::is_rendering( $view_id ),
 				'is_primary'   => View::is_primary_view( $view_id ),
 				'is_embedded'  => View::is_embedded_view( $view_id ),
 				'parent'       => View::get_parent_view( $view_id ),
 				'depth'        => View::get_rendering_depth( $view_id ),
 			];
-		} );
+		};
+		add_action( 'gravityview/template/before', $before_cb );
@@
-		$output = $renderer->render( $outer_view, $request );
+		try {
+			$output = $renderer->render( $outer_view, $request );
+		} finally {
+			remove_action( 'gravityview/template/before', $before_cb );
+		}

Note: $context was unused; renamed to $_context.


517-669: Same here: make the hook self-contained and removed after render.

Prevents cross-test side effects and satisfies the unused parameter hint.

-		add_action( 'gravityview/template/before', function ( $context ) use ( &$render_tracking, &$max_depth, &$max_stack_size ) {
+		$before_cb = function ( $_context ) use ( &$render_tracking, &$max_depth, &$max_stack_size ) {
 			$view_id        = $context->view->ID;
 			$stack          = View::get_rendering_stack();
 			$max_stack_size = max( $max_stack_size, count( $stack ) );
@@
 			$render_tracking[ $view_id ] = [
 				'view_id'      => $view_id,
 				'stack'        => $stack,
 				'is_rendering' => View::is_rendering( $view_id ),
 				'is_primary'   => View::is_primary_view( $view_id ),
 				'is_embedded'  => View::is_embedded_view( $view_id ),
 				'parent'       => View::get_parent_view( $view_id ),
 				'depth'        => $depth,
 			];
-		} );
+		};
+		add_action( 'gravityview/template/before', $before_cb );
@@
-		$output   = $renderer->render( $view_a, $request );
+		try {
+			$output = $renderer->render( $view_a, $request );
+		} finally {
+			remove_action( 'gravityview/template/before', $before_cb );
+		}
readme.txt (1)

24-42: Typo in changelog (plain).

Fix “p`xlain” → “plain”.

Apply this diff:

-* Fields linked to Single Entry layouts are now exported as p`xlain text values, not hyperlinks, when using direct CSV/TSV export URLs.
+* Fields linked to Single Entry layouts are now exported as plain text values, not hyperlinks, when using direct CSV/TSV export URLs.
includes/widgets/class-gravityview-widget-export-link.php (1)

183-186: Only append REST _wpnonce for logged‑in users to avoid public‑page cache churn.

REST route already validates the view‑specific nonce (future/includes/rest/class-gv-rest-views-route.php:591–592), so gate _wpnonce on is_user_logged_in() and keep the view‑specific _nonce.

Apply this diff:

-        $rest_nonce_url = add_query_arg( [
-            '_nonce'     => $nonce, // View-specific nonce for security.
-            '_wpnonce'   => wp_create_nonce( 'wp_rest' ), // REST API authentication.
-            'use_labels' => $use_labels,
-        ], $rest_nonce_url );
+        $args = [
+            '_nonce'     => $nonce, // View-specific nonce for security.
+            'use_labels' => $use_labels,
+        ];
+        if ( is_user_logged_in() ) {
+            $args['_wpnonce'] = wp_create_nonce( 'wp_rest' ); // REST API auth for logged-in requests.
+        }
+        $rest_nonce_url = add_query_arg( $args, $rest_nonce_url );

Also apply same change at the other occurrence (line ~195).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 778f749 and 6f6e146.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • future/includes/class-gv-renderer-view.php (1 hunks)
  • future/includes/class-gv-shortcode.php (4 hunks)
  • future/includes/class-gv-template-view-layout-builder.php (1 hunks)
  • future/includes/class-gv-view.php (3 hunks)
  • future/includes/gutenberg/blocks/entry-link/block.json (1 hunks)
  • future/includes/gutenberg/blocks/entry-link/block.php (1 hunks)
  • future/includes/gutenberg/blocks/entry-link/edit.js (4 hunks)
  • future/includes/gutenberg/build/entry-link.asset.php (1 hunks)
  • gravityview.php (2 hunks)
  • includes/class-gravityview-compatibility.php (1 hunks)
  • includes/widgets/class-gravityview-widget-export-link.php (3 hunks)
  • includes/widgets/search-widget/class-search-widget.php (4 hunks)
  • readme.txt (1 hunks)
  • templates/css/layout-builder.css (1 hunks)
  • templates/css/source/layout-builder/_view_entry.scss (1 hunks)
  • templates/views/gravityview-layout-builder.php (3 hunks)
  • templates/views/list.php (2 hunks)
  • templates/views/table.php (2 hunks)
  • tests/unit-tests/GV/GV_View_Tracking_Test.php (1 hunks)
  • tests/unit-tests/GravityView_Widget_Search_Test.php (1 hunks)
  • translations.pot (17 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-07T12:49:48.625Z
Learnt from: doekenorg
PR: GravityKit/GravityView#2363
File: includes/widgets/search-widget/settings/class-search-widget-settings-visible-fields-only.php:321-327
Timestamp: 2025-07-07T12:49:48.625Z
Learning: In GravityView search widget code, when processing field conditions, the empty fields case is handled early with a `__GK_NO_MATCH__` condition before field processing begins. This means downstream code can assume at least one field exists in the fields array.

Applied to files:

  • includes/widgets/search-widget/class-search-widget.php
🧬 Code graph analysis (11)
future/includes/class-gv-template-view-layout-builder.php (1)
includes/class-template.php (1)
  • GravityView_View (26-1067)
tests/unit-tests/GV/GV_View_Tracking_Test.php (5)
future/includes/class-gv-renderer-view.php (2)
  • View_Renderer (14-231)
  • render (29-230)
future/includes/class-gv-settings-view.php (1)
  • View_Settings (14-874)
tests/GV_UnitTestCase.php (1)
  • GV_UnitTestCase (3-80)
future/includes/class-gv-view.php (10)
  • reset_rendering_stack (1088-1090)
  • is_rendering (975-981)
  • push_rendering (947-949)
  • pop_rendering (960-962)
  • get_current_rendering (992-998)
  • get_rendering_stack (1009-1011)
  • is_primary_view (1024-1026)
  • is_embedded_view (1039-1041)
  • get_parent_view (1054-1062)
  • get_rendering_depth (1075-1077)
future/includes/class-gv-shortcode-gravityview.php (1)
  • gravityview (14-525)
templates/views/list.php (2)
includes/class-api.php (1)
  • gv_container_class (929-995)
future/includes/class-gv-view.php (1)
  • get_anchor_id (2034-2043)
templates/views/table.php (2)
includes/class-api.php (1)
  • gv_container_class (929-995)
future/includes/class-gv-view.php (1)
  • get_anchor_id (2034-2043)
templates/views/gravityview-layout-builder.php (3)
future/includes/class-gv-template-view-layout-builder.php (1)
  • entry_class (42-62)
includes/class-api.php (1)
  • gv_container_class (929-995)
future/includes/class-gv-view.php (1)
  • get_anchor_id (2034-2043)
future/includes/class-gv-view.php (1)
future/includes/class-gv-field.php (1)
  • update_configuration (265-305)
includes/widgets/class-gravityview-widget-export-link.php (1)
assets/js/admin-views.js (1)
  • nonce (1906-1906)
includes/widgets/search-widget/class-search-widget.php (3)
future/includes/class-gv-view.php (1)
  • is_rendering (975-981)
future/includes/class-gv-request.php (2)
  • is_entry (128-204)
  • is_view (109-115)
future/includes/class-gv-shortcode-gravityview.php (1)
  • gravityview (14-525)
tests/unit-tests/GravityView_Widget_Search_Test.php (4)
future/includes/class-gv-view.php (3)
  • from_post (742-859)
  • by_id (885-890)
  • reset_rendering_stack (1088-1090)
future/includes/class-gv-entry-gravityforms.php (1)
  • GF_Entry (14-183)
future/includes/class-gv-shortcode-gravityview.php (1)
  • gravityview (14-525)
future/includes/class-gv-renderer-entry.php (2)
  • Entry_Renderer (14-162)
  • render (28-161)
future/includes/class-gv-renderer-view.php (1)
future/includes/class-gv-view.php (2)
  • push_rendering (947-949)
  • pop_rendering (960-962)
future/includes/class-gv-shortcode.php (4)
future/includes/gutenberg/blocks/view/block.php (1)
  • View (9-84)
future/includes/class-gv-view.php (2)
  • get_current_rendering (992-998)
  • get_validation_secret (2127-2141)
includes/class-gravityview-shortcode.php (1)
  • shortcode (40-57)
includes/helper-functions.php (1)
  • gv_current_shortcode_tag (19-36)
🪛 PHPMD (2.15.0)
tests/unit-tests/GV/GV_View_Tracking_Test.php

360-360: Avoid unused parameters such as '$context'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (27)
future/includes/class-gv-view.php (2)

97-105: LGTM on introducing the rendering stack.

Private static storage is appropriate here and aligns with per-request lifecycle.


1916-1918: CSV link stripping can be negated by lightbox; ensure all link toggles are disabled.

update_configuration() merges defaults and will re-enable links when lightbox=1 (see update_configuration in future/includes/class-gv-field.php). Force lightbox off too.

-				// Remove all links from output.
-				$field->update_configuration( [ 'show_as_link' => '0' ] );
+				// Remove all links from output (cover lightbox and link flags).
+				$field->update_configuration( [ 'show_as_link' => '0', 'lightbox' => 0 ] );

Suggestion: if feasible, disable linking at render-time via the CSV template to avoid per-field mutation and catch any field-type-specific link flags. I can draft that follow-up if desired.

Add a unit test that asserts no hyperlinks are present for:

  • an Image field with lightbox enabled,
  • a Field configured to “Link to Single Entry,”
    when exporting via /csv and /tsv.
translations.pot (1)

12-12: POT-Creation-Date looks good

Timestamp reflects the current build; no action needed.

templates/css/layout-builder.css (1)

1-1: Featured entry background fix: LGTM

The :not(.gv-featured-entry) selector prevents unintended white background on featured entries.

templates/css/source/layout-builder/_view_entry.scss (1)

4-7: Scoped background to non-featured entries: LGTM

Selector change is precise and keeps featured styling intact.

templates/views/table.php (2)

22-24: Precomputing class and anchor: LGTM

Using gv_container_class and get_anchor_id improves consistency and keeps the filter signature stable.

Please confirm any third‑party filters of gravityview/view/wrapper_container don’t rely on the absence of a class attribute.


35-39: Wrapper template: LGTM

Escaping id and class is correct; {content} replacement preserved.

templates/views/list.php (2)

21-23: Container class + anchor adoption: LGTM

Consistent with table.php; safe for downstream consumers.

If docs reference wrapper markup, consider noting the added class attribute in the next docs update.


33-38: Wrapper filter usage: LGTM

No behavioral change beyond adding class; escaping is correct.

future/includes/gutenberg/blocks/entry-link/block.php (1)

50-51: Approve — forwarding 'secret' to shortcode is correct.
Shortcode validates/uses secret (future/includes/class-gv-shortcode.php) and the admin hint exposes data-secret (includes/admin/metaboxes/views/shortcode-hint.php).

templates/views/gravityview-layout-builder.php (1)

41-45: Per-entry class hook: good addition

Static entry_class() ensures consistent filtering/extensibility across entries. The usage and escaping look correct.

Also applies to: 47-47

includes/widgets/search-widget/class-search-widget.php (1)

954-980: Rendering-aware filtering: approach looks sound

The is_rendering/Mock_Request handling correctly preserves Single Entry behavior while allowing embedded Views to filter themselves.

Please confirm nested embedded Views (View-in-View-in-Entry) still respect the same rules; if needed, I can craft a unit test mirroring this scenario.

future/includes/class-gv-shortcode.php (1)

188-194: Bypass secret when shortcode is inside the same View: good

This prevents self-embedded Views from breaking due to secret enforcement. Comparison is safe and bounded.

tests/unit-tests/GravityView_Widget_Search_Test.php (1)

2192-2363: No duplicate test_embedded_view_filtering_in_single_entry found.
Single occurrence: tests/unit-tests/GravityView_Widget_Search_Test.php:2192.

future/includes/class-gv-renderer-view.php (2)

39-41: Rendering stack management is solid.

Pushing before render and guaranteed pop in finally is correct and resilient.

Also applies to: 226-229


139-169: Confirm hook order: ensure the view anchor is set before the search form action filter runs.

Found do_action('gravityview/template/view/render') at future/includes/class-gv-template-view.php:149, apply_filters('gravityview/widget/search/form/action') at includes/widgets/search-widget/class-search-widget.php:1879, and the add_action/add_filter are registered in future/includes/class-gv-renderer-view.php:139-169. If the search filter can run before the render action, set/compute the anchor earlier or register the search-action filter only after the anchor is set (or compute a fallback anchor inside the filter).

tests/unit-tests/GV/GV_View_Tracking_Test.php (7)

20-31: Good test hygiene resetting the rendering stack.

setUp/tearDown correctly isolate tests that manipulate the global stack.


38-71: Basic push/pop/is_rendering coverage looks correct.

Clear assertions for empty, push, and pop states.


76-125: Stack/top retrieval tests are comprehensive.

Covers current element and full stack transitions well.


130-175: Primary/embedded view predicates validated well.

Happy path and negatives both covered.


180-227: Parent and depth semantics are exercised properly.

Edge cases for non-members included.


232-254: Reset behavior verified.

Good that you assert both stack emptiness and current rendering null.


259-304: Complex nested scenario test is clear and valuable.

Accurately validates intermediate states and unwind behavior.

gravityview.php (1)

6-6: Version bump is consistent; LGTM.

Header Version and GV_PLUGIN_VERSION match (2.46.2). No functional risk spotted.

Also applies to: 35-35

includes/widgets/class-gravityview-widget-export-link.php (1)

206-214: Docblock update aligns with behavior.

Comment accurately reflects the purpose after removing user context juggling. No action needed.

future/includes/gutenberg/build/entry-link.asset.php (1)

1-1: Asset version hash updated; ensure enqueue uses this value.

Confirm the enqueued script/style pull their version from this asset file so caches are busted as intended after the rebuild.

Run to double-check:

includes/class-gravityview-compatibility.php (1)

208-217: Verify Foundation runtime supports 'globally_dismissible'.

Found usages at includes/class-gravityview-compatibility.php:214 and future/includes/class-gv-shortcode.php:256; add_runtime calls at includes/class-gravityview-compatibility.php:80 and includes/class-gravityview-admin-notices.php:340. vendor_prefixed/gravitykit/foundation/src is not present in the repo, so I cannot confirm notices()->add_runtime accepts the 'globally_dismissible' key — ensure Foundation 1.4.0+ is loaded at runtime or add a feature-detect/fallback before sending the flag.

@mrcasual mrcasual merged commit 26400c6 into main Sep 19, 2025
2 checks passed
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.

4 participants