Issues (2873)

Security Analysis    not enabled

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

  Cross-Site Scripting
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.
  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.
  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.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  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.
  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.
  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.
  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.
  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.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  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.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
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.

classes/fields/oembed.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @package Pods\Fields
5
 */
6
class PodsField_OEmbed extends PodsField {
7
8
	/**
9
	 * {@inheritdoc}
10
	 */
11
	public static $group = 'Relationships / Media';
12
13
	/**
14
	 * {@inheritdoc}
15
	 */
16
	public static $type = 'oembed';
17
18
	/**
19
	 * {@inheritdoc}
20
	 */
21
	public static $label = 'oEmbed';
22
23
	/**
24
	 * {@inheritdoc}
25
	 */
26
	public static $prepare = '%s';
27
28
	/**
29
	 * Available oEmbed providers
30
	 *
31
	 * @var array
32
	 * @since 2.7
33
	 */
34
	private $providers = array();
35
36
	/**
37
	 * Current embed width
38
	 *
39
	 * @var int
40
	 * @since 2.7
41
	 */
42
	private $width = 0;
43
44
	/**
45
	 * Current embed height
46
	 *
47
	 * @var int
48
	 * @since 2.7
49
	 */
50
	private $height = 0;
51
52
	/**
53
	 * {@inheritdoc}
54
	 */
55
	public function setup() {
56
57
		self::$label = __( 'oEmbed', 'pods' );
58
	}
59
60
	/**
61
	 * {@inheritdoc}
62
	 */
63
	public function admin_init() {
64
65
		// AJAX for Uploads
66
		add_action( 'wp_ajax_oembed_update_preview', array( $this, 'admin_ajax_oembed_update_preview' ) );
67
	}
68
69
	/**
70
	 * {@inheritdoc}
71
	 */
72
	public function options() {
73
74
		$options = array(
75
			static::$type . '_repeatable'   => array(
76
				'label'             => __( 'Repeatable Field', 'pods' ),
77
				'default'           => 0,
78
				'type'              => 'boolean',
79
				'help'              => __( 'Making a field repeatable will add controls next to the field which allows users to Add/Remove/Reorder additional values. These values are saved in the database as an array, so searching and filtering by them may require further adjustments".', 'pods' ),
80
				'boolean_yes_label' => '',
81
				'dependency'        => true,
82
				'developer_mode'    => true,
83
			),
84
			static::$type . '_width'        => array(
85
				'label'   => __( 'Embed Width', 'pods' ),
86
				'default' => 0,
87
				'type'    => 'number',
88
				'help'    => __( 'Optional width to use for this oEmbed. Leave as 0 (zero) to default to none.', 'pods' ),
89
			),
90
			static::$type . '_height'       => array(
91
				'label'   => __( 'Embed Height', 'pods' ),
92
				'default' => 0,
93
				'type'    => 'number',
94
				'help'    => __( 'Optional height to use for this oEmbed. Leave as 0 (zero) to default to none.', 'pods' ),
95
			),
96
			static::$type . '_show_preview' => array(
97
				'label'   => __( 'Show preview', 'pods' ),
98
				'default' => 0,
99
				'type'    => 'boolean',
100
			),
101
		);
102
103
		// Get all unique provider host names
104
		$unique_providers = array();
105
		foreach ( $this->get_providers() as $provider ) {
106
			if ( ! in_array( $provider['host'], $unique_providers, true ) ) {
107
				$unique_providers[] = $provider['host'];
108
			}
109
		}
110
		sort( $unique_providers );
111
112
		// Only add the options if we have data
113
		if ( ! empty( $unique_providers ) ) {
114
			$options[ static::$type . '_restrict_providers' ] = array(
115
				'label'      => __( 'Restrict to providers', 'pods' ),
116
				'help'       => __( 'Restrict input to specific WordPress oEmbed compatible providers.', 'pods' ),
117
				'type'       => 'boolean',
118
				'default'    => 0,
119
				'dependency' => true,
120
			);
121
			$options[ static::$type . '_enable_providers' ]   = array(
122
				'label'      => __( 'Select enabled providers', 'pods' ),
123
				'depends-on' => array( static::$type . '_restrict_providers' => true ),
124
				'group'      => array(),
125
			);
126
			// Add all the oEmbed providers
127
			foreach ( $unique_providers as $provider ) {
128
				$options[ static::$type . '_enable_providers' ]['group'][ static::$type . '_enabled_providers_' . tag_escape( $provider ) ] = array(
129
					'label'   => $provider,
130
					'type'    => 'boolean',
131
					'default' => 0,
132
				);
133
			}
134
		}//end if
135
136
		return $options;
137
	}
138
139
	/**
140
	 * {@inheritdoc}
141
	 */
142
	public function schema( $options = null ) {
143
144
		$schema = 'LONGTEXT';
145
146
		return $schema;
147
	}
148
149
	/**
150
	 * {@inheritdoc}
151
	 */
152
	public function display( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
153
154
		$value = $this->pre_save( $value, $id, $name, $options, null, $pod );
155
156
		$width  = (int) pods_v( static::$type . '_width', $options );
157
		$height = (int) pods_v( static::$type . '_height', $options );
158
		$args   = array();
159
		if ( $width > 0 ) {
160
			$args['width'] = $width;
161
		}
162
		if ( $height > 0 ) {
163
			$args['height'] = $height;
164
		}
165
166
		$value = wp_oembed_get( $value, $args );
167
168
		return $value;
169
	}
170
171
	/**
172
	 * {@inheritdoc}
173
	 */
174
	public function input( $name, $value = null, $options = null, $pod = null, $id = null ) {
175
176
		$options         = (array) $options;
177
		$form_field_type = PodsForm::$field_type;
178
179
		if ( is_array( $value ) ) {
180
			$value = implode( ' ', $value );
181
		}
182
183
		if ( isset( $options['name'] ) && false === PodsForm::permission( static::$type, $options['name'], $options, null, $pod, $id ) ) {
184
			if ( pods_v( 'read_only', $options, false ) ) {
185
				$options['readonly'] = true;
186
			} else {
187
				return;
188
			}
189
		} elseif ( ! pods_has_permissions( $options ) && pods_v( 'read_only', $options, false ) ) {
190
			$options['readonly'] = true;
191
		}
192
193
		pods_view( PODS_DIR . 'ui/fields/oembed.php', compact( array_keys( get_defined_vars() ) ) );
194
	}
195
196
	/**
197
	 * {@inheritdoc}
198
	 */
199
	public function validate( $value, $name = null, $options = null, $fields = null, $pod = null, $id = null, $params = null ) {
200
201
		$errors = array();
202
203
		$check = $this->pre_save( $value, $id, $name, $options, $fields, $pod, $params );
204
205
		if ( is_array( $check ) ) {
206
			$errors = $check;
207
		} else {
208
			if ( 0 < strlen( $value ) && '' === $check ) {
209
				if ( 1 === (int) pods_v( 'required', $options ) ) {
210
					$errors[] = __( 'This field is required.', 'pods' );
211
				}
212
			}
213
		}
214
215
		if ( ! empty( $errors ) ) {
216
			return $errors;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $errors; (array) is incompatible with the return type of the parent method PodsField::validate of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
217
		}
218
219
		return true;
220
	}
221
222
	/**
223
	 * {@inheritdoc}
224
	 */
225
	public function pre_save( $value, $id = null, $name = null, $options = null, $fields = null, $pod = null, $params = null ) {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
226
227
		$value = $this->strip_html( $value, $options );
228
229
		// Only allow ONE URL
230
		if ( ! empty( $value ) ) {
231
			$value = explode( ' ', $value );
232
			$value = esc_url( $value[0] );
233
		}
234
235
		if ( $this->validate_provider( $value, $options ) ) {
236
			return $value;
237
		} else {
238
			return false;
239
		}
240
241
	}
242
243
	/**
244
	 * {@inheritdoc}
245
	 */
246
	public function ui( $id, $value, $name = null, $options = null, $fields = null, $pod = null ) {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
This method's name is shorter than the configured minimum length of 3 characters.

Even though PHP does not care about the name of your methods, it is generally a good practice to choose method names which can be easily understood by other human readers.

Loading history...
247
248
		$value = $this->pre_save( $value, $id, $name, $options, $fields, $pod );
249
250
		return $value;
251
	}
252
253
	/**
254
	 * {@inheritdoc}
255
	 */
256
	public function strip_html( $value, $options = null ) {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
257
258
		if ( is_array( $value ) ) {
259
			// @codingStandardsIgnoreLine
260
			$value = @implode( ' ', $value );
261
		}
262
263
		$value = trim( $value );
264
265
		if ( empty( $value ) ) {
266
			return $value;
267
		}
268
269
		// Strip HTML
270
		$value = strip_tags( $value );
271
272
		// Strip shortcodes
273
		$value = strip_shortcodes( $value );
274
275
		return $value;
276
	}
277
278
	/**
279
	 * Passes any unlinked URLs that are on their own line to {@link WP_Embed::shortcode()} for potential embedding.
280
	 *
281
	 * @see   WP_Embed::autoembed()
282
	 * @see   WP_Embed::autoembed_callback()
283
	 *
284
	 * @uses  PodsField_OEmbed::autoembed_callback()
285
	 *
286
	 * @param string $content The content to be searched.
287
	 *
288
	 * @return string Potentially modified $content.
289
	 *
290
	 * @since 2.7
291
	 */
292
	public function autoembed( $content ) {
293
294
		// Replace line breaks from all HTML elements with placeholders.
295
		$content = wp_replace_in_html_tags( $content, array( "\n" => '<!-- wp-line-break -->' ) );
296
297
		// Find URLs that are on their own line.
298
		$content = preg_replace_callback(
299
			'|^(\s*)(https?://[^\s"]+)(\s*)$|im', array(
300
				$this,
301
				'autoembed_callback',
302
			), $content
303
		);
304
305
		// Put the line breaks back.
306
		return str_replace( '<!-- wp-line-break -->', "\n", $content );
307
308
	}
309
310
	/**
311
	 * Callback function for {@link WP_Embed::autoembed()}.
312
	 *
313
	 * @param array $match A regex match array.
314
	 *
315
	 * @return string The embed shortcode
316
	 *
317
	 * @since 2.7
318
	 */
319
	public function autoembed_callback( $match ) {
320
321
		$shortcode = '[embed width="' . $this->width . '" height="' . $this->height . '"]' . $match[2] . '[/embed]';
322
323
		return $shortcode;
324
325
	}
326
327
	/**
328
	 * Get a list of available providers from the WP_oEmbed class
329
	 *
330
	 * @see   wp-includes/class-oembed.php
331
	 * @return array $providers {
332
	 *     Array of provider data with regex as key
333
	 *
334
	 * @type string URL for this provider
335
	 * @type int
336
	 * @type string Hostname for this provider
337
	 * }
338
	 *
339
	 * @since 2.7
340
	 */
341
	public function get_providers() {
342
343
		// Return class property if already set
344
		if ( ! empty( $this->providers ) ) {
345
			return $this->providers;
346
		}
347
348
		if ( file_exists( ABSPATH . WPINC . '/class-oembed.php' ) ) {
349
			require_once ABSPATH . WPINC . '/class-oembed.php';
350
		}
351
352
		// Return an empty array if no providers could be found
353
		$providers = array();
354
355
		if ( function_exists( '_wp_oembed_get_object' ) ) {
356
			$wp_oembed = _wp_oembed_get_object();
357
			$providers = $wp_oembed->providers;
358
359
			foreach ( $providers as $key => $provider ) {
360
				$url  = wp_parse_url( $provider[0] );
361
				$host = $url['host'];
362
				$tmp  = explode( '.', $host );
363
364
				if ( count( $tmp ) === 3 ) {
0 ignored issues
show
Found "=== 3". Use Yoda Condition checks, you must
Loading history...
365
					// Take domain names like .co.uk in consideration
366
					if ( ! in_array( 'co', $tmp, true ) ) {
367
						unset( $tmp[0] );
368
					}
369
				} elseif ( count( $tmp ) === 4 ) {
0 ignored issues
show
Found "=== 4". Use Yoda Condition checks, you must
Loading history...
370
					// Take domain names like .co.uk in consideration
371
					unset( $tmp[0] );
372
				}
373
374
				$host = implode( '.', $tmp );
375
376
				$providers[ $key ]['host'] = $host;
377
			}
378
379
			$this->providers = $providers;
380
		}//end if
381
382
		return $providers;
383
384
	}
385
386
	/**
387
	 * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one.
388
	 * This function is ripped from WP since Pods has support from 3.8 and in the WP core this function is 4.0+
389
	 * We've stripped the autodiscover part from this function to keep it basic
390
	 *
391
	 * @since  2.7
392
	 * @access public
393
	 *
394
	 * @see    WP_oEmbed::get_provider()
395
	 *
396
	 * @param string $url The URL to the content.
397
	 *
398
	 * @return false|string False on failure, otherwise the oEmbed provider URL.
0 ignored issues
show
Should the return type not be integer|string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
399
	 */
400
	public function get_provider( $url ) {
401
402
		$provider = false;
403
404
		foreach ( $this->providers as $matchmask => $data ) {
405
			if ( isset( $data['host'] ) ) {
406
				unset( $data['host'] );
407
			}
408
			reset( $data );
409
410
			list( $providerurl, $regex ) = $data;
411
412
			$match = $matchmask;
413
414
			// Turn the asterisk-type provider URLs into regex
415
			if ( ! $regex ) {
416
				$matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
417
				$matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
418
			}
419
420
			if ( preg_match( $matchmask, $url ) ) {
421
				$provider = $match;
422
423
				break;
424
			}
425
		}//end foreach
426
427
		return $provider;
428
	}
429
430
	/**
431
	 * Validate a value with the enabled oEmbed providers (if required).
432
	 *
433
	 * @since 2.7
434
	 *
435
	 * @param string $value   Field value.
436
	 * @param array  $options Field options.
437
	 *
438
	 * @return bool
439
	 */
440
	public function validate_provider( $value, $options ) {
441
442
		// Check if we need to validate.
443
		if ( 0 === (int) pods_v( static::$type . '_restrict_providers', $options ) ) {
444
			return true;
445
		}
446
447
		$providers = $this->get_providers();
448
449
		// Filter existing providers.
450
		foreach ( $providers as $key => $provider ) {
451
			$fieldname = static::$type . '_enabled_providers_' . tag_escape( $provider['host'] );
452
453
			/**
454
			 * @todo Future compat to enable serialised strings as field options
455
			 */
456
457
			/**
458
			 * Current solution: all separate field options.
459
			 */
460
			if ( empty( $options[ $fieldname ] ) ) {
461
				unset( $providers[ $key ] );
462
			}
463
		}
464
465
		// Value validation.
466
		$provider_match = $this->get_provider( $value );
467
468
		foreach ( $providers as $match => $provider ) {
469
			if ( $provider_match === $match ) {
470
				return true;
471
			}
472
		}
473
474
		return false;
475
	}
476
477
	/**
478
	 * Handle update preview AJAX.
479
	 *
480
	 * @since 2.7
481
	 */
482
	public function admin_ajax_oembed_update_preview() {
483
484
		// Sanitize input.
485
		// @codingStandardsIgnoreLine
486
		$params = pods_unslash( (array) $_POST );
487
488
		if ( ! empty( $params['_nonce_pods_oembed'] ) && ! empty( $params['pods_field_oembed_value'] ) && wp_verify_nonce( $params['_nonce_pods_oembed'], 'pods_field_oembed_preview' ) ) {
489
			$value = $this->strip_html( $params['pods_field_oembed_value'] );
490
491
			$name    = '';
492
			$options = array();
493
494
			if ( ! empty( $params['pods_field_oembed_name'] ) ) {
495
				$name = $this->strip_html( $params['pods_field_oembed_name'] );
496
			}
497
498
			if ( ! empty( $params['pods_field_oembed_options'] ) ) {
499
				$options = $params['pods_field_oembed_options'];
500
			}
501
502
			// Load the field to get it's options.
503
			$options = pods_api()->load_field( (object) $options );
504
505
			// Field options are stored here, if not, just stay with the full options array.
506
			if ( ! empty( $options['options'] ) ) {
507
				$options = $options['options'];
508
			}
509
510
			// Run display function to run oEmbed.
511
			$value = $this->display( $value, $name, $options );
512
513
			if ( empty( $value ) ) {
514
				$value = __( 'Please choose a valid oEmbed URL.', 'pods' );
515
				wp_send_json_error( $value );
516
			} else {
517
				wp_send_json_success( $value );
518
			}
519
		}//end if
520
		wp_send_json_error( __( 'Unauthorized request', 'pods' ) );
521
522
		die();
523
		// Kill it!
524
	}
525
526
}
527