Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions includes/Ajax/Admin_Form_Builder_Ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,6 @@
wp_send_json_error( __( 'Unauthorized operation', 'wp-user-frontend' ) );
}

if ( ! current_user_can( wpuf_admin_role() ) ) {
wp_send_json_error( __( 'Unauthorized operation', 'wp-user-frontend' ) );
}

if ( isset( $post_type ) && empty( $post_data['post_type'] ) ) {
wp_send_json_error( __( 'Invalid post type', 'wp-user-frontend' ) );
}
Expand Down Expand Up @@ -146,10 +142,6 @@
wp_send_json_error( __( 'Unauthorized operation', 'wp-user-frontend' ) );
}

if ( ! current_user_can( wpuf_admin_role() ) ) {
wp_send_json_error( __( 'Unauthorized operation', 'wp-user-frontend' ) );
}

if ( isset( $post_type ) && empty( $post_data['post_type'] ) ) {
wp_send_json_error( __( 'Invalid post type', 'wp-user-frontend' ) );
}
Expand Down Expand Up @@ -186,7 +178,7 @@
$cat .= '<div class="wpuf-mt-6 wpuf-input-container taxonomy-container" data-taxonomy="' . esc_attr( $tax->name ) . '">';
$cat .= '<div class="wpuf-flex wpuf-items-center">';
$cat .= '<label for="' . esc_attr( $select_id ) . '" class="wpuf-text-sm wpuf-text-gray-700 wpuf-my-2">';
$cat .= sprintf( __( 'Default %s %s', 'wp-user-frontend' ), $post_type, $tax->label );
$cat .= sprintf( __( 'Default %s', 'wp-user-frontend' ), $tax->label );

Check failure on line 181 in includes/Ajax/Admin_Form_Builder_Ajax.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.
$cat .= '</label></div>';
Comment on lines +181 to 182
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

🧩 Analysis chain

Fix i18n placeholder and escape output (pipeline blocker).

Add translators comment and escape both the string and the taxonomy label to satisfy PHPCS and prevent HTML injection.

Apply:

-                $cat .= sprintf( __( 'Default %s', 'wp-user-frontend' ), $tax->label );
+                /* translators: %s: taxonomy label */
+                $cat .= sprintf( esc_html__( 'Default %s', 'wp-user-frontend' ), esc_html( $tax->label ) );

Run PHPCS locally to confirm:


🏁 Script executed:

#!/bin/bash
vendor/bin/phpcs includes/Ajax/Admin_Form_Builder_Ajax.php -q

Length of output: 62


I'll examine the actual code context to verify the i18n and escaping issues.



🏁 Script executed:

#!/bin/bash
# Inspect the problematic lines and surrounding context
sed -n '170,195p' includes/Ajax/Admin_Form_Builder_Ajax.php

Length of output: 1470


🏁 Script executed:

#!/bin/bash
# Check for translators comments in the file
rg -n "translators:" includes/Ajax/Admin_Form_Builder_Ajax.php -B2 -A2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for other sprintf + __() patterns in file to see if translators comments exist elsewhere
rg -n "sprintf.*__\(" includes/Ajax/Admin_Form_Builder_Ajax.php -B1 -A1

Length of output: 263


🏁 Script executed:

#!/bin/bash
# Check WordPress escaping patterns for taxonomy properties
rg -n "tax->label" includes/Ajax/Admin_Form_Builder_Ajax.php -B3 -A3

Length of output: 520



---

🌐 Web query:

