Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/jetformbuilder/includes/generators/get-from-db.php
+++ b/jetformbuilder/includes/generators/get-from-db.php
@@ -74,8 +74,8 @@
return array(
array(
'single' => true,
- 'description' => __( 'The Trigger Field value is used as the Meta Key and overrides the static setting above. If the Trigger Field is empty, the static Meta Key is used.', 'jet-form-builder' ),
- 'example' => __( 'Choose the field whose value will be used as the dynamic Meta Key.', 'jet-form-builder' ),
+ 'description' => __( 'The Trigger Field value can switch the Meta Key when it matches a saved Trigger Field choice or an allowed server-side key. Otherwise the static Meta Key is used.', 'jet-form-builder' ),
+ 'example' => __( 'Choose the field whose saved option values should be allowed as dynamic Meta Keys.', 'jet-form-builder' ),
),
);
}
@@ -100,7 +100,8 @@
/**
* Generate options with context from dependent fields.
- * Uses the watched field value as meta key.
+ * Context may override the meta key only when the candidate value is
+ * explicitly allowed by saved field choices or server-side filters.
*
* @param array $settings Parsed settings.
* @param array $context ['field_name' => 'value'] from listened fields.
@@ -112,10 +113,40 @@
$context_value = reset( $context );
$meta_key = is_scalar( $context_value ) ? trim( sanitize_text_field( (string) $context_value ) ) : '';
- // Use watched field value as override only when it is non-empty.
- // Otherwise keep static meta_key from generator settings.
if ( '' !== $meta_key ) {
- $settings['meta_key'] = $meta_key;
+ $runtime = $settings['_jfb_runtime'] ?? array();
+ $block_attrs = $settings['_jfb_block_attrs'] ?? array();
+ $validate_dynamic = apply_filters(
+ 'jet-form-builder/generators/get-from-db/validate_dynamic_meta_key',
+ true,
+ $meta_key,
+ $settings,
+ $context,
+ $runtime,
+ $block_attrs
+ );
+
+ if ( ! $validate_dynamic ) {
+ $settings['meta_key'] = $meta_key;
+
+ return $this->generate( $settings );
+ }
+
+ $allowed_meta_keys = $runtime['allowed_meta_keys'] ?? array();
+ $allowed_meta_keys = apply_filters(
+ 'jet-form-builder/generators/get-from-db/allowed-meta-keys',
+ $allowed_meta_keys,
+ $meta_key,
+ $settings,
+ $context,
+ $runtime,
+ $block_attrs
+ );
+ $allowed_meta_keys = $this->sanitize_meta_keys_allowlist( $allowed_meta_keys );
+
+ if ( in_array( $meta_key, $allowed_meta_keys, true ) ) {
+ $settings['meta_key'] = $meta_key;
+ }
}
}
@@ -123,6 +154,32 @@
}
/**
+ * Sanitize an allowlist of meta keys received from runtime or filters.
+ *
+ * @param mixed $allowed_meta_keys Meta keys allowlist.
+ *
+ * @return array
+ */
+ private function sanitize_meta_keys_allowlist( $allowed_meta_keys ): array {
+ if ( ! is_array( $allowed_meta_keys ) ) {
+ return array();
+ }
+
+ $allowed_meta_keys = array_filter(
+ array_map(
+ static function ( $meta_key ) {
+ return is_scalar( $meta_key )
+ ? trim( sanitize_text_field( (string) $meta_key ) )
+ : '';
+ },
+ $allowed_meta_keys
+ )
+ );
+
+ return array_values( array_unique( $allowed_meta_keys ) );
+ }
+
+ /**
* Returns generated options list.
*
* @param array|string $args Settings array or legacy string.
--- a/jetformbuilder/includes/generators/get-from-users.php
+++ b/jetformbuilder/includes/generators/get-from-users.php
@@ -127,8 +127,8 @@
return array(
array(
'single' => true,
- 'description' => __( 'The Trigger Field value overrides the static "Filter by Roles" setting above. If the Trigger Field is empty, the static roles are used.', 'jet-form-builder' ),
- 'example' => __( 'Select the field that returns one or more user role slugs, for example: administrator, editor, subscriber.', 'jet-form-builder' ),
+ 'description' => __( 'The Trigger Field value can narrow the configured "Filter by Roles" setting when it matches a saved Trigger Field choice or an allowed server-side role. If validation fails, the static roles are used.', 'jet-form-builder' ),
+ 'example' => __( 'Select the field whose saved option values should be allowed as dynamic user roles, for example: editor or subscriber.', 'jet-form-builder' ),
),
);
}
@@ -153,7 +153,7 @@
/**
* Generates options with context from a dependent field.
- * Overrides the roles setting with the value from the listened field.
+ * Narrows the saved roles setting with the value from the listened field.
*
* @param array $settings Parsed settings from block attributes.
* @param array $context Associative array ['field_name' => 'value'] from dependent fields.
@@ -163,26 +163,63 @@
public function generate_with_context( array $settings, array $context = array() ): array {
if ( ! empty( $context ) ) {
$context_value = reset( $context );
+ $context_roles = array();
// Support both single role (scalar) and multi-role (checkbox/multi-select array).
if ( is_array( $context_value ) ) {
- $roles = array_filter(
- array_map(
- static function ( $role ) {
- return sanitize_key( (string) $role );
- },
- $context_value
- )
+ $context_roles = $this->sanitize_roles_list( $context_value );
+ } else {
+ $role = sanitize_key( (string) $context_value );
+ if ( $role ) {
+ $context_roles = array( $role );
+ }
+ }
+
+ if ( ! empty( $context_roles ) ) {
+ $runtime = $settings['_jfb_runtime'] ?? array();
+ $block_attrs = $settings['_jfb_block_attrs'] ?? array();
+ $validate_dynamic = apply_filters(
+ 'jet-form-builder/generators/get-from-users/validate_dynamic_roles',
+ true,
+ $context_roles,
+ $settings,
+ $context,
+ $runtime,
+ $block_attrs
);
+ if ( ! $validate_dynamic ) {
+ $settings['roles'] = array_values( $context_roles );
+
+ return $this->generate( $settings );
+ }
+
+ $registered_roles = $this->get_registered_roles();
+ $allowed_roles = $runtime['allowed_roles'] ?? array();
+ $allowed_roles = apply_filters(
+ 'jet-form-builder/generators/get-from-users/allowed-roles',
+ $allowed_roles,
+ $context_roles,
+ $settings,
+ $context,
+ $runtime,
+ $block_attrs
+ );
+ $allowed_roles = $this->sanitize_roles_list( $allowed_roles );
+ $context_roles = array_intersect( $context_roles, $registered_roles );
+
+ if ( ! empty( $allowed_roles ) ) {
+ $context_roles = array_intersect( $context_roles, $allowed_roles );
+ } else {
+ $context_roles = array();
+ }
+
+ $saved_roles = $this->normalize_saved_roles( $settings['roles'] ?? array() );
+ $roles = empty( $saved_roles ) ? $context_roles : array_intersect( $context_roles, $saved_roles );
+
if ( ! empty( $roles ) ) {
$settings['roles'] = array_values( $roles );
}
- } else {
- $role = sanitize_key( (string) $context_value );
- if ( $role ) {
- $settings['roles'] = array( $role );
- }
}
}
@@ -190,6 +227,62 @@
}
/**
+ * Normalize configured roles into a sanitized list.
+ *
+ * @param mixed $saved_roles Configured roles.
+ *
+ * @return array
+ */
+ private function normalize_saved_roles( $saved_roles ): array {
+ if ( is_array( $saved_roles ) ) {
+ return $this->sanitize_roles_list( $saved_roles );
+ }
+
+ return $this->sanitize_roles_list(
+ array_map( 'trim', explode( ',', (string) $saved_roles ) )
+ );
+ }
+
+ /**
+ * Sanitize a list of role slugs.
+ *
+ * @param mixed $roles List of roles.
+ *
+ * @return array
+ */
+ private function sanitize_roles_list( $roles ): array {
+ if ( ! is_array( $roles ) ) {
+ return array();
+ }
+
+ $roles = array_filter(
+ array_map(
+ static function ( $role ) {
+ return sanitize_key( (string) $role );
+ },
+ $roles
+ )
+ );
+
+ return array_values( array_unique( $roles ) );
+ }
+
+ /**
+ * Return the registered WordPress role slugs.
+ *
+ * @return array
+ */
+ private function get_registered_roles(): array {
+ $roles = wp_roles();
+
+ if ( ! $roles || empty( $roles->roles ) || ! is_array( $roles->roles ) ) {
+ return array();
+ }
+
+ return $this->sanitize_roles_list( array_keys( $roles->roles ) );
+ }
+
+ /**
* Returns WP roles as options array for multi-select.
*
* @return array
--- a/jetformbuilder/includes/generators/registry.php
+++ b/jetformbuilder/includes/generators/registry.php
@@ -281,6 +281,13 @@
} else {
$settings = $generator->parse_settings( $block_attrs );
}
+
+ if ( isset( $block_attrs['_jfb_runtime'] ) && is_array( $block_attrs['_jfb_runtime'] ) ) {
+ $settings['_jfb_runtime'] = $block_attrs['_jfb_runtime'];
+ }
+
+ $settings['_jfb_block_attrs'] = $block_attrs;
+
return $generator->generate_with_context( $settings, $context );
}
--- a/jetformbuilder/jet-form-builder.php
+++ b/jetformbuilder/jet-form-builder.php
@@ -3,7 +3,7 @@
* Plugin Name: JetFormBuilder
* Plugin URI: https://jetformbuilder.com/
* Description: Advanced form builder plugin for WordPress block editor. Create forms from the ground up, customize the existing ones, and style them up – all in one editor.
- * Version: 3.6.3
+ * Version: 3.6.3.1
* Author: Crocoblock
* Author URI: https://crocoblock.com/
* Text Domain: jet-form-builder
@@ -18,7 +18,7 @@
die();
}
-const JET_FORM_BUILDER_VERSION = '3.6.3';
+const JET_FORM_BUILDER_VERSION = '3.6.3.1';
const JET_FORM_BUILDER__FILE__ = __FILE__;
const JET_FORM_BUILDER_SITE = 'https://jetformbuilder.com';
--- a/jetformbuilder/modules/option-field/rest-api/generator-update-endpoint.php
+++ b/jetformbuilder/modules/option-field/rest-api/generator-update-endpoint.php
@@ -122,6 +122,7 @@
}
$context = $this->sanitize_context( $context );
+ $block_attrs['_jfb_runtime'] = $this->build_generator_runtime( $block_attrs, $form_post, $field_name );
// Scoped context storage for integrations (preferred over $_REQUEST).
$had_scoped_context = array_key_exists( 'jfb_generator_context', $GLOBALS );
@@ -266,4 +267,130 @@
return $sanitized;
}
+
+ /**
+ * Build server-side runtime metadata for generator context validation.
+ *
+ * @param array $block_attrs Target field block attributes.
+ * @param WP_Post $form_post Form post containing the target field.
+ * @param string $field_name Target field name.
+ *
+ * @return array
+ */
+ private function build_generator_runtime( array $block_attrs, WP_Post $form_post, string $field_name ): array {
+ $runtime = array(
+ 'form_id' => (int) $form_post->ID,
+ 'field_name' => $field_name,
+ );
+
+ $generator_id = $block_attrs['generator_function'] ?? '';
+ $saved_choices = $this->get_saved_listened_field_choice_values( $block_attrs, $form_post );
+
+ if ( 'get_from_db' === $generator_id ) {
+ $runtime['allowed_meta_keys'] = $saved_choices;
+ }
+
+ if ( 'get_from_users' === $generator_id ) {
+ $runtime['allowed_roles'] = $saved_choices;
+ }
+
+ return $runtime;
+ }
+
+ /**
+ * Extract saved choice values from all listened source fields.
+ *
+ * @param array $block_attrs Target field block attributes.
+ * @param WP_Post $form_post Form post containing the target field.
+ *
+ * @return array
+ */
+ private function get_saved_listened_field_choice_values( array $block_attrs, WP_Post $form_post ): array {
+ $choice_values = array();
+
+ foreach ( $this->get_listened_field_names( $block_attrs ) as $listen_field_name ) {
+ $listened_block = Plugin::instance()->form->get_field_by_name( $form_post->ID, $listen_field_name );
+
+ if ( empty( $listened_block ) ) {
+ continue;
+ }
+
+ $choice_values = array_merge(
+ $choice_values,
+ $this->get_saved_choice_values( $listened_block )
+ );
+ }
+
+ return array_values( array_unique( $choice_values ) );
+ }
+
+ /**
+ * Normalize saved listened field names from block attributes.
+ *
+ * @param array $block_attrs Target field block attributes.
+ *
+ * @return array
+ */
+ private function get_listened_field_names( array $block_attrs ): array {
+ $listened = $block_attrs['generator_listen_field'] ?? array();
+
+ if ( is_string( $listened ) ) {
+ $listened = '' === $listened ? array() : array( $listened );
+ }
+
+ if ( ! is_array( $listened ) ) {
+ return array();
+ }
+
+ return array_values(
+ array_filter(
+ array_map(
+ static function ( $field_name ) {
+ return sanitize_text_field( (string) $field_name );
+ },
+ $listened
+ )
+ )
+ );
+ }
+
+ /**
+ * Extract saved option values from a source field block.
+ *
+ * Custom option fields are intentionally excluded because they accept
+ * arbitrary user input and cannot provide a safe static allowlist.
+ *
+ * @param array $field_block Source field block.
+ *
+ * @return array
+ */
+ private function get_saved_choice_values( array $field_block ): array {
+ $attrs = $field_block['attrs'] ?? array();
+
+ if ( empty( $attrs['field_options'] ) || ! is_array( $attrs['field_options'] ) ) {
+ return array();
+ }
+
+ if ( ! empty( $attrs['custom_option'] ) ) {
+ return array();
+ }
+
+ $values = array();
+
+ foreach ( $attrs['field_options'] as $option ) {
+ if ( ! is_array( $option ) || ! isset( $option['value'] ) || ! is_scalar( $option['value'] ) ) {
+ continue;
+ }
+
+ $value = trim( sanitize_text_field( (string) $option['value'] ) );
+
+ if ( '' === $value ) {
+ continue;
+ }
+
+ $values[] = $value;
+ }
+
+ return $values;
+ }
}