Issues (850)

Security Analysis    4 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection (1)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection (2)
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

api/class-getpaid-rest-settings-controller.php (12 issues)

1
<?php
2
/**
3
 * REST API Setting Options controller
4
 *
5
 * Handles requests to the /settings and /settings/$setting_id endpoints.
6
 *
7
 * @package GetPaid
8
 * @subpackage REST API
9
 * @since   2.0.0
10
 */
11
12
defined( 'ABSPATH' ) || exit;
13
14
/**
15
 * GetPaid REST Setting controller class.
16
 *
17
 * @package Invoicing
18
 */
19
class GetPaid_REST_Settings_Controller extends GetPaid_REST_Controller {
20
21
	/**
22
	 * An array of available settings.
23
	 *
24
	 * @var string
25
	 */
26
	protected $settings;
27
28
	/**
29
	 * Route base.
30
	 *
31
	 * @var string
32
	 */
33
	protected $rest_base = 'settings';
34
35
	/**
36
	 * Registers the routes for the objects of the controller.
37
	 *
38
	 * @since 2.0.0
39
	 *
40
	 * @see register_rest_route()
41
	 */
42
	public function register_namespace_routes( $namespace ) {
43
44
		// List all registered tabs.
45
		register_rest_route(
46
			$namespace,
47
			$this->rest_base,
48
			array(
49
				array(
50
					'methods'             => WP_REST_Server::READABLE,
51
					'callback'            => array( $this, 'get_tabs' ),
52
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
53
				),
54
				'schema' => '__return_empty_array',
55
			)
56
		);
57
58
		// View/Update a single setting.
59
		register_rest_route(
60
			$namespace,
61
			$this->rest_base . '/setting/(?P<id>[\w-]+)',
62
			array(
63
				'args'   => array(
64
					'id' => array(
65
						'description' => __( 'Unique identifier for the setting.', 'invoicing' ),
66
						'type'        => 'string',
67
						'required'    => true,
68
					),
69
				),
70
				array(
71
					'methods'             => WP_REST_Server::READABLE,
72
					'callback'            => array( $this, 'get_item' ),
73
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
74
				),
75
				array(
76
					'methods'             => WP_REST_Server::EDITABLE,
77
					'callback'            => array( $this, 'update_item' ),
78
					'permission_callback' => array( $this, 'update_items_permissions_check' ),
79
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
80
				),
81
				'schema' => array( $this, 'get_public_item_schema' ),
82
			)
83
		);
84
85
		// List registered sections for a given tab.
86
		register_rest_route(
87
			$namespace,
88
			$this->rest_base . '/(?P<tab>[\w-]+)',
89
			array(
90
				'args'   => array(
91
					'tab' => array(
92
						'description' => __( 'Unique identifier for the tab whose sections should be retrieved.', 'invoicing' ),
93
						'type'        => 'string',
94
						'required'    => true,
95
						'enum'        => array_keys( wpinv_get_settings_tabs() ),
96
					),
97
				),
98
				array(
99
					'methods'             => WP_REST_Server::READABLE,
100
					'callback'            => array( $this, 'get_sections' ),
101
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
102
				),
103
				'schema' => '__return_empty_array',
104
			)
105
		);
106
107
		// List all registered settings for a given tab.
108
		register_rest_route(
109
			$namespace,
110
			$this->rest_base . '/(?P<tab>[\w-]+)/(?P<section>[\w-]+)',
111
			array(
112
				'args'   => array(
113
					'tab'     => array(
114
						'description' => __( 'Unique identifier for the tab whose settings should be retrieved.', 'invoicing' ),
115
						'type'        => 'string',
116
						'required'    => true,
117
						'enum'        => array_keys( wpinv_get_settings_tabs() ),
118
					),
119
					'section' => array(
120
						'description' => __( 'The section in the tab whose settings should be retrieved.', 'invoicing' ),
121
						'type'        => 'string',
122
						'required'    => true,
123
					),
124
				),
125
				array(
126
					'methods'             => WP_REST_Server::READABLE,
127
					'callback'            => array( $this, 'get_items' ),
128
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
129
				),
130
				'schema' => array( $this, 'get_public_item_schema' ),
131
			)
132
		); 
133
134
		register_rest_route(
135
			$namespace,
136
			'/' . $this->rest_base . '/batch',
137
			array(
138
				'args'   => array(
139
					'id' => array(
140
						'description' => __( 'Setting ID.', 'invoicing' ),
141
						'type'        => 'string',
142
					),
143
				),
144
				array(
145
					'methods'             => WP_REST_Server::EDITABLE,
146
					'callback'            => array( $this, 'batch_items' ),
147
					'permission_callback' => array( $this, 'batch_items_permissions_check' ),
148
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
149
				),
150
				'schema' => array( $this, 'get_public_batch_schema' ),
151
			)
152
		);
153
154
	}
155
156
	/**
157
	 * Return all settings.
158
	 *
159
	 * @since  2.0.0
160
	 * @param  WP_REST_Request $request Request data.
161
	 * @return WP_Error|WP_REST_Response
162
	 */
163
	public function get_items( $request ) {
164
165
		$settings = $this->get_settings();
166
167
		if ( ! isset( $settings[ $request['tab'] ] ) ) {
168
			return new WP_Error( 'rest_invalid_tab', __( 'Invalid tab.', 'invoicing' ), array( 'status' => 400 ) );
169
		}
170
171
		if ( ! isset( $settings[ $request['tab'] ][ $request['section'] ] ) ) {
172
			return new WP_Error( 'rest_invalid_section', __( 'Invalid section.', 'invoicing' ), array( 'status' => 400 ) );
173
		}
174
175
		$settings = $settings[ $request['tab'] ][ $request['section'] ];
176
		$prepared = array();
177
178
		foreach ( $settings as $setting ) {
179
180
			$setting      = $this->sanitize_setting( $setting );
181
			$setting_data = $this->prepare_item_for_response( $setting, $request );
182
			$setting_data = $this->prepare_response_for_collection( $setting_data );
183
184
			if ( $this->is_setting_type_valid( $setting['type'] ) ) {
185
				$prepared[]   = $setting_data;
186
			}
187
}
188
189
		return rest_ensure_response( $prepared );
190
	}
191
192
	/**
193
	 * Return a single setting.
194
	 *
195
	 * @since  2.0.0
196
	 * @param  WP_REST_Request $request Request data.
197
	 * @return WP_Error|WP_REST_Response
198
	 */
199
	public function get_item( $request ) {
200
		$setting  = $this->get_setting( $request['id'] );
201
202
		if ( is_wp_error( $setting ) ) {
203
			return $setting;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $setting also could return the type array which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
204
		}
205
206
		$setting  = $this->sanitize_setting( $setting );
0 ignored issues
show
It seems like $setting can also be of type WP_Error; however, parameter $setting of GetPaid_REST_Settings_Co...ler::sanitize_setting() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

206
		$setting  = $this->sanitize_setting( /** @scrutinizer ignore-type */ $setting );
Loading history...
207
		$response = $this->prepare_item_for_response( $setting, $request );
208
		return rest_ensure_response( $response );
209
	}
210
211
	/**
212
	 * Update a single setting.
213
	 *
214
	 * @since  2.0.0
215
	 * @param  WP_REST_Request $request Request data.
216
	 * @return WP_Error|WP_REST_Response
217
	 */
218
	public function update_item( $request ) {
219
		$setting = $this->get_setting( $request['id'] );
220
221
		if ( is_wp_error( $setting ) ) {
222
			return $setting;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $setting also could return the type array which is incompatible with the documented return type WP_Error|WP_REST_Response.
Loading history...
223
		}
224
225
		if ( is_callable( array( $this, 'validate_setting_' . $setting['type'] . '_field' ) ) ) {
226
			$value = $this->{'validate_setting_' . $setting['type'] . '_field'}( $request['value'], $setting );
227
		} else {
228
			$value = $this->validate_setting_text_field( $request['value'], $setting );
0 ignored issues
show
The call to GetPaid_REST_Settings_Co...te_setting_text_field() has too many arguments starting with $setting. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

228
			/** @scrutinizer ignore-call */ 
229
   $value = $this->validate_setting_text_field( $request['value'], $setting );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
229
		}
230
231
		if ( is_wp_error( $value ) ) {
232
			return $value;
233
		}
234
235
		wpinv_update_option( $request['id'], $value );
236
		$setting['value'] = $value;
237
		$setting          = $this->sanitize_setting( $setting );
0 ignored issues
show
It seems like $setting can also be of type WP_Error; however, parameter $setting of GetPaid_REST_Settings_Co...ler::sanitize_setting() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

237
		$setting          = $this->sanitize_setting( /** @scrutinizer ignore-type */ $setting );
Loading history...
238
		$response         = $this->prepare_item_for_response( $setting, $request );
239
240
		return rest_ensure_response( $response );
241
	}
242
243
	/**
244
	 * Makes sure the current user has access to READ the settings APIs.
245
	 *
246
	 * @since  2.0.0
247
	 * @param WP_REST_Request $request Full data about the request.
248
	 * @return WP_Error|boolean
249
	 */
250
	public function get_items_permissions_check( $request ) {
251
		if ( ! wpinv_current_user_can_manage_invoicing() ) {
252
			return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot list resources.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
253
		}
254
255
		return true;
256
	}
257
258
	/**
259
	 * Makes sure the current user has access to WRITE the settings APIs.
260
	 *
261
	 * @since  2.0.0
262
	 * @param WP_REST_Request $request Full data about the request.
263
	 * @return WP_Error|boolean
264
	 */
265
	public function update_items_permissions_check( $request ) {
266
		if ( ! wpinv_current_user_can_manage_invoicing() ) {
267
			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
268
		}
269
270
		return true;
271
	}
272
273
	/**
274
	 * Check if a given request has access batch create, update and delete items.
275
	 *
276
	 * @param  WP_REST_Request $request Full details about the request.
277
	 *
278
	 * @return boolean|WP_Error
279
	 */
280
	public function batch_items_permissions_check( $request ) {
281
		return wpinv_current_user_can_manage_invoicing() ? true : new WP_Error( 'rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'invoicing' ), array( 'status' => rest_authorization_required_code() ) );
282
	}
283
284
	/**
285
	 * Prepare links for the request.
286
	 *
287
	 * @param string $setting_id Setting ID.
288
	 * @return array Links for the given setting.
289
	 */
290
	protected function prepare_links( $setting_id ) {
291
292
		$links = array(
293
			'self'       => array(
294
				'href' => rest_url( sprintf( '/%s/%s/setting/%s', $this->namespace, $this->rest_base, $setting_id ) ),
295
			),
296
			'collection' => array(
297
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
298
			),
299
		);
300
301
		return $links;
302
	}
303
304
	/**
305
	 * Prepare a settings object for serialization.
306
	 *
307
	 * @since  2.0.0
308
	 * @param array           $item Setting object.
309
	 * @param WP_REST_Request $request Request object.
310
	 * @return WP_REST_Response $response Response data.
311
	 */
312
	public function prepare_item_for_response( $item, $request ) {
313
		$context = empty( $request['context'] ) ? 'view' : $request['context'];
314
		$data    = $this->add_additional_fields_to_object( $item, $request );
315
		$data    = $this->filter_response_by_context( $data, $context );
316
317
		$response = rest_ensure_response( $data );
318
319
		$response->add_links( $this->prepare_links( $item['id'] ) );
320
321
		return $response;
322
	}
323
324
	/**
325
	 * Filters out bad values from the settings array/filter so we
326
	 * only return known values via the API.
327
	 *
328
	 * @since 2.0.0
329
	 * @param  array $setting Setting.
330
	 * @return array
331
	 */
332
	public function filter_setting( $setting ) {
333
		return array_intersect_key(
334
			$setting,
335
			array_flip( array_filter( array_keys( $setting ), array( $this, 'allowed_setting_keys' ) ) )
336
		);
337
	}
338
339
	/**
340
	 * Callback for allowed keys for each setting response.
341
	 *
342
	 * @param  string $key Key to check.
343
	 * @return boolean
344
	 */
345
	public function allowed_setting_keys( $key ) {
346
		return in_array( $key, array_keys( $this->setting_defaults() ), true );
347
	}
348
349
	/**
350
	 * Returns default options for a setting. null means the field is required.
351
	 *
352
	 * @since  2.0.0
353
	 * @return array
354
	 */
355
	protected function setting_defaults() {
356
		return array(
357
			'id'          => null,
358
			'name'        => null,
359
			'desc'        => '',
360
			'options'     => array(),
361
			'std'         => false,
362
			'value'       => false,
363
			'placeholder' => '',
364
			'readonly'    => false,
365
			'faux'        => false,
366
			'section'     => 'main',
367
			'tab'         => 'general',
368
			'type'        => 'text',
369
		);
370
	}
371
372
	/**
373
	 * Sanitizes a setting's field.
374
	 *
375
	 * @param  array $setting The setting to sanitize.
376
	 * @return array
377
	 */
378
	public function sanitize_setting( $setting ) {
379
380
		$setting          = wp_parse_args( $setting, $this->setting_defaults() );
381
		$setting['value'] = wpinv_get_option( $setting['id'], $setting['std'] );
382
		return $this->filter_setting( $setting );
383
384
	}
385
386
	/**
387
	 * Get setting data.
388
	 *
389
	 * @since  2.0.0
390
	 * @param string $setting_id Setting ID.
391
	 * @return array|WP_Error
392
	 */
393
	public function get_setting( $setting_id ) {
394
395
		if ( empty( $setting_id ) ) {
396
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'invoicing' ), array( 'status' => 404 ) );
397
		}
398
399
		$settings  = $this->get_settings();
400
401
		foreach ( $settings as $tabs ) {
402
403
			foreach ( $tabs as $sections ) {
404
405
				if ( isset( $sections[ $setting_id ] ) ) {
406
					if ( ! $this->is_setting_type_valid( $sections[ $setting_id ]['type'] ) ) {
407
						return new WP_Error( 'rest_setting_setting_type_invalid', __( 'Invalid setting type.', 'invoicing' ), array( 'status' => 404 ) );
408
					}
409
410
					return $sections[ $setting_id ];
411
				}
412
}
413
}
414
415
		return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'invoicing' ), array( 'status' => 404 ) );