```web
WordPress i18n translators comment requirement sprintf __ PHPCS

💡 Result:

Short answer: add a translators comment (/* translators: ... */) directly above any translation string that contains placeholders (e.g. %s, %d, %1$s), and prefer using printf()/sprintf() with (numbered) placeholders so translators can reorder them. WordPress Coding Standards (WPCS) include an i18n sniff that flags missing/misplaced translator comments.

What to do

  • Use placeholders and printf/sprintf instead of interpolated variables. Example:
    /* translators: %s: Name of a city */
    printf( __( 'Your city is %s.', 'my-plugin' ), $city );
  • Put the comment immediately above the translation call, start it with "translators:" and describe each placeholder (use numbering when multiple placeholders).
  • Use numbered placeholders (%1$s, %2$s) when the argument order may change in other languages.

Why

  • The WordPress i18n docs and Plugin Handbook require translator comments for strings with placeholders to give translators the necessary context. [1][2]
  • Core ran a large effort to add missing translator comments and the WPCS i18n sniff now checks for missing/misplaced comments. [3][4]

Sources

  1. WordPress Internationalization Guidelines (placeholders & translators comments). [1]
  2. Plugin Handbook — translators comments and placement. [2]
  3. Trac ticket/history about adding missing translators comments (44360) and core changes. [3]
  4. WordPress Coding Standards (WPCS) changelog — added WordPress.WP.I18n sniff to detect missing translator comments. [4]

Would you like a PHPCS ruleset snippet or example to fail/auto-fix this in CI?


🌐 Web query:

WordPress esc_html__ vs __ escaping functions best practice

💡 Result:

Short answer

  • Use esc_html__ (or esc_attr__, esc_html_e, esc_attr_e, etc.) when you need a translated string that is already escaped for output. [1][2]
  • __ only returns the translation without escaping — wrap it with the correct escaping function or use the esc_…__ variant. [1][4]

Best-practice rules

  1. Escape late: escape when outputting, not when storing. Use the escaping function that fits the context (HTML text vs HTML attribute vs JS vs URL). [1]
  2. Prefer the combined i18n+escape helpers (esc_html__, esc_attr__, esc_html_e, esc_attr_e) to avoid forgetting to escape. [2][4]
  3. Choose the correct escape:
    • esc_html__ / esc_html(): text inside HTML elements (strips any HTML). [1]
    • esc_attr__ / esc_attr(): values used inside attributes (title, value, class). [1][4]
    • esc_js(): inline JavaScript. [1]
    • esc_url(): URLs (or esc_url_raw for DB/storage cases). [1]
  4. If HTML is expected and allowed, do NOT use esc_html — use wp_kses_post() or wp_kses() with a whitelist instead. [1][5]
  5. Audit for uses of __, _e, _x that are echoed without escaping and replace with escaped variants (Plugin Handbook guidance). [6]

Examples

  • Safe HTML output: echo esc_html__( 'Settings', 'my-domain' ); [2]
  • Attribute: printf( '', esc_attr__( 'Default', 'my-domain' ) ); [1]
  • If HTML allowed: echo wp_kses_post( __( 'Some HTML', 'my-domain' ) ); [1][5]

Sources
[1] WordPress — Escaping data (Developer Handbook).
[2] Function reference: esc_html__ (WordPress Code Reference).
[3] Internationalization guidelines — escaping strings (WordPress Handbook).
[4] esc_attr() reference (WordPress Code Reference).
[5] wp_kses / wp_kses_post guidance (WordPress Handbook).
[6] Plugin Handbook — common issues: using __ without escaping.


Fix i18n placeholder and escape output (PHPCS blocker and XSS risk).

Line 181 outputs $tax->label unescaped inside an HTML label tag and is missing the required translators comment. Add the translators comment and escape both the string and label using esc_html__() and esc_html() to satisfy PHPCS i18n standards and prevent HTML injection.

-                $cat .= sprintf( __( 'Default %s', 'wp-user-frontend' ), $tax->label );
+                /* translators: %s: taxonomy label */
+                $cat .= sprintf( esc_html__( 'Default %s', 'wp-user-frontend' ), esc_html( $tax->label ) );
🧰 Tools
🪛 GitHub Check: Run PHPCS inspection

[failure] 181-181:
A function call to __() with texts containing placeholders was found, but was not accompanied by a "translators:" comment on the line above to clarify the meaning of the placeholders.

🤖 Prompt for AI Agents
In includes/Ajax/Admin_Form_Builder_Ajax.php around lines 181-182, the code
prints $tax->label directly into the label text and lacks a translators comment;
update the translation call to use esc_html__() (with an inline translators
comment for context) and escape the dynamic label with esc_html(), replacing the
current sprintf/concatenation so the translatable string is escaped and the
label is safely escaped to prevent XSS.


$cat .= '<select
Expand All @@ -201,7 +193,7 @@

if ( ! is_wp_error( $categories ) && ! empty( $categories ) ) {
foreach ( $categories as $category ) {
$selected = in_array( $category->term_id, (array) $current_value ) ? 'selected="selected"' : '';

Check failure on line 196 in includes/Ajax/Admin_Form_Builder_Ajax.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Not using strict comparison for in_array; supply true for $strict argument.
$cat .= '<option value="' . esc_attr( $category->term_id ) . '" ' . $selected . '>' . esc_html( $category->name ) . '</option>';
}
}
Expand Down
44 changes: 44 additions & 0 deletions includes/Traits/FieldableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
$taxonomy_templates['post_tag'] = new Form_Field_Post_Tags();
} else {
$taxonomy_templates[ $tax_name ] = new Form_Field_Post_Taxonomy( $tax_name, $taxonomy );
// $taxonomy_templates[ 'taxonomy' ] = new WPUF_Form_Field_Post_Taxonomy($tax_name, $taxonomy);

Check warning on line 50 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

This comment is 64% valid code; is this commented out code?
}
}
}
Expand All @@ -69,25 +69,35 @@
public function set_wp_post_types() {
$args = [ '_builtin' => true ];
$wpuf_post_types = wpuf_get_post_types( $args );

Check failure on line 72 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Whitespace found at end of line
// Add tribe_events if The Events Calendar post type is registered
if ( post_type_exists( 'tribe_events' ) && ! in_array( 'tribe_events', $wpuf_post_types, true ) ) {
$wpuf_post_types[] = 'tribe_events';
}

Check failure on line 77 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Whitespace found at end of line
// Add product if WooCommerce post type is registered
if ( post_type_exists( 'product' ) && ! in_array( 'product', $wpuf_post_types, true ) ) {
$wpuf_post_types[] = 'product';
}

Check failure on line 82 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Whitespace found at end of line
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

🧩 Analysis chain

Trailing whitespace trips PHPCS.

Remove trailing spaces on these lines to appease CI.

Also applies to: 87-87


🏁 Script executed:

#!/bin/bash
vendor/bin/phpcbf includes/Traits/FieldableTrait.php && vendor/bin/phpcs includes/Traits/FieldableTrait.php -q

Length of output: 63


I'll directly inspect the file to verify the trailing whitespace issue since the PHP tools aren't available in the sandbox.


---

🏁 Script executed:

```shell
#!/bin/bash
# Display the file with line numbers and make trailing whitespace visible
# Using cat with -A to show whitespace characters
sed -n '80,90p' includes/Traits/FieldableTrait.php | cat -A

