Completed
Pull Request — 2.x (#3632)
by Scott Kingsley
05:01
created

PodsField_OEmbed::get_providers()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 22
nc 13
nop 0
dl 0
loc 37
rs 5.3846
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package Pods\Fields
4
 */
5
class PodsField_OEmbed extends PodsField {
6
7
	/**
8
	 * Field Type Group
9
	 *
10
	 * @var string
11
	 * @since 2.0
12
	 */
13
	public static $group = 'Relationships / Media';
14
15
	/**
16
	 * Field Type Identifier
17
	 *
18
	 * @var string
19
	 * @since 2.0
20
	 */
21
	public static $type = 'oembed';
22
23
	/**
24
	 * Field Type Label
25
	 *
26
	 * @var string
27
	 * @since 2.0
28
	 */
29
	public static $label = 'oEmbed';
30
31
	/**
32
	 * Field Type Preparation
33
	 *
34
	 * @var string
35
	 * @since 2.0
36
	 */
37
	public static $prepare = '%s';
38
	
39
	/**
40
	 * Available oEmbed providers
41
	 * 
42
	 * @var array
43
	 * @since 2.7
44
	 */
45
	private $providers = array();
46
	
47
	/**
48
	 * Current embed width
49
	 * 
50
	 * @var int
51
	 * @since 2.7
52
	 */
53
	private $width = 0;
54
	
55
	/**
56
	 * Current embed height
57
	 * 
58
	 * @var int
59
	 * @since 2.7
60
	 */
61
	private $height = 0;
62
63
	/**
64
	 * Do things like register/enqueue scripts and stylesheets
65
	 *
66
	 * @since 2.0
67
	 */
68
	public function __construct () {
69
		self::$label = __( 'oEmbed', 'pods' );
70
	}
71
72
	/**
73
	 * Add admin_init actions
74
	 *
75
	 * @since 2.3
76
	 */
77
	public function admin_init() {
78
		// AJAX for Uploads
79
		add_action( 'wp_ajax_oembed_update_preview', array( $this, 'admin_ajax_oembed_update_preview' ) );
80
	}
81
82
	/**
83
	 * Add options and set defaults to
84
	 *
85
	 * @return array
86
	 * @since 2.0
87
	 */
88
	public function options () {
89
90
		$options = array(
91
			self::$type . '_repeatable' => array(
92
				'label' => __( 'Repeatable Field', 'pods' ),
93
				'default' => 0,
94
				'type' => 'boolean',
95
				'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' ),
96
				'boolean_yes_label' => '',
97
				'dependency' => true,
98
				'developer_mode' => true
99
			),
100
			self::$type . '_width' => array(
101
				'label' => __( 'Embed Width', 'pods' ),
102
				'default' => 0,
103
				'type' => 'number',
104
				'help' => __( 'Optional width to use for this oEmbed. Leave as 0 (zero) to default to none.', 'pods' )
105
			),
106
			self::$type . '_height' => array(
107
				'label' => __( 'Embed Height', 'pods' ),
108
				'default' => 0,
109
				'type' => 'number',
110
				'help' => __( 'Optional height to use for this oEmbed. Leave as 0 (zero) to default to none.', 'pods' )
111
			),
112
			self::$type . '_show_preview' => array(
113
				'label' => __( 'Show preview', 'pods' ),
114
				'default' => 0,
115
				'type' => 'boolean'
116
			),
117
		);
118
119
		// Get all unique provider host names
120
		$unique_providers = array();
121
		foreach ( $this->get_providers() as $provider ) {
122
			if ( ! in_array( $provider['host'], $unique_providers ) )
123
				$unique_providers[] = $provider['host'];
124
		}
125
		sort($unique_providers);
126
127
		// Only add the options if we have data
128
		if ( ! empty( $unique_providers ) ) {
129
			$options[ self::$type . '_restrict_providers' ] = array(
130
				'label' => __( 'Restrict to providers', 'pods' ),
131
				'help' => __( 'Restrict input to specific WordPress oEmbed compatible providers.', 'pods' ),
132
				'type' => 'boolean',
133
				'default' => 0,
134
				'dependency' => true,
135
			);
136
			$options[ self::$type . '_enable_providers' ] = array(
137
				'label' => __( 'Select enabled providers', 'pods' ),
138
				'depends-on' => array( self::$type . '_restrict_providers' => true ),
139
				'group' => array()
140
			);
141
			// Add all the oEmbed providers
142
			foreach( $unique_providers as $provider ) {
143
				$options[ self::$type . '_enable_providers' ]['group'][ self::$type . '_enabled_providers_' . tag_escape( $provider ) ] = array(
144
					'label' => $provider,
145
					'type' => 'boolean',
146
					'default' => 0,
147
				);
148
			}
149
		}
150
151
152
		return $options;
153
	}
154
155
	/**
156
	 * Define the current field's schema for DB table storage
157
	 *
158
	 * @param array $options
159
	 *
160
	 * @return array
161
	 * @since 2.0
162
	 */
163
	public function schema ( $options = null ) {
164
		$schema = 'LONGTEXT';
165
166
		return $schema;
167
	}
168
169
	/**
170
	 * Change the way the value of the field is displayed with Pods::get
171
	 *
172
	 * @param mixed $value
173
	 * @param string $name
174
	 * @param array $options
175
	 * @param array $fields
0 ignored issues
show
Bug introduced by
There is no parameter named $fields. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
176
	 * @param array $pod
177
	 * @param int $id
178
	 *
179
	 * @since 2.0
180
	 */
181
	public function display ( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
182
		$value = $this->pre_save( $value, $id, $name, $options, null, $pod );
183
184
		$width = (int) pods_v( self::$type . '_width', $options );
185
		$height = (int) pods_v( self::$type . '_height', $options );
186
		$args = array();
187
		if ( $width > 0 ) {
188
			$args['width'] = $width;
189
		}
190
		if ( $height > 0 ) {
191
			$args['height'] = $height;
192
		}
193
194
		$value = wp_oembed_get( $value, $args );
195
196
		/**
197
		 * @var $embed WP_Embed
198
		 */
199
		/*$embed = $GLOBALS[ 'wp_embed' ];
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
200
201
		if ( 0 < $width && 0 < $height ) {
202
			$this->width = $width;
203
			$this->height = $height;
204
			
205
			// Setup [embed] shortcodes with set width/height
206
			$value = $this->autoembed( $value );
207
			
208
			// Run [embed] shortcodes
209
			$value = $embed->run_shortcode( $value );
210
		} else {
211
			// Autoembed URL normally
212
			$value = $embed->autoembed( $value );
213
		}*/
214
		return $value;
215
	}
216
217
	/**
218
	 * Customize output of the form field
219
	 *
220
	 * @param string $name
221
	 * @param mixed $value
222
	 * @param array $options
223
	 * @param array $pod
224
	 * @param int $id
225
	 *
226
	 * @since 2.0
227
	 */
228 View Code Duplication
	public function input ( $name, $value = null, $options = null, $pod = null, $id = null ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229
		$options = (array) $options;
230
		$form_field_type = PodsForm::$field_type;
0 ignored issues
show
Bug introduced by
The property field_type cannot be accessed from this context as it is declared private in class PodsForm.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
231
232
		if ( is_array( $value ) )
233
			$value = implode( ' ', $value );
234
235
		if ( isset( $options[ 'name' ] ) && false === PodsForm::permission( self::$type, $options[ 'name' ], $options, null, $pod, $id ) ) {
236
			if ( pods_var( 'read_only', $options, false ) )
237
				$options[ 'readonly' ] = true;
238
			else
239
				return;
240
		}
241
		elseif ( !pods_has_permissions( $options ) && pods_var( 'read_only', $options, false ) )
242
			$options[ 'readonly' ] = true;
243
244
		pods_view( PODS_DIR . 'ui/fields/oembed.php', compact( array_keys( get_defined_vars() ) ) );
245
	}
246
247
	/**
248
	 * Validate a value before it's saved
249
	 *
250
	 * @param mixed $value
251
	 * @param string $name
252
	 * @param array $options
253
	 * @param array $fields
254
	 * @param array $pod
255
	 * @param int $id
256
	 *
257
	 * @param null $params
258
	 * @return array|bool
259
	 * @since 2.0
260
	 */
261
	public function validate ( $value, $name = null, $options = null, $fields = null, $pod = null, $id = null, $params = null ) {
262
		$errors = array();
263
264
		$check = $this->pre_save( $value, $id, $name, $options, $fields, $pod, $params );
265
266
		if ( is_array( $check ) )
267
			$errors = $check;
268
		else {
269
			if ( 0 < strlen( $value ) && strlen( $check ) < 1 ) {
270
				if ( 1 == (bool) pods_v( 'required', $options ) )
271
					$errors[] = __( 'This field is required.', 'pods' );
272
			}
273
		}
274
275
		if ( !empty( $errors ) )
276
			return $errors;
277
278
		return true;
279
	}
280
281
	/**
282
	 * Change the value or perform actions after validation but before saving to the DB
283
	 *
284
	 * @param mixed $value
285
	 * @param int $id
286
	 * @param string $name
287
	 * @param array $options
288
	 * @param array $fields
289
	 * @param array $pod
290
	 * @param object $params
291
	 *
292
	 * @return mixed|string
293
	 * @since 2.0
294
	 */
295
	public function pre_save ( $value, $id = null, $name = null, $options = null, $fields = null, $pod = null, $params = null ) {
296
		$value = $this->strip_html( $value, $options );
297
298
		// Only allow ONE URL
299
		if ( ! empty( $value ) ) {
300
			$value = explode( ' ', $value );
301
			$value = esc_url( $value[0] );
302
		}
303
304
		if ( $this->validate_provider( $value, $options ) ) {
305
			return $value;
306
		} else {
307
			return false;
308
		}
309
310
	}
311
312
	/**
313
	 * Customize the Pods UI manage table column output
314
	 *
315
	 * @param int $id
316
	 * @param mixed $value
317
	 * @param string $name
318
	 * @param array $options
319
	 * @param array $fields
320
	 * @param array $pod
321
	 *
322
	 * @return mixed|string
323
	 * @since 2.0
324
	 */
325
	public function ui ( $id, $value, $name = null, $options = null, $fields = null, $pod = null ) {
326
		$value = $this->pre_save( $value, $id, $name, $options, $fields, $pod );
327
328
		return $value;
329
	}
330
331
	/**
332
	 * Strip HTML based on options
333
	 *
334
	 * @param string $value
335
	 * @param array $options
336
	 *
337
	 * @return string
338
	 */
339
	public function strip_html ( $value, $options = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
340
		if ( is_array( $value ) )
341
			$value = @implode( ' ', $value );
342
343
		$value = trim( $value );
344
345
		// Strip HTML
346
		$value = strip_tags( $value );
347
348
		// Strip shortcodes
349
		$value = strip_shortcodes( $value );
350
351
		return $value;
352
	}
353
354
	/**
355
	 * Passes any unlinked URLs that are on their own line to {@link WP_Embed::shortcode()} for potential embedding.
356
	 *
357
	 * @see WP_Embed::autoembed()
358
	 * @see WP_Embed::autoembed_callback()
359
	 * 
360
	 * @uses PodsField_OEmbed::autoembed_callback()
361
	 *
362
	 * @param string $content The content to be searched.
363
	 * @return string Potentially modified $content.
364
	 * 
365
	 * @since 2.7
366
	 */
367
	public function autoembed( $content ) {
368
369
		// Replace line breaks from all HTML elements with placeholders.
370
		$content = wp_replace_in_html_tags( $content, array( "\n" => '<!-- wp-line-break -->' ) );
371
372
		// Find URLs that are on their own line.
373
		$content = preg_replace_callback( '|^(\s*)(https?://[^\s"]+)(\s*)$|im', array( $this, 'autoembed_callback' ), $content );
374
375
		// Put the line breaks back.
376
		return str_replace( '<!-- wp-line-break -->', "\n", $content );
377
378
	}
379
380
	/**
381
	 * Callback function for {@link WP_Embed::autoembed()}.
382
	 *
383
	 * @param array $match A regex match array.
384
	 * @return string The embed shortcode
385
	 * 
386
	 * @since 2.7
387
	 */
388
	public function autoembed_callback( $match ) {
389
		
390
		$shortcode = '[embed width="' . $this->width . '" height="' . $this->height . '"]' . $match[2] . '[/embed]';
391
		
392
		return $shortcode;
393
394
	}
395
396
	/**
397
	 * Get a list of available providers from the WP_oEmbed class
398
	 *
399
	 * @see wp-includes/class-oembed.php
400
	 * @return array $providers {
401
	 *     Array of provider data with regex as key
402
	 * 
403
	 *     @type string URL for this provider
404
	 *     @type int
405
	 *     @type string Hostname for this provider
406
	 * }
407
	 * 
408
	 * @since 2.7
409
	 */
410
	public function get_providers() {
411
412
		// Return class property if already set
413
		if ( ! empty( $this->providers ) ) {
414
			return $this->providers;
415
		}
416
		
417
		if ( file_exists( ABSPATH . WPINC . '/class-oembed.php' ) )
418
			require_once( ABSPATH . WPINC . '/class-oembed.php' );
419
420
		if ( function_exists( '_wp_oembed_get_object' ) ) {
421
			$wp_oembed = _wp_oembed_get_object();
422
			$providers = $wp_oembed->providers;
423
			foreach ( $providers as $key => $provider ) {
424
				$url = parse_url( $provider[0] );
425
				$host = $url['host'];
426
				$tmp = explode('.', $host);
427
				if ( count( $tmp ) == 3 ) {
428
					// Take domain names like .co.uk in consideration
429
					if ( ! in_array( 'co', $tmp ) ) {
430
						unset( $tmp[0] );
431
					}
432
				} elseif ( count( $tmp ) == 4 ) {
433
					// Take domain names like .co.uk in consideration
434
					unset( $tmp[0] );
435
				}
436
				$host = implode( '.', $tmp );
437
				$providers[ $key ]['host'] = $host;
438
			}
439
			$this->providers = $providers;
440
			return $providers;
441
		}
442
		
443
		// Return an empty array if no providers could be found
444
		return array();
445
446
	}
447
448
	/**
449
	 * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one.
450
	 * This function is ripped from WP since Pods has support from 3.8 and in the WP core this function is 4.0+
451
	 * We've stripped the autodiscover part from this function to keep it basic
452
	 *
453
	 * @since 2.7
454
	 * @access public
455
	 *
456
	 * @see WP_oEmbed::get_provider()
457
	 *
458
	 * @param string        $url  The URL to the content.
459
	 * @return false|string False on failure, otherwise the oEmbed provider URL.
460
	 */
461
	public function get_provider( $url ) {
462
463
		$provider = false;
464
465
		foreach ( $this->providers as $matchmask => $data ) {
466
			if ( isset( $data['host'] ) ) {
467
				unset( $data['host'] );
468
			}
469
			reset( $data );
470
471
			list( $providerurl, $regex ) = $data;
472
473
			$match = $matchmask;
474
475
			// Turn the asterisk-type provider URLs into regex
476
			if ( !$regex ) {
477
				$matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
478
				$matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
479
			}
480
481
			if ( preg_match( $matchmask, $url ) ) {
482
				//$provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
483
				$provider = $match;
484
				break;
485
			}
486
		}
487
488
		return $provider;
489
	}
490
491
	/**
492
	 * Validate a value with the enabled oEmbed providers (if required)
493
	 * 
494
	 * @since 2.7
495
	 * @param string $value
496
	 * @param array $options Field options
497
	 * @return bool
498
	 */
499
	public function validate_provider( $value, $options ) {
500
501
		// Do we even need to validate?
502
		if ( false == (int) pods_v( self::$type . '_restrict_providers', $options ) ) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing (int) pods_v(self::$type...t_providers', $options) of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
503
			return true;
504
		}
505
506
		$providers = $this->get_providers();
507
508
		// Filter existing providers
509
		foreach( $providers as $key => $provider ) {
510
			$fieldname = self::$type . '_enabled_providers_' . tag_escape( $provider['host'] );
511
512
			/**
513
			 * @todo Future compat to enable serialised strings as field options
514
			 */
515
			/*if ( empty( $options[ self::$type . '_enabled_providers_' ][ $provider['host'] ] ) ) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
516
				unset( $providers[ $key ] );
517
			} else*/
518
519
			/**
520
			 * Current solution: all separate field options
521
			 */
522
			if ( empty( $options[ $fieldname ] ) ) {
523
				unset( $providers[ $key ] );
524
			}
525
		}
526
527
		// Value validation
528
		$provider_match = $this->get_provider( $value );
529
		foreach ( $providers as $match => $provider ) {
530
			if ( $provider_match == $match ) {
531
				return true;
532
			}
533
		}
534
		return false;
535
	}
536
537
	/**
538
	 * Handle update preview AJAX
539
	 *
540
	 * @since 2.7
541
	 */
542
	public function admin_ajax_oembed_update_preview() {
543
544
		// Sanitize input
545
		$params = pods_unslash( (array) $_POST );
546
547
		if (   ! empty( $params['_nonce_pods_oembed'] ) 
548
			&& ! empty( $params['pods_field_oembed_value'] ) 
549
			&& wp_verify_nonce( $params['_nonce_pods_oembed'], 'pods_field_oembed_preview' ) 
550
		) {
551
			$value = $this->strip_html( $params['pods_field_oembed_value'] );
552
			$name = ( ! empty( $params['pods_field_oembed_name'] ) ) ? $this->strip_html( $params['pods_field_oembed_name'] ) : '';
553
			$options = ( ! empty( $params['pods_field_oembed_options'] ) ) ? $params['pods_field_oembed_options'] : array();
554
555
			// Load the field to get it's options
556
			$options = pods_api()->load_field( (object) $options );
557
558
			// Field options are stored here, if not, just stay with the full options array
559
			if ( ! empty( $options['options'] ) ) {
560
				$options = $options['options'];
561
			}
562
563
			// Run display function to run oEmbed
564
			$value = $this->display( $value, $name, $options );
565
566
			if ( empty( $value ) ) {
567
				$value = __( 'Please choose a valid oEmbed URL.', 'pods' );
568
				wp_send_json_error( $value );
569
			} else {
570
				wp_send_json_success( $value );
571
			}
572
		}
573
		wp_send_json_error( __( 'Unauthorized request', 'pods' ) );
574
575
		die(); // Kill it!
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_ajax_oembed_update_preview() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
576
	}
577
578
}
579