416
	}
417
418
	/**
419
	 * Get all tabs.
420
	 *
421
	 * @param  WP_REST_Request $request Request data.
422
	 * @return array
423
	 */
424
	public function get_tabs( $request ) {
425
		$tabs     = wpinv_get_settings_tabs();
426
		$prepared = array();
427
428
		foreach ( $tabs as $id => $tab ) {
429
430
			$_request        = $request;
431
			$_request['tab'] = sanitize_title( $id );
432
			$data            = array(
433
				'id'       => sanitize_title( $id ),
434
				'label'    => sanitize_text_field( $tab ),
435
				'sections' => $this->get_sections( $_request ),
436
			);
437
438
			$data     = $this->add_additional_fields_to_object( $data, $request );
439
			$response = rest_ensure_response( $data );
440
441
			if ( ! is_wp_error( $response ) ) {
442
				$links = array(
443
					'sections'   => array(
444
						'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $id ) ),
445
					),
446
					'collection' => array(
447
						'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
448
					),
449
				);
450
				$response->add_links( $links );
451
				$response = $this->prepare_response_for_collection( $response );
452
			}
453
454
			$prepared[] = $response;
455
456
		}
457
458
		return rest_ensure_response( $prepared );
0 ignored issues
show
Bug Best Practice introduced by
The expression return rest_ensure_response($prepared) returns the type WP_REST_Response which is incompatible with the documented return type array.
Loading history...
459
	}