Length of output: 423


Remove trailing whitespace on lines 82 and 87.

Lines 82 and 87 contain 8 spaces of indentation with no other content, creating trailing whitespace that violates PHPCS standards. These should be completely blank lines with no characters.

🧰 Tools
🪛 GitHub Check: Run PHPCS inspection

[failure] 82-82:
Whitespace found at end of line

🤖 Prompt for AI Agents
In includes/Traits/FieldableTrait.php around lines 82 and 87, remove the
trailing whitespace on those lines so they are completely empty (no spaces or
tabs). Open the file, delete the 8-space indentation characters on lines 82 and
87, save, and run PHPCS to verify no trailing whitespace remains.

// Add download if Easy Digital Downloads post type is registered
if ( post_type_exists( 'download' ) && ! in_array( 'download', $wpuf_post_types, true ) ) {
$wpuf_post_types[] = 'download';
}

Check failure on line 87 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Whitespace found at end of line
$ignore_taxonomies = apply_filters( 'wpuf-ignore-taxonomies', [

Check warning on line 88 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Words in hook names should be separated using underscores. Expected: 'wpuf_ignore_taxonomies', but found: 'wpuf-ignore-taxonomies'.

Check failure on line 88 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Opening parenthesis of a multi-line function call must be the last content on the line
'post_format',
] );

