Skip to content

gwpc-post-image-subfields.php: Added snippet to enable word count for Post Image subfields. #1148

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

saifsultanc
Copy link
Contributor

Context

⛑️ Ticket(s): https://secure.helpscout.net/conversation/3033697344/87708

Summary

Use GP Word Count to count the words entered in the subfields (caption, description, alt text, title) of a Post Image field.

Loom:
https://www.loom.com/share/005eda834a6348f9a874a5e6cdd85461

Copy link

coderabbitai bot commented Aug 20, 2025

Walkthrough

Adds a new PHP module implementing per-subfield word-count enforcement for Gravity Forms Post Image fields. It registers a singleton class that enqueues a word-count script on relevant forms, initializes textarea counters for configured subfields, and validates min/max word counts server-side during submission.

Changes

Cohort / File(s) Summary
New module: Post Image subfield word count
gp-word-count/gwpc-post-image-subfields.php
Introduces GW_Post_Image_Word_Count singleton with hooks to enqueue scripts, initialize textarea counters for caption/description/alt/title subfields, and validate word counts server-side; includes configuration API and subfield mapping helpers.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Browser
  participant WP as WordPress
  participant GF as Gravity Forms
  participant GW as GW_Post_Image_Word_Count

  Note over WP,GW: Page render
  WP->>GW: init()
  GW->>GF: hook gform_enqueue_scripts / gform_register_init_scripts
  GF->>GW: enqueue_scripts(form)
  alt Form has configured post_image fields
    GW->>Browser: enqueue GP Word Count JS
    GF->>GW: enqueue_textarea_counter(form)
    GW->>Browser: register textareaCounter init (per subfield with min/max)
  else No relevant fields
    Note over GW: No-op
  end
Loading
sequenceDiagram
  autonumber
  participant User
  participant Browser
  participant GF as Gravity Forms
  participant GW as GW_Post_Image_Word_Count

  Note over Browser: Client-side typing
  Browser->>Browser: textareaCounter enforces/indicates counts

  Note over GF,GW: Submission
  User->>GF: Submit form
  GF->>GW: validate_word_count(result)
  loop For each configured post_image subfield
    GW->>GW: count words, compare min/max
    alt Outside limits
      GW-->>GF: set field validation failed + message
    else Within limits
      GW-->>GF: validation ok
    end
  end
  GF-->>User: Success or error per subfield
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • veryspry

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch saif/add/87708-add-post-image-subfields

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

Warnings
⚠️ When ready, don't forget to request reviews on this pull request from your fellow wizards.

Generated by 🚫 dangerJS against f27145d

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

🧹 Nitpick comments (7)
gp-word-count/gwpc-post-image-subfields.php (7)

27-37: Make the class truly singleton and avoid duplicate hook registrations across multiple configurations

Creating multiple instances with new GW_Post_Image_Word_Count( ... ) registers the hooks multiple times, which can enqueue/validate repeatedly. Promote the first instance to the singleton and only register hooks once; subsequent instantiations should only append config.

This also fixes the PHPCS error at Line 32 (missing trailing comma).

Apply this diff:

 public function __construct( $config = array() ) {
-
-		$this->_configs[] = array_merge( array(
-			'form_id'  => false,
-			'field_id' => false,
-			'limits'   => array()
-		), $config );
-
-		add_action( 'init', array( $this, 'init' ) );
-
-	}
+		// Promote first instance to singleton and register hooks once.
+		if ( self::$_instance instanceof self ) {
+			self::$_instance->_configs[] = array_merge( array(
+				'form_id'  => false,
+				'field_id' => false,
+				'limits'   => array(),
+			), $config );
+			return;
+		}
+
+		self::$_instance = $this;
+
+		$this->_configs[] = array_merge( array(
+			'form_id'  => false,
+			'field_id' => false,
+			'limits'   => array(),
+		), $config );
+
+		add_action( 'init', array( $this, 'init' ) );
+
+	}

57-63: Reformat subfield map to meet WPCS for multi-line associative arrays

PHPCS flagged Lines 58–61: “When a multi-item array uses associative keys, each value should start on a new line.”