460
461
	/**
462
	 * Get all sections.
463
	 *
464
	 * @param  WP_REST_Request $request Request data.
465
	 * @return array
466
	 */
467
	public function get_sections( $request ) {
468
469
		$tab      = sanitize_title( $request['tab'] );
470
		$sections = wpinv_get_settings_tab_sections( $tab );
471
		$prepared = array();
472
473
		foreach ( $sections as $id => $section ) {
474
475
			$data            = array(
476
				'id'    => sanitize_title( $id ),
477
				'label' => sanitize_text_field( $section ),
478
			);
479
480
			$data     = $this->add_additional_fields_to_object( $data, $request );
481
			$response = rest_ensure_response( $data );
482
483
			if ( ! is_wp_error( $response ) ) {
484
				$links = array(
485
					'settings'   => array(
486
						'href' => rest_url( sprintf( '/%s/%s/%s/%s', $this->namespace, $this->rest_base, $tab, $id ) ),
487
					),
488
					'collection' => array(
489
						'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $tab ) ),
490
					),
491
					'tabs'       => array(
492
						'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
493
					),
494
				);
495
				$response->add_links( $links );
496
				$response = $this->prepare_response_for_collection( $response );
497
			}
498
499
			$prepared[] = $response;
500
501
		}
502
503
		return rest_ensure_response( $prepared );
0 ignored issues
show
Bug Best Practice introduced by
The expression return rest_ensure_response($prepared) returns the type WP_REST_Response which is incompatible with the documented return type array.
Loading history...
504
	}
