Completed
Pull Request — 2.x (#3632)
by Scott Kingsley
18:53 queued 11:35
created

admin_ajax_oembed_update_preview()   C

Complexity

Conditions 8
Paths 17

Size

Total Lines 35
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 19
nc 17
nop 0
dl 0
loc 35
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
	}
70
71
	/**
72
	 * Add admin_init actions
73
	 *
74
	 * @since 2.3
75
	 */
76
	public function admin_init() {
77
		// AJAX for Uploads
78
		add_action( 'wp_ajax_oembed_update_preview', array( $this, 'admin_ajax_oembed_update_preview' ) );
79
	}
80
81
	/**
82
	 * Add options and set defaults to
83
	 *
84
	 * @return array
85
	 * @since 2.0
86
	 */
87
	public function options () {
88
89
		$options = array(
90
			self::$type . '_repeatable' => array(
91
				'label' => __( 'Repeatable Field', 'pods' ),
92
				'default' => 0,
93
				'type' => 'boolean',
94
				'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' ),
95
				'boolean_yes_label' => '',
96
				'dependency' => true,
97
				'developer_mode' => true
98
			),
99
			self::$type . '_width' => array(
100
				'label' => __( 'Embed Width', 'pods' ),
101
				'default' => 0,
102
				'type' => 'number',
103
				'help' => __( 'Optional width to use for this oEmbed. Leave as 0 (zero) to default to none.', 'pods' )
104
			),
105
			self::$type . '_height' => array(
106
				'label' => __( 'Embed Height', 'pods' ),
107
				'default' => 0,
108
				'type' => 'number',
109
				'help' => __( 'Optional height to use for this oEmbed. Leave as 0 (zero) to default to none.', 'pods' )
110
			),
111
			self::$type . '_show_preview' => array(
112
				'label' => __( 'Show preview', 'pods' ),
113
				'default' => 0,
114
				'type' => 'boolean'
115
			),
116
		);
117
118
		// Get all unique provider host names
119
		$unique_providers = array();
120
		foreach ( $this->get_providers() as $provider ) {
121
			if ( ! in_array( $provider['host'], $unique_providers ) )
122
				$unique_providers[] = $provider['host'];
123
		}
124
		sort($unique_providers);
125
126
		// Only add the options if we have data
127
		if ( ! empty( $unique_providers ) ) {
128
			$options[ self::$type . '_restrict_providers' ] = array(
129
				'label' => __( 'Restrict to providers', 'pods' ),
130
				'help' => __( 'Restrict input to specific WordPress oEmbed compatible providers.', 'pods' ),
131
				'type' => 'boolean',
132
				'default' => 0,
133
				'dependency' => true,
134
			);
135
			$options[ self::$type . '_enable_providers' ] = array(
136
				'label' => __( 'Select enabled providers', 'pods' ),
137
				'depends-on' => array( self::$type . '_restrict_providers' => true ),
138
				'group' => array()
139
			);
140
			// Add all the oEmbed providers
141
			foreach( $unique_providers as $provider ) {
142
				$options[ self::$type . '_enable_providers' ]['group'][ self::$type . '_enabled_providers_' . tag_escape( $provider ) ] = array(
143
					'label' => $provider,
144
					'type' => 'boolean',
145
					'default' => 0,
146
				);
147
			}
148
		}
149
150
151
		return $options;
152
	}
153
154
	/**
155
	 * Define the current field's schema for DB table storage
156
	 *
157
	 * @param array $options
158
	 *
159
	 * @return array
160
	 * @since 2.0
161
	 */
162
	public function schema ( $options = null ) {
163
		$schema = 'LONGTEXT';
164
165
		return $schema;
166
	}
167
168
	/**
169
	 * Change the way the value of the field is displayed with Pods::get
170
	 *
171
	 * @param mixed $value
172
	 * @param string $name
173
	 * @param array $options
174
	 * @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...
175
	 * @param array $pod
176
	 * @param int $id
177
	 *
178
	 * @since 2.0
179
	 */