Apply this diff:

 	public function get_subfields() {
-		return array(
-			'caption'     => array( 'suffix' => '4', 'label' => __( 'Caption', 'gravityforms' ) ),
-			'description' => array( 'suffix' => '7', 'label' => __( 'Description', 'gravityforms' ) ),
-			'alt'         => array( 'suffix' => '2', 'label' => __( 'Alt Text', 'gravityforms' ) ),
-			'title'       => array( 'suffix' => '1', 'label' => __( 'Title', 'gravityforms' ) ),
-		);
+		return array(
+			'caption'     => array(
+				'suffix' => '4',
+				'label'  => __( 'Caption', 'gravityforms' ),
+			),
+			'description' => array(
+				'suffix' => '7',
+				'label'  => __( 'Description', 'gravityforms' ),
+			),
+			'alt'         => array(
+				'suffix' => '2',
+				'label'  => __( 'Alt Text', 'gravityforms' ),
+			),
+			'title'       => array(
+				'suffix' => '1',
+				'label'  => __( 'Title', 'gravityforms' ),
+			),
+		);
 	}

105-121: Add translators comments for placeholders and remove an unnecessary sprintf

PHPCS flagged many lines here for missing “translators:” comments. Also, Line 115 calls sprintf() without a placeholder in the string.

Apply this diff:

 			$args = array(
 				'formId'                 => $form_id,
 				'limit'                   => $max,
 				'min'                     => $min,
 				'truncate'                => true,
-				'defaultLabel'            => sprintf( __( 'Max: %s words', 'gp-word-count' ), '{limit}' ),
-				'defaultLabelSingular'    => sprintf( __( 'Max: %s word', 'gp-word-count' ), '{limit}' ),
-				'counterLabel'            => sprintf( __( '%s words left', 'gp-word-count' ), '{remaining}' ),
-				'counterLabelSingular'    => sprintf( __( '%s word left', 'gp-word-count' ), '{remaining}' ),
-				'limitReachedLabel'       => '<span class="gwwc-max-reached" style="font-weight:bold;">' . sprintf( __( '%s words left', 'gp-word-count' ), '{remaining}' ) . '</span>',
-				'limitExceededLabel'      => '<span class="gwwc-max-exceeded" style="font-weight:bold;color:#c0392b;">' . sprintf( __( 'Limit exceeded!', 'gp-word-count' ), '{remaining}' ) . '</span>',
-				'minCounterLabel'         => sprintf( __( '%s more words required', 'gp-word-count' ), '{remaining}' ),
-				'minCounterLabelSingular' => sprintf( __( '%s more word required', 'gp-word-count' ), '{remaining}' ),
+				/* translators: %s: maximum number of words allowed */
+				'defaultLabel'            => sprintf( __( 'Max: %s words', 'gp-word-count' ), '{limit}' ),
+				/* translators: %s: maximum number of words allowed */
+				'defaultLabelSingular'    => sprintf( __( 'Max: %s word', 'gp-word-count' ), '{limit}' ),
+				/* translators: %s: remaining words allowed before reaching the limit */
+				'counterLabel'            => sprintf( __( '%s words left', 'gp-word-count' ), '{remaining}' ),
+				/* translators: %s: remaining words allowed before reaching the limit */
+				'counterLabelSingular'    => sprintf( __( '%s word left', 'gp-word-count' ), '{remaining}' ),
+				/* translators: %s: remaining words allowed before reaching the limit */
+				'limitReachedLabel'       => '<span class="gwwc-max-reached" style="font-weight:bold;">' . sprintf( __( '%s words left', 'gp-word-count' ), '{remaining}' ) . '</span>',
+				'limitExceededLabel'      => '<span class="gwwc-max-exceeded" style="font-weight:bold;color:#c0392b;">' . __( 'Limit exceeded!', 'gp-word-count' ) . '</span>',
+				/* translators: %s: remaining words needed to meet the minimum */
+				'minCounterLabel'         => sprintf( __( '%s more words required', 'gp-word-count' ), '{remaining}' ),
+				/* translators: %s: remaining words needed to meet the minimum */
+				'minCounterLabelSingular' => sprintf( __( '%s more word required', 'gp-word-count' ), '{remaining}' ),
 				'minReachedLabel'         => '<span class="gwwc-min-reached" style="font-weight:bold;color:#27ae60">' . __( 'Minimum word count met.', 'gp-word-count' ) . '</span>',
-				'minDefaultLabel'         => sprintf( __( 'Min: %s words', 'gp-word-count' ), '{min}' ),
-				'minDefaultLabelSingular' => sprintf( __( 'Min: %s word', 'gp-word-count' ), '{min}' ),
+				/* translators: %s: minimum number of words required */
+				'minDefaultLabel'         => sprintf( __( 'Min: %s words', 'gp-word-count' ), '{min}' ),
+				/* translators: %s: minimum number of words required */
+				'minDefaultLabelSingular' => sprintf( __( 'Min: %s word', 'gp-word-count' ), '{min}' ),
 			);