505
506
	/**
507
	 * Get all settings.
508
	 *
509
	 * @return array
510
	 */
511
	public function get_settings() {
512
513
		if ( empty( $this->settings ) ) {
514
			$this->settings = wpinv_get_registered_settings();
0 ignored issues
show
Documentation Bug introduced by
It seems like wpinv_get_registered_settings() of type array is incompatible with the declared type string of property $settings.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
515
		}
516
517
		return $this->settings;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->settings also could return the type string which is incompatible with the documented return type array.
Loading history...
518
519
	}
520
521
	/**
522
	 * Boolean for if a setting type is a valid supported setting type.
523
	 *
524
	 * @since  2.0.0
525
	 * @param  string $type Type.
526
	 * @return bool
527
	 */
528
	public function is_setting_type_valid( $type ) {
529
530
		return in_array(
531
			$type,
532
            array(
533
				'text',         // Validates with validate_setting_text_field.
534
				'email',        // Validates with validate_setting_text_field.
535
				'number',       // Validates with validate_setting_text_field.
536
				'color',        // Validates with validate_setting_text_field.
537
				'password',     // Validates with validate_setting_text_field.
538
				'textarea',     // Validates with validate_setting_textarea_field.
539
				'select',       // Validates with validate_setting_select_field.
540
				'multiselect',  // Validates with validate_setting_multiselect_field.
541
				'radio',        // Validates with validate_setting_radio_field (-> validate_setting_select_field).
542
				'checkbox',     // Validates with validate_setting_checkbox_field.
543
				'header',       // Validates with validate_setting_text_field.
544
			)
545
		);
546
547
	}