180
	public function display ( $value = null, $name = null, $options = null, $pod = null, $id = null ) {
181
		$value = $this->pre_save( $value, $id, $name, $options, null, $pod );
182
183
		$width = (int) pods_v( self::$type . '_width', $options );
184
		$height = (int) pods_v( self::$type . '_height', $options );
185
		$args = array();
186
		if ( $width > 0 ) {
187
			$args['width'] = $width;
188
		}
189
		if ( $height > 0 ) {
190
			$args['height'] = $height;
191
		}
192
193
		$value = wp_oembed_get( $value, $args );
194
195
		/**
196
		 * @var $embed WP_Embed
197
		 */
198
		/*$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...
199
200
		if ( 0 < $width && 0 < $height ) {
201
			$this->width = $width;
202
			$this->height = $height;
203
			
204
			// Setup [embed] shortcodes with set width/height
205
			$value = $this->autoembed( $value );
206
			
207
			// Run [embed] shortcodes
208
			$value = $embed->run_shortcode( $value );
209
		} else {
210
			// Autoembed URL normally
211
			$value = $embed->autoembed( $value );
212
		}*/
213
		return $value;
214
	}
215
216
	/**
217
	 * Customize output of the form field
218
	 *
219
	 * @param string $name
220
	 * @param mixed $value
221
	 * @param array $options
222
	 * @param array $pod
223
	 * @param int $id
224
	 *
225
	 * @since 2.0
226
	 */
227 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...
228
		$options = (array) $options;
229
		$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...
230
231
		if ( is_array( $value ) )
232
			$value = implode( ' ', $value );
233
234
		if ( isset( $options[ 'name' ] ) && false === PodsForm::permission( self::$type, $options[ 'name' ], $options, null, $pod, $id ) ) {
235
			if ( pods_var( 'read_only', $options, false ) )
236
				$options[ 'readonly' ] = true;
237
			else
238
				return;
239
		}
240
		elseif ( !pods_has_permissions( $options ) && pods_var( 'read_only', $options, false ) )
241
			$options[ 'readonly' ] = true;
242
243
		pods_view( PODS_DIR . 'ui/fields/oembed.php', compact( array_keys( get_defined_vars() ) ) );
244
	}
245
246
	/**
247
	 * Validate a value before it's saved
248
	 *
249
	 * @param mixed $value
250
	 * @param string $name
251
	 * @param array $options
252
	 * @param array $fields
253
	 * @param array $pod
254
	 * @param int $id
255
	 *
256
	 * @param null $params
257
	 * @return array|bool
258
	 * @since 2.0
259
	 */
260
	public function validate ( $value, $name = null, $options = null, $fields = null, $pod = null, $id = null, $params = null ) {
261
		$errors = array();
262
263
		$check = $this->pre_save( $value, $id, $name, $options, $fields, $pod, $params );
264
265
		if ( is_array( $check ) )
266
			$errors = $check;
267
		else {
268
			if ( 0 < strlen( $value ) && strlen( $check ) < 1 ) {
269
				if ( 1 == (bool) pods_v( 'required', $options ) )
270
					$errors[] = __( 'This field is required.', 'pods' );
271
			}
272
		}
273
274
		if ( !empty( $errors ) )
275
			return $errors;
276
277
		return true;
278
	}
279
280
	/**
281
	 * Change the value or perform actions after validation but before saving to the DB
282
	 *
283
	 * @param mixed $value
284
	 * @param int $id
285
	 * @param string $name
286
	 * @param array $options
287
	 * @param array $fields
288
	 * @param array $pod
289
	 * @param object $params
290
	 *
291
	 * @return mixed|string
292
	 * @since 2.0
293
	 */
294
	public function pre_save ( $value, $id = null, $name = null, $options = null, $fields = null, $pod = null, $params = null ) {
295
		$value = $this->strip_html( $value, $options );
296
297
		// Only allow ONE URL
298
		if ( ! empty( $value ) ) {
299
			$value = explode( ' ', $value );
300
			$value = esc_url( $value[0] );
301
		}
302
303
		if ( $this->validate_provider( $value, $options ) ) {
304
			return $value;
305
		} else {
306
			return false;
307
		}
308
309
	}
310
311
	/**
312
	 * Customize the Pods UI manage table column output
313
	 *
314
	 * @param int $id
315
	 * @param mixed $value
316
	 * @param string $name
317
	 * @param array $options
318
	 * @param array $fields
319
	 * @param array $pod
320
	 *
321
	 * @return mixed|string
322
	 * @since 2.0
323
	 */