123-126: Use wp_json_encode for safe JSON in inline JS

wp_json_encode() sets sane defaults and handles encoding more safely for inline scripts. Minor, but recommended.

Apply this diff:

-				$args_json = json_encode( $args );
+				$args_json = wp_json_encode( $args );
 				$input_id  = "input_{$form_id}_{$field_id}_{$suffix}";
 				$script    = "jQuery('#{$input_id}').textareaCounter({$args_json});";

162-176: Order placeholders in translatable strings to satisfy WPCS and translators

PHPCS flagged Lines 165 and 174: multiple placeholders should be ordered. Use numbered placeholders and add translators comments.

Apply this diff:

-					if ( $min && $word_count < $min ) {
+					if ( $min && $word_count < $min ) {
 						$field->failed_validation = true;
-						$field->validation_message = sprintf(
-							_n( '%s must be at least %s word.', '%s must be at least %s words.', $min, 'gp-word-count' ),
-							$label, $min
-						);
+						/* translators: 1: subfield label, 2: minimum word count */
+						$field->validation_message = sprintf(
+							_n( '%1$s must be at least %2$s word.', '%1$s must be at least %2$s words.', $min, 'gp-word-count' ),
+							$label,
+							$min
+						);
 						$result['is_valid'] = false;
 					}
 
-					if ( $max && $word_count > $max ) {
+					if ( $max && $word_count > $max ) {
 						$field->failed_validation = true;
-						$field->validation_message = sprintf(
-							_n( '%s may only be %s word.', '%s may only be %s words.', $max, 'gp-word-count' ),
-							$label, $max
-						);
+						/* translators: 1: subfield label, 2: maximum word count */
+						$field->validation_message = sprintf(
+							_n( '%1$s may only be %2$s word.', '%1$s may only be %2$s words.', $max, 'gp-word-count' ),
+							$label,
+							$max
+						);
 						$result['is_valid'] = false;
 					}

17-18: Align assignments or disable the sniff locally

PHPCS warning (Line 18): “Equals sign not aligned with surrounding assignments.” If you prefer not aligning, you can either align these two properties or add a local phpcs:ignore for Generic.Formatting.MultipleStatementAlignment to keep your preferred style.

Would you like me to add a scoped // phpcs:ignore?


194-205: Fix tab alignment in configuration and keep style consistent

PHPCS flagged Line 199 for using a tab for mid-line alignment. Replace with spaces. Also normalize commented examples for consistency.

Apply this diff:

 	'limits'   => array(
-		'caption'	 => array( 'min' => 3, 'max' => 10 ),
+		'caption' => array( 'min' => 3, 'max' => 10 ),
 		'description' => array( 'min' => 2, 'max' => 20 ),
-		// 'alt'	  => array( 'min' => 1, 'max' => 5 ),
-		// 'title'	=> array( 'min' => 1, 'max' => 5 ),
+		// 'alt'   => array( 'min' => 1, 'max' => 5 ),
+		// 'title' => array( 'min' => 1, 'max' => 5 ),
 	),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e320628 and f27145d.

📒 Files selected for processing (1)
  • gp-word-count/gwpc-post-image-subfields.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHPCS (Files Changed)
gp-word-count/gwpc-post-image-subfields.php

[failure] 199-199:
Spaces must be used for mid-line alignment; tabs are not allowed


[failure] 174-174:
Multiple placeholders should be ordered. Expected '%1$s, %2$s', but got %s, %s.


[failure] 174-174:
Multiple placeholders should be ordered. Expected '%1$s, %2$s', but got %s, %s.


[failure] 165-165:
Multiple placeholders should be ordered. Expected '%1$s, %2$s', but got %s, %s.


[failure] 165-165:
Multiple placeholders should be ordered. Expected '%1$s, %2$s', but got %s, %s.


[warning] 119-119:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 117-117:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 116-116:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 114-114:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 113-113:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 112-112:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 111-111:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 110-110:
A gettext call containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.


[warning] 106-106:
Array double arrow not aligned correctly; expected 18 space(s) between "'formId'" and double arrow, but found 17.


[failure] 61-61:
When a multi-item array uses associative keys, each value should start on a new line.


[failure] 60-60:
When a multi-item array uses associative keys, each value should start on a new line.


[failure] 59-59:
When a multi-item array uses associative keys, each value should start on a new line.


[failure] 58-58:
When a multi-item array uses associative keys, each value should start on a new line.


[failure] 32-32:
Each array item in a multi-line array declaration must end in a comma


[warning] 18-18:
Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space

🪛 GitHub Actions: PHP Lint (PR)
gp-word-count/gwpc-post-image-subfields.php

[warning] 18-18: PHPCS (thenabeel/action-phpcs@v8): Equals sign not aligned with surrounding assignments; expected 9 spaces but found 1 space. (Generic.Formatting.MultipleStatementAlignment.NotSameWarning)


[error] 32-32: PHPCS (thenabeel/action-phpcs@v8): Each array item in a multi-line array declaration must end in a comma. (WordPress.Arrays.CommaAfterArrayItem.NoComma)

🔇 Additional comments (4)
gp-word-count/gwpc-post-image-subfields.php (4)

65-78: Confirm the script handle and plugin basename for GP Word Count

Line 72 enqueues the script by building a URL with plugins_url( 'scripts/jquery.textareaCounter.js', 'gwwordcount/gwwordcount.php' ). Please verify:

  • The handle you want to use is indeed gp-word-count (matches GP Word Count).
  • The plugin basename 'gwwordcount/gwwordcount.php' is correct for GP Word Count in your environment. If the slug is gp-word-count/gp-word-count.php, this URL will 404.

Optionally, prefer enqueuing the registered handle when available:

if ( wp_script_is( 'gp-word-count', 'registered' ) ) {
	wp_enqueue_script( 'gp-word-count' );
} else {
	wp_enqueue_script( 'gp-word-count', plugins_url( 'scripts/jquery.textareaCounter.js', 'gp-word-count/gp-word-count.php' ), array( 'jquery' ), null, true );
}

80-131: Verify textareaCounter works on non-textareas (Title/Alt are text inputs)

Caption and Description may be textareas, but Title and Alt are often inputs. The GP Word Count jquery.textareaCounter generally operates on inputs as well, but please confirm it behaves correctly for input[type="text"] on your form, especially with truncate => true.


58-62: Double-check subfield suffixes against your GF version

The suffixes used here (Title 1, Alt 2, Caption 4, Description 7) match common Post Image input IDs. Gravity Forms has tweaked internal indexing across versions in the past.

Please confirm these match the rendered input IDs in your environment (inspect the DOM for the Post Image field). If they differ, consider deriving suffixes dynamically from $field->inputs by label to make this robust across versions.


1-14: Solid addition; scope and intent are clear

The snippet’s purpose, hooks used, and server-side validation approach look good. Nice job mapping per-subfield limits and keeping the UX consistent with GP Word Count labels.

Comment on lines +153 to +158
$suffix = $subfields[ $subfield ]['suffix'];
$label = $subfields[ $subfield ]['label'];
$input_name = "input_{$field->id}_{$suffix}";
$value = rgpost( $input_name );
$word_count = preg_match_all( '/\S+/', trim( $value ) );

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid PHP 8.1+ deprecation/warnings: ensure trim() gets a string

If the subfield isn’t present, rgpost() may return null. trim(null) is deprecated. Cast before trimming and handle the empty case.

Apply this diff:

-					$input_name = "input_{$field->id}_{$suffix}";
-					$value      = rgpost( $input_name );
-					$word_count = preg_match_all( '/\S+/', trim( $value ) );
+					$input_name = "input_{$field->id}_{$suffix}";
+					$value      = trim( (string) rgpost( $input_name ) );
+					$word_count = $value === '' ? 0 : preg_match_all( '/\S+/', $value );
📝 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
$suffix = $subfields[ $subfield ]['suffix'];
$label = $subfields[ $subfield ]['label'];
$input_name = "input_{$field->id}_{$suffix}";
$value = rgpost( $input_name );
$word_count = preg_match_all( '/\S+/', trim( $value ) );
$suffix = $subfields[ $subfield ]['suffix'];
$label = $subfields[ $subfield ]['label'];
$input_name = "input_{$field->id}_{$suffix}";
$value = trim( (string) rgpost( $input_name ) );
$word_count = $value === '' ? 0 : preg_match_all( '/\S+/', $value );
🤖 Prompt for AI Agents
In gp-word-count/gwpc-post-image-subfields.php around lines 153 to 158, rgpost()
can return null so calling trim() directly can trigger PHP 8.1+ deprecation
warnings; ensure you coerce the value to a string or default to an empty string
before trimming and then only call preg_match_all if the trimmed string is
non-empty (otherwise set word_count to 0). Replace the direct trim($value) usage
with a safe string cast or null coalescing to '' and handle the empty case to
avoid warnings and produce a correct 0 word count.

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

Successfully merging this pull request may close these issues.

1 participant