548
549
	/**
550
	 * Get the settings schema, conforming to JSON Schema.
551
	 *
552
	 * @return array
553
	 */
554
	public function get_item_schema() {
555
556
		// Maybe retrieve the schema from cache.
557
		if ( ! empty( $this->schema ) ) {
558
			return $this->add_additional_fields_schema( $this->schema );
559
		}
560
561
		$schema = array(
562
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
563
			'title'      => 'setting',
564
			'type'       => 'object',
565
			'properties' => array(
566
				'id'          => array(
567
					'description' => __( 'A unique identifier for the setting.', 'invoicing' ),
568
					'type'        => 'string',
569
					'arg_options' => array(
570
						'sanitize_callback' => 'sanitize_title',
571
					),
572
					'context'     => array( 'view', 'edit' ),
573
					'readonly'    => true,
574
				),
575
				'tab'         => array(
576
					'description' => __( 'An identifier for the tab this setting belongs to.', 'invoicing' ),
577
					'type'        => 'string',
578
					'arg_options' => array(
579
						'sanitize_callback' => 'sanitize_title',
580
					),
581
					'context'     => array( 'view', 'edit' ),
582
					'readonly'    => true,
583
				),
584
				'section'     => array(
585
					'description' => __( 'An identifier for the section this setting belongs to.', 'invoicing' ),
586
					'type'        => 'string',
587
					'arg_options' => array(
588
						'sanitize_callback' => 'sanitize_title',
589
					),
590
					'context'     => array( 'view', 'edit' ),
591
					'readonly'    => true,
592
				),
593
				'name'        => array(
594
					'description' => __( 'A human readable label for the setting used in interfaces.', 'invoicing' ),
595
					'type'        => 'string',
596
					'arg_options' => array(
597
						'sanitize_callback' => 'sanitize_text_field',
598
					),
599
					'context'     => array( 'view', 'edit' ),
600
					'readonly'    => true,
601
				),
602
				'desc'        => array(
603
					'description' => __( 'A human readable description for the setting used in interfaces.', 'invoicing' ),
604
					'type'        => 'string',
605
					'context'     => array( 'view', 'edit' ),
606
					'readonly'    => true,
607
				),
608
				'value'       => array(
609
					'description' => __( 'The current value of this setting.', 'invoicing' ),
610
					'type'        => 'mixed',
611
					'context'     => array( 'view', 'edit' ),
612
				),
613
				'default'     => array(
614
					'description' => __( 'Default value for the setting.', 'invoicing' ),
615
					'type'        => 'mixed',
616
					'context'     => array( 'view', 'edit' ),
617
					'readonly'    => true,
618
				),
619
				'placeholder' => array(
620
					'description' => __( 'Placeholder text to be displayed in text inputs.', 'invoicing' ),
621
					'type'        => 'string',
622
					'arg_options' => array(
623
						'sanitize_callback' => 'sanitize_text_field',
624
					),
625
					'context'     => array( 'view', 'edit' ),
626
					'readonly'    => true,
627
				),
628
				'type'        => array(
629
					'description' => __( 'Type of setting.', 'invoicing' ),
630
					'type'        => 'string',
631
					'arg_options' => array(
632
						'sanitize_callback' => 'sanitize_text_field',
633
					),
634
					'context'     => array( 'view', 'edit' ),
635
					'enum'        => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox', 'raw_html' ),
636
					'readonly'    => true,
637
				),
638
				'options'     => array(
639
					'description' => __( 'Array of options (key value pairs) for inputs such as select, multiselect, and radio buttons.', 'invoicing' ),
640
					'type'        => 'object',
641
					'context'     => array( 'view', 'edit' ),
642
					'readonly'    => true,
643
				),
644
				'readonly'    => array(
645
					'description' => __( 'Whether or not this setting is readonly', 'invoicing' ),
646
					'type'        => 'string',
647
					'context'     => array( 'view' ),
648
					'readonly'    => true,
649
				),
650
				'faux'        => array(
651
					'description' => __( 'Whether or not this setting is readonly/faux', 'invoicing' ),
652
					'type'        => 'string',
653
					'context'     => array( 'view' ),
654
					'readonly'    => true,
655
				),
656
			),
657
		);
658
659
		// Filters the settings schema for the REST API.
660
        $schema = apply_filters( 'getpaid_rest_settings_schema', $schema );
661
662
		// Cache the settings schema.
663
		$this->schema = $schema;
664
665
		return $this->add_additional_fields_schema( $this->schema );
666
667
	}