324
	public function ui ( $id, $value, $name = null, $options = null, $fields = null, $pod = null ) {
325
		$value = $this->pre_save( $value, $id, $name, $options, $fields, $pod );
326
327
		return $value;
328
	}
329
330
	/**
331
	 * Strip HTML based on options
332
	 *
333
	 * @param string $value
334
	 * @param array $options
335
	 *
336
	 * @return string
337
	 */
338
	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...
339
		if ( is_array( $value ) )
340
			$value = @implode( ' ', $value );
341
342
		$value = trim( $value );
343
344
		// Strip HTML
345
		$value = strip_tags( $value );
346
347
		// Strip shortcodes
348
		$value = strip_shortcodes( $value );
349
350
		return $value;
351
	}
352
353
	/**
354
	 * Passes any unlinked URLs that are on their own line to {@link WP_Embed::shortcode()} for potential embedding.
355
	 *
356
	 * @see WP_Embed::autoembed()
357
	 * @see WP_Embed::autoembed_callback()
358
	 * 
359
	 * @uses PodsField_OEmbed::autoembed_callback()
360
	 *
361
	 * @param string $content The content to be searched.
362
	 * @return string Potentially modified $content.
363
	 * 
364
	 * @since 2.7
365
	 */
366
	public function autoembed( $content ) {
367
368
		// Replace line breaks from all HTML elements with placeholders.
369
		$content = wp_replace_in_html_tags( $content, array( "\n" => '<!-- wp-line-break -->' ) );
370
371
		// Find URLs that are on their own line.
372
		$content = preg_replace_callback( '|^(\s*)(https?://[^\s"]+)(\s*)$|im', array( $this, 'autoembed_callback' ), $content );
373
374
		// Put the line breaks back.
375
		return str_replace( '<!-- wp-line-break -->', "\n", $content );
376
377
	}
378
379
	/**
380
	 * Callback function for {@link WP_Embed::autoembed()}.
381
	 *
382
	 * @param array $match A regex match array.
383
	 * @return string The embed shortcode
384
	 * 
385
	 * @since 2.7
386
	 */
387
	public function autoembed_callback( $match ) {
388
		
389
		$shortcode = '[embed width="' . $this->width . '" height="' . $this->height . '"]' . $match[2] . '[/embed]';
390
		
391
		return $shortcode;
392
393
	}
394
395
	/**
396
	 * Get a list of available providers from the WP_oEmbed class
397
	 *
398
	 * @see wp-includes/class-oembed.php
399
	 * @return array $providers {
400
	 *     Array of provider data with regex as key
401
	 * 
402
	 *     @type string URL for this provider
403
	 *     @type int
404
	 *     @type string Hostname for this provider
405
	 * }
406
	 * 
407
	 * @since 2.7
408
	 */
409
	public function get_providers() {
410
411
		// Return class property if already set
412
		if ( ! empty( $this->providers ) ) {
413
			return $this->providers;
414
		}
415
		
416
		if ( file_exists( ABSPATH . WPINC . '/class-oembed.php' ) )
417
			require_once( ABSPATH . WPINC . '/class-oembed.php' );
418
419
		if ( function_exists( '_wp_oembed_get_object' ) ) {
420
			$wp_oembed = _wp_oembed_get_object();
421
			$providers = $wp_oembed->providers;
422
			foreach ( $providers as $key => $provider ) {
423
				$url = parse_url( $provider[0] );
424
				$host = $url['host'];
425
				$tmp = explode('.', $host);
426
				if ( count( $tmp ) == 3 ) {
427
					// Take domain names like .co.uk in consideration
428
					if ( ! in_array( 'co', $tmp ) ) {
429
						unset( $tmp[0] );
430
					}
431
				} elseif ( count( $tmp ) == 4 ) {
432
					// Take domain names like .co.uk in consideration
433
					unset( $tmp[0] );
434
				}
435
				$host = implode( '.', $tmp );
436
				$providers[ $key ]['host'] = $host;
437
			}
438
			$this->providers = $providers;
439
			return $providers;
440
		}
441
		
442
		// Return an empty array if no providers could be found
443
		return array();
444
445
	}
