PluginProbe ʕ •ᴥ•ʔ
JetFormBuilder — Dynamic Blocks Form Builder / 3.6.5.2
JetFormBuilder — Dynamic Blocks Form Builder v3.6.5.2
3.6.5.2 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 / includes / presets / sources / base-source.php
jetformbuilder / includes / presets / sources Last commit date
base-source.php 5 days ago preset-source-post.php 2 months ago preset-source-query-var.php 5 days ago preset-source-term.php 1 year ago preset-source-user.php 2 years ago
base-source.php
396 lines
1 <?php
2
3
4 namespace Jet_Form_Builder\Presets\Sources;
5
6 use Jet_Form_Builder\Blocks\Types\Base;
7 use Jet_Form_Builder\Exceptions\Preset_Exception;
8 use Jet_Form_Builder\Presets\Preset_Manager;
9 use JFB_Modules\Rich_Content\Macros_Parser;
10
11 // If this file is called directly, abort.
12 if ( ! defined( 'WPINC' ) ) {
13 die;
14 }
15
16 abstract class Base_Source {
17
18 protected $fields_map;
19 protected $field_data = array();
20 protected $field_args;
21 protected $preset_data;
22 protected $field = '__condition__';
23 protected $prop;
24 private $src;
25
26 protected $permission;
27
28 const FUNC_PREFIX = 'source__';
29
30 abstract public function query_source();
31
32 abstract public function get_id();
33
34 public function condition(): bool {
35 return true;
36 }
37
38 /**
39 * Field args getter
40 *
41 * @return array
42 */
43 public function get_field_args() {
44 return $this->field_args;
45 }
46
47 /**
48 * Fields map getter
49 *
50 * @return array
51 */
52 public function get_fields_map() {
53 return $this->fields_map;
54 }
55
56 /**
57 * @param $fields_map
58 * @param $preset_data
59 * @param $args
60 *
61 * @return $this
62 * @throws Preset_Exception
63 */
64 public function init_source( $fields_map, $preset_data, $args ): Base_Source {
65 $this->field_args = $args;
66 $this->field = $args['name'] ?? '';
67 $this->fields_map = $fields_map;
68 $this->preset_data = $preset_data;
69 $this->field_data = $this->get_field_data();
70 $this->prop = $this->get_prop();
71
72 return $this;
73 }
74
75 public function after_init(): Base_Source {
76 return $this;
77 }
78
79 public function after_register() {
80 }
81
82 public function on_sanitize(): bool {
83 return true;
84 }
85
86 public function is_need_prop() {
87 return true;
88 }
89
90 /**
91 * @return mixed
92 * @throws Preset_Exception
93 */
94 public function maybe_query_source() {
95 if ( $this->prop ) {
96 $this->src = $this->query_source();
97
98 return $this;
99 }
100
101 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
102 throw new Preset_Exception( 'Empty `prop` in ' . get_class( $this ), $this->field_data );
103 }
104
105 /**
106 * @return mixed
107 * @throws Preset_Exception
108 */
109 public function get_field_data() {
110 if ( $this->has_field_in_map() ) {
111 return $this->fields_map[ $this->field ];
112 }
113 // phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped
114 throw new Preset_Exception(
115 "Empty `fields_map['{$this->field}']` in " . get_class( $this ),
116 $this->fields_map
117 );
118 // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped
119 }
120
121 public function has_field_in_map() {
122 return ( isset( $this->fields_map[ $this->field ]['prop'] ) || isset( $this->fields_map[ $this->field ]['key'] ) );
123 }
124
125 /**
126 * @return mixed
127 */
128 public function src() {
129 return $this->src;
130 }
131
132 /**
133 * @return mixed
134 * @throws Preset_Exception
135 */
136 public function safe_src() {
137 $this->throw_if_preset_not_available();
138
139 return $this->src();
140 }
141
142 /**
143 * @return bool
144 * @throws Preset_Exception
145 */
146 protected function can_get_preset() {
147 return ( ! empty( $this->src() ) && ! is_wp_error( $this->src() ) );
148 }
149
150 /**
151 * Whether this source allows the `restricted: false` opt-out (see
152 * has_permission()) to skip can_get_preset() at all. A source can override
153 * this to return false when its capability must never be made public by a
154 * form's stored, trusted preset configuration.
155 *
156 * Preset_Source_Options_Page intentionally uses the default: a form author
157 * can explicitly publish the selected Options Page field by switching
158 * "Restrict access" off. Request-provided preset data cannot do so because
159 * it never receives the trusted-origin marker. See issues-tracker #20359.
160 *
161 * @return bool
162 */
163 protected function allows_restriction_bypass(): bool {
164 return true;
165 }
166
167 /**
168 * The `restricted: false` opt-out below only takes effect when both:
169 * - `_trusts_restriction_flag` is true - a flag set programmatically by
170 * Base_Preset::get_source() (never taken from preset_data itself, so
171 * an attacker can't forge it) from whatever the CALL SITE declared via
172 * Base_Preset::trust_restriction_flag(). Only origins that are
173 * admin-authored declare trust: the form's own preset meta
174 * (General_Preset), a field block's attributes
175 * (Preset_Manager::get_field_value()) and the settings parsed via
176 * jet_fb_parse_dynamic_trusted() (validation rules, date min/max,
177 * conditional blocks, action conditions, dynamic value). Parsing a
178 * submitted field value - jet_fb_parse_dynamic() via Rich_Content -
179 * never does, so a `restricted` flag smuggled through a submitted
180 * value is ignored there.
181 * - allows_restriction_bypass() is true for this source (see above).
182 *
183 * This closes the access-control bypass reported in issues-tracker
184 * #20359 while preserving the documented "Restrict access" editor
185 * toggle for admin-configured presets.
186 *
187 * `jet-form-builder/preset-sanitize` runs on BOTH paths - the opt-out
188 * result as well as can_get_preset() - so an integrator can always
189 * further restrict access, including on presets whose author switched
190 * the toggle off. Returning true from it does not grant access that
191 * can_get_preset() denied on the checked path, because the filter only
192 * ever sees an already-computed decision.
193 *
194 * @return bool
195 * @throws Preset_Exception
196 */
197 protected function has_permission(): bool {
198 if ( is_null( $this->permission ) ) {
199 $this->permission = apply_filters(
200 'jet-form-builder/preset-sanitize',
201 $this->allows_restriction_bypass_for_data() ? true : $this->can_get_preset(),
202 $this
203 );
204 }
205
206 return $this->permission;
207 }
208
209 /**
210 * Whether the `restricted: false` opt-out applies to this preset data.
211 *
212 * All four conditions must hold, see has_permission() above.
213 *
214 * @return bool
215 */
216 private function allows_restriction_bypass_for_data(): bool {
217 return (
218 ! empty( $this->preset_data['_trusts_restriction_flag'] ) &&
219 $this->allows_restriction_bypass() &&
220 array_key_exists( 'restricted', $this->preset_data ) &&
221 ! $this->preset_data['restricted']
222 );
223 }
224
225 /**
226 * @throws Preset_Exception
227 */
228 final protected function throw_if_preset_not_available() {
229 if ( ! $this->has_permission() ) {
230 throw new Preset_Exception( static::class . '::can_get_preset return FALSE' );
231 }
232 }
233
234
235 protected function get_prop() {
236 if ( ! $this->is_need_prop() ) {
237 return true;
238 }
239
240 return ( ! empty( $this->field_data['prop'] ) ? $this->field_data['prop'] : false );
241 }
242
243 /**
244 * Public prop getter
245 *
246 * @return string
247 */
248 public function get_prop_name() {
249 return $this->prop;
250 }
251
252 /**
253 * @return false|mixed
254 * @throws Preset_Exception
255 */
256 public function get_result_on_prop() {
257 if ( ! $this->is_need_prop() ) {
258 return $this->src();
259 }
260
261 $extra = $this->get_extra_fields();
262
263 if ( empty( $extra ) ) {
264 return $this->get_current_value();
265 }
266
267 $value = array();
268
269 foreach ( $extra as $name => $field ) {
270 $this->before_query_extra_field( $field );
271
272 $value[ $name ] = $this->get_current_value();
273 }
274
275 return $value;
276 }
277
278 protected function before_query_extra_field( $field ) {
279 $this->field_data['key'] = $field;
280 }
281
282 /**
283 * @return false|mixed
284 * @throws Preset_Exception
285 */
286 private function get_current_value() {
287 $func_name = self::FUNC_PREFIX . $this->prop;
288
289 if ( is_callable( array( $this, $func_name ) ) ) {
290 $result = call_user_func( array( $this, $func_name ) );
291 } else {
292 $result = $this->default_prop( $this->prop );
293 }
294
295 return apply_filters(
296 'jet-form-builder/preset/source/value',
297 $result,
298 $this
299 );
300 }
301
302 private function get_extra_fields(): array {
303 try {
304 $extra = $this->get_field_object()->get_extra_fields( $this );
305 } catch ( Preset_Exception $exception ) {
306 return array();
307 }
308
309 $extra = apply_filters( 'jet-form-builder/preset/extra-fields', $extra, $this );
310
311 $parser = ( new Macros_Parser() )->set_replacements(
312 array(
313 'key' => $this->field_data['key'] ?? '',
314 'prop' => $this->prop,
315 )
316 );
317
318 foreach ( $extra as $index => $field ) {
319 $extra[ $index ] = $parser->parse_macros( $field );
320 }
321
322 return $extra;
323 }
324
325 /**
326 * @param string $prop
327 *
328 * @return mixed
329 * @throws Preset_Exception
330 */
331 public function default_prop( string $prop ) {
332 $source = $this->src;
333
334 if ( isset( $source->$prop ) ) {
335 return $source->$prop;
336 } elseif ( isset( $source->data ) && isset( $source->data->$prop ) ) {
337 return $source->data->$prop;
338 }
339
340 if ( ! is_object( $source ) ) {
341 throw new Preset_Exception( "Source isn't object" );
342 }
343
344 throw new Preset_Exception(
345 esc_html( "Can't get value from " . get_class( $source ) )
346 );
347 }
348
349
350 /**
351 * @return mixed
352 * @throws Preset_Exception
353 */
354 final public function result() {
355 $this->throw_if_preset_not_available();
356
357 return $this->parse_result_value( $this->get_result_on_prop() );
358 }
359
360 public function parse_result_value( $value ) {
361 if ( ! isset( $this->field_args['type'] ) ) {
362 return $value;
363 }
364
365 return Preset_Manager::instance()->prepare_result( $this->field_args['type'], $value );
366 }
367
368
369 /**
370 * @return Base
371 * @throws Preset_Exception
372 */
373 public function get_field_object(): Base {
374 $type = $this->field_args['type'] ?? false;
375 $block = jet_form_builder()->blocks->get_field_by_name( $type );
376
377 if ( ! $block ) {
378 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
379 throw new Preset_Exception( 'Undefined block_type: ' . $type, $this->field_args );
380 }
381
382 $block->block_attrs = $this->field_args;
383
384 return $block;
385 }
386
387 /**
388 * @return mixed
389 * @throws Preset_Exception
390 */
391 public function get_expected_format() {
392 return $this->get_field_object()->expected_preset_type()[0] ?? 'raw';
393 }
394
395 }
396