Check failure on line 90 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Closing parenthesis of a multi-line function call must be on a line by itself
foreach ( $wpuf_post_types as $post_type ) {
$this->wp_post_types[ $post_type ] = [];
$taxonomies = get_object_taxonomies( $post_type, 'object' );
foreach ( $taxonomies as $tax_name => $taxonomy ) {
if ( ! in_array( $tax_name, $ignore_taxonomies ) ) {

Check failure on line 95 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Not using strict comparison for in_array; supply true for $strict argument.
$this->wp_post_types[ $post_type ][ $tax_name ] = [
'title' => $taxonomy->label,
'hierarchical' => $taxonomy->hierarchical,
];
$this->wp_post_types[ $post_type ][ $tax_name ]['terms'] = get_terms( [

Check failure on line 100 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Opening parenthesis of a multi-line function call must be the last content on the line
'taxonomy' => $tax_name,
'hide_empty' => false,
] );
Expand All @@ -103,6 +113,26 @@
'terms' => [],
];
}

// Special handling for product to include product_tag in free version
if ( 'product' === $post_type && ! isset( $this->wp_post_types[ $post_type ]['product_tag'] ) ) {
// Add product_tag as a canonical field for WooCommerce forms
$this->wp_post_types[ $post_type ]['product_tag'] = [
'title' => __( 'Product Tags', 'wp-user-frontend' ),
'hierarchical' => false,
'terms' => [],
];
}

// Special handling for download to include download_tag in free version
if ( 'download' === $post_type && ! isset( $this->wp_post_types[ $post_type ]['download_tag'] ) ) {
// Add download_tag as a canonical field for EDD forms
$this->wp_post_types[ $post_type ]['download_tag'] = [
'title' => __( 'Download Tags', 'wp-user-frontend' ),
'hierarchical' => false,
'terms' => [],
];
}
}
}

Expand Down Expand Up @@ -132,7 +162,7 @@

foreach ( $form_vars as $key => $value ) {
// get column field input fields
if ( $value['input_type'] == 'column_field' ) {

Check warning on line 165 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="
$inner_fields = $value['inner_fields'];

foreach ( $inner_fields as $column_key => $column_fields ) {
Expand All @@ -144,12 +174,12 @@
}

//separate the post and custom fields
if ( isset( $column_field['is_meta'] ) && $column_field['is_meta'] == 'yes' ) {

Check warning on line 177 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="
$meta_vars[] = $column_field;
continue;
}

if ( $column_field['input_type'] == 'taxonomy' ) {

Check warning on line 182 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="

// don't add "category"
// if ( $column_field['name'] == 'category' ) {
Expand All @@ -172,12 +202,12 @@
}

//separate the post and custom fields
if ( isset( $value['is_meta'] ) && $value['is_meta'] == 'yes' ) {

Check warning on line 205 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="
$meta_vars[] = $value;
continue;
}

if ( $value['input_type'] == 'taxonomy' ) {

Check warning on line 210 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="

// don't add "category"
// if ( $value['name'] == 'category' ) {
Expand Down Expand Up @@ -221,10 +251,10 @@
wpuf()->ajax->send_error( __( 'Empty reCaptcha Field', 'wp-user-frontend' ) );
}

if ( $recaptcha_type == 'enable_no_captcha' ) {

Check warning on line 254 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="
$no_captcha = 1;
$invisible_captcha = 0;
} elseif ( $recaptcha_type == 'invisible_recaptcha' ) {

Check warning on line 257 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="
$invisible_captcha = 1;
$no_captcha = 0;
} else {
Expand Down Expand Up @@ -278,7 +308,7 @@
$remote_addr = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
$g_recaptcha_response = isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : '';

if ( $no_captcha == 1 && 0 == $invisible ) {

Check warning on line 311 in includes/Traits/FieldableTrait.php

View workflow job for this annotation

GitHub Actions / Run PHPCS inspection

Loose comparisons are not allowed. Expected: "==="; Found: "=="
if ( ! class_exists( 'WPUF_ReCaptcha' ) ) {
require_once WPUF_ROOT . '/Lib/recaptchalib_noCaptcha.php';
}
Expand Down Expand Up @@ -498,6 +528,20 @@
register_taxonomy_for_object_type( 'post_tag', 'tribe_events' );
}

// Auto-register product_tag taxonomy for product if not already registered
if ( 'product' === $this->form_settings['post_type'] &&
'product_tag' === $taxonomy['name'] &&
! is_object_in_taxonomy( $this->form_settings['post_type'], $taxonomy['name'] ) ) {
register_taxonomy_for_object_type( 'product_tag', 'product' );
}

// Auto-register download_tag taxonomy for download if not already registered
if ( 'download' === $this->form_settings['post_type'] &&
'download_tag' === $taxonomy['name'] &&
! is_object_in_taxonomy( $this->form_settings['post_type'], $taxonomy['name'] ) ) {
register_taxonomy_for_object_type( 'download_tag', 'download' );
}

if ( is_object_in_taxonomy( $this->form_settings['post_type'], $taxonomy['name'] ) ) {
$tax = $taxonomy_name;
// if it's not an array, make it one
Expand Down
Loading