446
447
	/**
448
	 * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one.
449
	 * This function is ripped from WP since Pods has support from 3.8 and in the WP core this function is 4.0+
450
	 * We've stripped the autodiscover part from this function to keep it basic
451
	 *
452
	 * @since 2.7
453
	 * @access public
454
	 *
455
	 * @see WP_oEmbed::get_provider()
456
	 *
457
	 * @param string        $url  The URL to the content.
458
	 * @return false|string False on failure, otherwise the oEmbed provider URL.
459
	 */
460
	public function get_provider( $url ) {
461
462
		$provider = false;
463
464
		foreach ( $this->providers as $matchmask => $data ) {
465
			if ( isset( $data['host'] ) ) {
466
				unset( $data['host'] );
467
			}
468
			reset( $data );
469
470
			list( $providerurl, $regex ) = $data;
471
472
			$match = $matchmask;
473
474
			// Turn the asterisk-type provider URLs into regex
475
			if ( !$regex ) {
476
				$matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
477
				$matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
478
			}
479
480
			if ( preg_match( $matchmask, $url ) ) {
481
				//$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...
482
				$provider = $match;
483
				break;
484
			}
485
		}
486
487
		return $provider;
488
	}
489
490
	/**
491
	 * Validate a value with the enabled oEmbed providers (if required)
492
	 * 
493
	 * @since 2.7
494
	 * @param string $value
495
	 * @param array $options Field options
496
	 * @return bool
497
	 */
498
	public function validate_provider( $value, $options ) {
499
500
		// Do we even need to validate?
501
		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...
502
			return true;
503
		}
504
505
		$providers = $this->get_providers();
506
507
		// Filter existing providers
508
		foreach( $providers as $key => $provider ) {
509
			$fieldname = self::$type . '_enabled_providers_' . tag_escape( $provider['host'] );
510
511
			/**
512
			 * @todo Future compat to enable serialised strings as field options
513
			 */
514
			/*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...
515
				unset( $providers[ $key ] );
516
			} else*/
517
518
			/**
519
			 * Current solution: all separate field options
520
			 */
521
			if ( empty( $options[ $fieldname ] ) ) {
522
				unset( $providers[ $key ] );
523
			}
524
		}
525
526
		// Value validation
527
		$provider_match = $this->get_provider( $value );
528
		foreach ( $providers as $match => $provider ) {
529
			if ( $provider_match == $match ) {
530
				return true;
531
			}
532
		}
533
		return false;
534
	}
535
536
	/**
537
	 * Handle update preview AJAX
538
	 *
539
	 * @since 2.7
540
	 */
541
	public function admin_ajax_oembed_update_preview() {
542
543
		// Sanitize input
544
		$params = pods_unslash( (array) $_POST );
545
546
		if (   ! empty( $params['_nonce_pods_oembed'] ) 
547
			&& ! empty( $params['pods_field_oembed_value'] ) 
548
			&& wp_verify_nonce( $params['_nonce_pods_oembed'], 'pods_field_oembed_preview' ) 
549
		) {
550
			$value = $this->strip_html( $params['pods_field_oembed_value'] );
551
			$name = ( ! empty( $params['pods_field_oembed_name'] ) ) ? $this->strip_html( $params['pods_field_oembed_name'] ) : '';
552
			$options = ( ! empty( $params['pods_field_oembed_options'] ) ) ? $params['pods_field_oembed_options'] : array();
553
554
			// Load the field to get it's options
555
			$options = pods_api()->load_field( (object) $options );
556
557
			// Field options are stored here, if not, just stay with the full options array
558
			if ( ! empty( $options['options'] ) ) {
559
				$options = $options['options'];
560
			}
561
562
			// Run display function to run oEmbed
563
			$value = $this->display( $value, $name, $options );
564
565
			if ( empty( $value ) ) {
566
				$value = __( 'Please choose a valid oEmbed URL.', 'pods' );
567
				wp_send_json_error( $value );
568
			} else {
569
				wp_send_json_success( $value );
570
			}
571
		}
572
		wp_send_json_error( __( 'Unauthorized request', 'pods' ) );
573
574
		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...
575
	}
576
577
}
578