668
669
	/**
670
	 * Validate a text value for a text based setting.
671
	 *
672
	 * @since 2.0.0
673
	 * @param string $value Value.
674
	 * @param array  $setting Setting.
675
	 * @return string
676
	 */
677
	public function validate_setting_text_field( $value ) {
678
		$value = is_null( $value ) ? '' : $value;
0 ignored issues
show
The condition is_null($value) is always false.
Loading history...
679
		return wp_kses_post( trim( stripslashes( $value ) ) );
680
	}
681
682
	/**
683
	 * Validate select based settings.
684
	 *
685
	 * @since 2.0.0
686
	 * @param string $value Value.
687
	 * @param array  $setting Setting.
688
	 * @return string|WP_Error
689
	 */
690
	public function validate_setting_select_field( $value, $setting ) {
691
		if ( array_key_exists( $value, $setting['options'] ) ) {
692
			return $value;
693
		} else {
694
			return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'invoicing' ), array( 'status' => 400 ) );
695
		}
696
	}
697
698
	/**
699
	 * Validate multiselect based settings.
700
	 *
701
	 * @since 2.0.0
702
	 * @param array $values Values.
703
	 * @param array $setting Setting.
704
	 * @return array|WP_Error
705
	 */
706
	public function validate_setting_multiselect_field( $values, $setting ) {
707
		if ( empty( $values ) ) {
708
			return array();
709
		}
710
711
		if ( ! is_array( $values ) ) {
0 ignored issues
show
The condition is_array($values) is always true.
Loading history...
712
			return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'invoicing' ), array( 'status' => 400 ) );
713
		}
714
715
		$final_values = array();
716
		foreach ( $values as $value ) {
717
			if ( array_key_exists( $value, $setting['options'] ) ) {
718
				$final_values[] = $value;
719
			}
720
		}
721
722
		return $final_values;
723
	}
724
725
	/**
726
	 * Validate radio based settings.
727
	 *
728
	 * @since 2.0.0
729
	 * @param string $value Value.
730
	 * @param array  $setting Setting.
731
	 * @return string|WP_Error
732
	 */
733
	public function validate_setting_radio_field( $value, $setting ) {
734
		return $this->validate_setting_select_field( $value, $setting );
735
	}
736
737
	/**
738
	 * Validate checkbox based settings.
739
	 *
740
	 * @since 2.0.0
741
	 * @param string $value Value.
742
	 * @return int
743
	 */
744
	public function validate_setting_checkbox_field( $value ) {
745
		return (int) ! empty( $value );
746
	}
747
748
	/**
749
	 * Validate textarea based settings.
750
	 *
751
	 * @since 2.0.0
752
	 * @param string $value Value.
753
	 * @return string
754
	 */
755
	public function validate_setting_textarea_field( $value ) {
756
		$value = is_null( $value ) ? '' : $value;
0 ignored issues
show
The condition is_null($value) is always false.
Loading history...
757
		return wp_kses(
758
			trim( stripslashes( $value ) ),
759
			array_merge(
760
				array(
761
					'iframe' => array(
762
						'src'   => true,
763
						'style' => true,
764
						'id'    => true,
765
						'class' => true,
766
					),
767
				),
768
				wp_kses_allowed_html( 'post' )
769
			)
770
		);
771
	}
772
773
}
774