PluginProbe ʕ •ᴥ•ʔ
JetFormBuilder — Dynamic Blocks Form Builder / 3.6.5
JetFormBuilder — Dynamic Blocks Form Builder v3.6.5
3.6.5.1 3.6.5 3.6.4.2 3.6.4.1 3.6.4 3.6.3.1 3.6.3 3.6.2.2 3.6.2.1 3.6.2 3.6.1.1 3.6.1 3.6.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.1.0 2.1.1 2.1.10 2.1.11 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 3.0.0 3.0.0.1 3.0.0.2 3.0.0.3 3.0.1 3.0.1.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.0.1 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.3.1 3.3.4 3.3.4.1 3.3.4.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.5.1 3.4.5.2 3.4.6 3.4.7 3.4.7.1 3.5.0 3.5.1 3.5.1.1 3.5.1.2 3.5.2 3.5.2.1 3.5.3 3.5.4 3.5.5 3.5.6 3.5.6.1 3.5.6.2 3.5.6.3 3.6.0
jetformbuilder / modules / validation / ssr / is-field-value-unique.php
jetformbuilder / modules / validation / ssr Last commit date
base-validation-callback.php 1 week ago is-field-value-unique.php 1 week ago is-user-email-unique.php 2 years ago is-user-login-unique.php 2 years ago is-user-password-valid.php 1 year ago
is-field-value-unique.php
192 lines
1 <?php
2
3
4 namespace JFB_Modules\Validation\Ssr;
5
6 use Jet_Form_Builder\Classes\Tools;
7 use Jet_Form_Builder\Request\Parser_Context;
8 use JFB_Modules\Block_Parsers\Field_Data_Parser;
9
10 // If this file is called directly, abort.
11 if ( ! defined( 'WPINC' ) ) {
12 die;
13 }
14
15 class Is_Field_Value_Unique extends Base_Validation_Callback {
16
17 public function get_id(): string {
18 return 'is_field_value_unique';
19 }
20
21 public function get_label(): string {
22 return __( 'Is field value unique', 'jet-form-builder' );
23 }
24
25 public function is_valid( $value, Parser_Context $context ): bool {
26 $request = $context->get_request();
27 $field_path = $this->get_field_path( $request['_jfb_validation_path'] ?? '' );
28 $form_id = absint( $request['_jet_engine_booking_form_id'] ?? 0 );
29
30 return $this->is_value_unique( $value, $field_path, $form_id );
31 }
32
33 public function is_valid_with_parser( Field_Data_Parser $parser ): bool {
34 // The parser path is the same path authenticated by the submission signature.
35 $field_path = $this->get_field_path( explode( '.', $parser->get_scoped_name() ) );
36 $form_id = absint( jet_fb_handler()->get_form_id() );
37
38 return $this->is_value_unique( $parser->get_value(), $field_path, $form_id );
39 }
40
41 private function is_value_unique( $value, array $field_path, int $form_id ): bool {
42 $field_name = $field_path[0] ?? '';
43 $nested_path = array_slice( $field_path, 1 );
44
45 if ( ! $form_id || '' === $field_name || is_numeric( $field_name ) ) {
46 return false;
47 }
48
49 global $wpdb;
50 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
51 $record_ids = $wpdb->get_col(
52 $wpdb->prepare(
53 'SELECT id FROM ' . $wpdb->prefix . 'jet_fb_records WHERE form_id = %d AND status = %s',
54 $form_id,
55 'success'
56 )
57 );
58
59 if ( empty( $record_ids ) ) {
60 return true;
61 }
62
63 $placeholders = array_fill( 0, count( $record_ids ), '%d' );
64
65 if ( empty( $nested_path ) ) {
66 $sql = sprintf(
67 'SELECT COUNT(*) FROM ' . $wpdb->prefix . 'jet_fb_records_fields
68 WHERE record_id IN (%s)
69 AND field_name = %%s
70 AND field_value = %%s',
71 implode( ', ', $placeholders )
72 );
73
74 $params = array_merge( $record_ids, array( $field_name, $value ) );
75 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
76 $exists = $wpdb->get_var( $wpdb->prepare( $sql, $params ) );
77
78 return ! $exists;
79 }
80
81 $sql = sprintf(
82 'SELECT field_value, field_attrs FROM ' . $wpdb->prefix . 'jet_fb_records_fields
83 WHERE record_id IN (%s)
84 AND field_name = %%s',
85 implode( ', ', $placeholders )
86 );
87
88 $params = array_merge( $record_ids, array( $field_name ) );
89 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
90 $rows = $wpdb->get_results( $wpdb->prepare( $sql, $params ), ARRAY_A );
91
92 foreach ( $rows as $row ) {
93 if ( ! $this->matches_nested_value( $row, $nested_path, $value ) ) {
94 continue;
95 }
96
97 return false;
98 }
99
100 return true;
101 }
102
103 private function get_field_path( $field_path ): array {
104 if ( ! is_array( $field_path ) ) {
105 $field_path = array( $field_path );
106 }
107
108 $segments = array();
109
110 foreach ( $field_path as $segment ) {
111 if ( ! is_scalar( $segment ) ) {
112 return array();
113 }
114
115 $segment = sanitize_text_field( (string) $segment );
116
117 if ( '' === $segment ) {
118 return array();
119 }
120
121 $segments[] = $segment;
122 }
123
124 return $segments;
125 }
126
127 private function matches_nested_value( array $row, array $nested_path, $value ): bool {
128 $attrs = Tools::decode_json( $row['field_attrs'] ?? '{}' );
129
130 if ( empty( $attrs['is_encoded'] ) ) {
131 return false;
132 }
133
134 $decoded = Tools::decode_json( $row['field_value'] ?? '' );
135
136 if ( ! is_array( $decoded ) ) {
137 return false;
138 }
139
140 $normalized_path = $this->normalize_nested_path( $nested_path );
141
142 if ( empty( $normalized_path ) ) {
143 return false;
144 }
145
146 return $this->matches_path( $decoded, $normalized_path, $value );
147 }
148
149 private function matches_path( $current, array $path, $value ): bool {
150 if ( empty( $path ) ) {
151 return $current === $value;
152 }
153
154 if ( ! is_array( $current ) ) {
155 return false;
156 }
157
158 // Repeater indexes are not signature-bound, so scan every row at each depth.
159 if ( wp_is_numeric_array( $current ) ) {
160 foreach ( $current as $item ) {
161 if ( $this->matches_path( $item, $path, $value ) ) {
162 return true;
163 }
164 }
165
166 return false;
167 }
168
169 $segment = array_shift( $path );
170
171 if ( ! array_key_exists( $segment, $current ) ) {
172 return false;
173 }
174
175 return $this->matches_path( $current[ $segment ], $path, $value );
176 }
177
178 private function normalize_nested_path( array $nested_path ): array {
179 $normalized = array();
180
181 foreach ( $nested_path as $segment ) {
182 if ( is_numeric( $segment ) ) {
183 continue;
184 }
185
186 $normalized[] = $segment;
187 }
188
189 return $normalized;
190 }
191 }
192