Completed
Push — fix/unit-tests-init ( a7661a )
by Jeremy
07:42
created

Jetpack_Recipes::recipe_image_shortcode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
1
<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3
use Automattic\Jetpack\Assets;
4
5
/**
6
 * Embed recipe 'cards' in post, with basic styling and print functionality
7
 *
8
 * To Do
9
 * - defaults settings
10
 * - basic styles/themecolor styles
11
 * - validation/sanitization
12
 * - print styles
13
 *
14
 * @package Jetpack
15
 */
16
17
/**
18
 * Register and display Recipes in posts.
19
 */
20
class Jetpack_Recipes {
21
22
	/**
23
	 * Have scripts and styles been enqueued already.
24
	 *
25
	 * @var bool
26
	 */
27
	private $scripts_and_style_included = false;
28
29
	/**
30
	 * Constructor
31
	 */
32
	public function __construct() {
33
		// Enqueue styles if [recipe] exists.
34
		add_action( 'wp_head', array( $this, 'add_scripts' ), 1 );
35
36
		// Render [recipe], along with other shortcodes that can be nested within.
37
		add_shortcode( 'recipe', array( $this, 'recipe_shortcode' ) );
38
		add_shortcode( 'recipe-notes', array( $this, 'recipe_notes_shortcode' ) );
39
		add_shortcode( 'recipe-ingredients', array( $this, 'recipe_ingredients_shortcode' ) );
40
		add_shortcode( 'recipe-directions', array( $this, 'recipe_directions_shortcode' ) );
41
		add_shortcode( 'recipe-nutrition', array( $this, 'recipe_nutrition_shortcode' ) );
42
		add_shortcode( 'recipe-image', array( $this, 'recipe_image_shortcode' ) );
43
	}
44
45
	/**
46
	 * Returns KSES tags with Schema-specific attributes.
47
	 *
48
	 * @since 8.0.0
49
	 *
50
	 * @return array Array to be used by KSES.
51
	 */
52
	private static function kses_tags() {
53
		$allowedtags = wp_kses_allowed_html( 'post' );
54
		// Create an array of all the tags we'd like to add the itemprop attribute to.
55
		$tags = array( 'li', 'ol', 'ul', 'img', 'p', 'h3', 'time', 'span' );
56
		foreach ( $tags as $tag ) {
57
			if ( ! isset( $allowedtags[ $tag ] ) ) {
58
				$allowedtags[ $tag ] = array();
59
			}
60
			$allowedtags[ $tag ]['class']    = array();
61
			$allowedtags[ $tag ]['itemprop'] = array();
62
			$allowedtags[ $tag ]['datetime'] = array();
63
		}
64
65
		// Allow itemscope and itemtype for divs.
66
		if ( ! isset( $allowedtags['div'] ) ) {
67
			$allowedtags['div'] = array();
68
		}
69
		$allowedtags['div']['class']     = array();
70
		$allowedtags['div']['itemscope'] = array();
71
		$allowedtags['div']['itemtype']  = array();
72
		return $allowedtags;
73
	}
74
75
	/**
76
	 * Enqueue scripts and styles
77
	 */
78
	public function add_scripts() {
79
		if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) {
80
			return;
81
		}
82
83 View Code Duplication
		foreach ( $GLOBALS['posts'] as $p ) {
84
			if ( has_shortcode( $p->post_content, 'recipe' ) ) {
85
				$this->scripts_and_style_included = true;
86
				break;
87
			}
88
		}
89
90
		if ( ! $this->scripts_and_style_included ) {
91
			return;
92
		}
93
94
		wp_enqueue_style( 'jetpack-recipes-style', plugins_url( '/css/recipes.css', __FILE__ ), array(), '20130919' );
95
		wp_style_add_data( 'jetpack-recipes-style', 'rtl', 'replace' );
96
97
		// add $themecolors-defined styles.
98
		wp_add_inline_style( 'jetpack-recipes-style', self::themecolor_styles() );
99
		wp_enqueue_script(
100
			'jetpack-recipes-printthis',
101
			Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes-printthis.min.js', 'modules/shortcodes/js/recipes-printthis.js' ),
102
			array( 'jquery' ),
103
			'20170202',
104
			false
105
		);
106
107
		wp_enqueue_script(
108
			'jetpack-recipes-js',
109
			Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes.min.js', 'modules/shortcodes/js/recipes.js' ),
110
			array( 'jquery', 'jetpack-recipes-printthis' ),
111
			'20131230',
112
			false
113
		);
114
115
		$title_var     = wp_title( '|', false, 'right' );
116
		$rtl           = is_rtl() ? '-rtl' : '';
117
		$print_css_var = plugins_url( "/css/recipes-print{$rtl}.css", __FILE__ );
118
119
		wp_localize_script(
120
			'jetpack-recipes-js',
121
			'jetpack_recipes_vars',
122
			array(
123
				'pageTitle' => $title_var,
124
				'loadCSS'   => $print_css_var,
125
			)
126
		);
127
	}
128
129
	/**
130
	 * Our [recipe] shortcode.
131
	 * Prints recipe data styled to look good on *any* theme.
132
	 *
133
	 * @param array  $atts    Array of shortcode attributes.
134
	 * @param string $content Post content.
135
	 *
136
	 * @return string HTML for recipe shortcode.
137
	 */
138
	public static function recipe_shortcode( $atts, $content = '' ) {
139
		$atts = shortcode_atts(
140
			array(
141
				'title'       => '', // string.
142
				'servings'    => '', // intval.
143
				'time'        => '', // strtotime-compatible time description.
144
				'difficulty'  => '', // string.
145
				'print'       => '', // URL for external print version.
146
				'source'      => '', // string.
147
				'sourceurl'   => '', // URL string. Only used if source set.
148
				'image'       => '', // URL or attachment ID.
149
				'description' => '', // string.
150
				'cooktime'    => '', // strtotime-compatible time description.
151
				'preptime'    => '', // strtotime-compatible time description.
152
				'rating'      => '', // string.
153
			),
154
			$atts,
155
			'recipe'
156
		);
157
158
		return self::recipe_shortcode_html( $atts, $content );
159
	}
160
161
	/**
162
	 * The recipe output
163
	 *
164
	 * @param array  $atts    Array of shortcode attributes.
165
	 * @param string $content Post content.
166
	 *
167
	 * @return string HTML output
168
	 */
169
	private static function recipe_shortcode_html( $atts, $content = '' ) {
170
171
		$html = '<div class="hrecipe h-recipe jetpack-recipe" itemscope itemtype="https://schema.org/Recipe">';
172
173
		// Print the recipe title if exists.
174
		if ( '' !== $atts['title'] ) {
175
			$html .= '<h3 class="p-name jetpack-recipe-title fn" itemprop="name">' . esc_html( $atts['title'] ) . '</h3>';
176
		}
177
178
		// Print the recipe meta if exists.
179
		if (
180
			'' !== $atts['servings']
181
			|| '' !== $atts['time']
182
			|| '' !== $atts['difficulty']
183
			|| '' !== $atts['print']
184
			|| '' !== $atts['preptime']
185
			|| '' !== $atts['cooktime']
186
			|| '' !== $atts['rating']
187
		) {
188
			$html .= '<ul class="jetpack-recipe-meta">';
189
190 View Code Duplication
			if ( '' !== $atts['servings'] ) {
191
				$html .= sprintf(
192
					'<li class="jetpack-recipe-servings p-yield yield" itemprop="recipeYield"><strong>%1$s: </strong>%2$s</li>',
193
					esc_html_x( 'Servings', 'recipe', 'jetpack' ),
194
					esc_html( $atts['servings'] )
195
				);
196
			}
197
198
			$time_types = array( 'cooktime', 'preptime', 'time' );
199
			foreach ( $time_types as $time_type ) {
200
				if ( '' === $atts[ $time_type ] ) {
201
					continue;
202
				}
203
				$html .= self::output_time( $atts[ $time_type ], $time_type );
204
			}
205
206 View Code Duplication
			if ( '' !== $atts['difficulty'] ) {
207
				$html .= sprintf(
208
					'<li class="jetpack-recipe-difficulty"><strong>%1$s: </strong>%2$s</li>',
209
					esc_html_x( 'Difficulty', 'recipe', 'jetpack' ),
210
					esc_html( $atts['difficulty'] )
211
				);
212
			}
213
214 View Code Duplication
			if ( '' !== $atts['rating'] ) {
215
				$html .= sprintf(
216
					'<li class="jetpack-recipe-rating">
217
						<strong>%1$s: </strong>
218
						<span itemprop="contentRating">%2$s</span>
219
					</li>',
220
					esc_html_x( 'Rating', 'recipe', 'jetpack' ),
221
					esc_html( $atts['rating'] )
222
				);
223
			}
224
225
			if ( '' !== $atts['source'] ) {
226
				$html .= sprintf(
227
					'<li class="jetpack-recipe-source"><strong>%1$s: </strong>',
228
					esc_html_x( 'Source', 'recipe', 'jetpack' )
229
				);
230
231
				if ( '' !== $atts['sourceurl'] ) :
232
					// Show the link if we have one.
233
					$html .= sprintf(
234
						'<a href="%2$s">%1$s</a>',
235
						esc_html( $atts['source'] ),
236
						esc_url( $atts['sourceurl'] )
237
					);
238
				else :
239
					// Skip the link.
240
					$html .= sprintf(
241
						'%1$s',
242
						esc_html( $atts['source'] )
243
					);
244
				endif;
245
246
				$html .= '</li>';
247
			}
248
249
			if ( 'false' !== $atts['print'] ) {
250
				$html .= sprintf(
251
					'<li class="jetpack-recipe-print"><a href="#">%1$s</a></li>',
252
					esc_html_x( 'Print', 'recipe', 'jetpack' )
253
				);
254
			}
255
256
			$html .= '</ul>';
257
		}
258
259
		// Output the image if we have one and it's not shown elsewhere.
260
		if ( '' !== $atts['image'] ) {
261
			if ( ! has_shortcode( $content, 'recipe-image' ) ) {
262
				$html .= self::output_image_html( $atts['image'] );
263
			}
264
		}
265
266
		// Output the description, if we have one.
267
		if ( '' !== $atts['description'] ) {
268
			$html .= sprintf(
269
				'<p class="jetpack-recipe-description" itemprop="description">%1$s</p>',
270
				esc_html( $atts['description'] )
271
			);
272
		}
273
274
		// Print content between codes.
275
		$html .= '<div class="jetpack-recipe-content">' . do_shortcode( $content ) . '</div>';
276
277
		// Close it up.
278
		$html .= '</div>';
279
280
		// If there is a recipe within a recipe, remove the shortcode.
281
		if ( has_shortcode( $html, 'recipe' ) ) {
282
			remove_shortcode( 'recipe' );
283
		}
284
285
		// Sanitize html.
286
		$html = wp_kses( $html, self::kses_tags() );
287
288
		// Return the HTML block.
289
		return $html;
290
	}
291
292
	/**
293
	 * Our [recipe-image] shortcode.
294
	 * Controls placement of image in recipe.
295
	 *
296
	 * @param array $atts Array of shortcode attributes.
297
	 *
298
	 * @return string HTML for recipe notes shortcode.
299
	 */
300
	public static function recipe_image_shortcode( $atts ) {
301
		$atts = shortcode_atts(
302
			array(
303
				'image' => '', // string.
304
				0       => '', // string.
305
			),
306
			$atts,
307
			'recipe-image'
308
		);
309
		$src  = $atts['image'];
310
		if ( ! empty( $atts[0] ) ) {
311
			$src = $atts[0];
312
		}
313
		return self::output_image_html( $src );
314
	}
315
316
	/**
317
	 * Our [recipe-notes] shortcode.
318
	 * Outputs ingredients, styled in a div.
319
	 *
320
	 * @param array  $atts    Array of shortcode attributes.
321
	 * @param string $content Post content.
322
	 *
323
	 * @return string HTML for recipe notes shortcode.
324
	 */
325 View Code Duplication
	public static function recipe_notes_shortcode( $atts, $content = '' ) {
326
		$atts = shortcode_atts(
327
			array(
328
				'title' => '', // string.
329
			),
330
			$atts,
331
			'recipe-notes'
332
		);
333
334
		$html = '';
335
336
		// Print a title if one exists.
337
		if ( '' !== $atts['title'] ) {
338
			$html .= '<h4 class="jetpack-recipe-notes-title">' . esc_html( $atts['title'] ) . '</h4>';
339
		}
340
341
		$html .= '<div class="jetpack-recipe-notes">';
342
343
		// Format content using list functionality, if desired.
344
		$html .= self::output_list_content( $content, 'notes' );
345
346
		$html .= '</div>';
347
348
		// Sanitize html.
349
		$html = wp_kses( $html, self::kses_tags() );
350
351
		// Return the HTML block.
352
		return $html;
353
	}
354
355
	/**
356
	 * Our [recipe-ingredients] shortcode.
357
	 * Outputs notes, styled in a div.
358
	 *
359
	 * @param array  $atts    Array of shortcode attributes.
360
	 * @param string $content Post content.
361
	 *
362
	 * @return string HTML for recipe ingredients shortcode.
363
	 */
364 View Code Duplication
	public static function recipe_ingredients_shortcode( $atts, $content = '' ) {
365
		$atts = shortcode_atts(
366
			array(
367
				'title' => esc_html_x( 'Ingredients', 'recipe', 'jetpack' ), // string.
368
			),
369
			$atts,
370
			'recipe-ingredients'
371
		);
372
373
		$html = '<div class="jetpack-recipe-ingredients">';
374
375
		// Print a title unless the user has opted to exclude it.
376
		if ( 'false' !== $atts['title'] ) {
377
			$html .= '<h4 class="jetpack-recipe-ingredients-title">' . esc_html( $atts['title'] ) . '</h4>';
378
		}
379
380
		// Format content using list functionality.
381
		$html .= self::output_list_content( $content, 'ingredients' );
382
383
		$html .= '</div>';
384
385
		// Sanitize html.
386
		$html = wp_kses( $html, self::kses_tags() );
387
388
		// Return the HTML block.
389
		return $html;
390
	}
391
392
	/**
393
	 * Our [recipe-nutrition] shortcode.
394
	 * Outputs notes, styled in a div.
395
	 *
396
	 * @param array  $atts    Array of shortcode attributes.
397
	 * @param string $content Post content.
398
	 *
399
	 * @return string HTML for recipe nutrition shortcode.
400
	 */
401 View Code Duplication
	public static function recipe_nutrition_shortcode( $atts, $content = '' ) {
402
		$atts = shortcode_atts(
403
			array(
404
				'title' => esc_html_x( 'Nutrition', 'recipe', 'jetpack' ), // string.
405
			),
406
			$atts,
407
			'recipe-nutrition'
408
		);
409
410
		$html = '<div class="jetpack-recipe-nutrition p-nutrition nutrition">';
411
412
		// Print a title unless the user has opted to exclude it.
413
		if ( 'false' !== $atts['title'] ) {
414
			$html .= '<h4 class="jetpack-recipe-nutrition-title">' . esc_html( $atts['title'] ) . '</h4>';
415
		}
416
417
		// Format content using list functionality.
418
		$html .= self::output_list_content( $content, 'nutrition' );
419
420
		$html .= '</div>';
421
422
		// Sanitize html.
423
		$html = wp_kses( $html, self::kses_tags() );
424
425
		// Return the HTML block.
426
		return $html;
427
	}
428
429
	/**
430
	 * Reusable function to check for shortened formatting.
431
	 * Basically, users can create lists with the following shorthand:
432
	 * - item one
433
	 * - item two
434
	 * - item three
435
	 * And we'll magically convert it to a list. This has the added benefit
436
	 * of including itemprops for the recipe schema.
437
	 *
438
	 * @param string $content HTML content.
439
	 * @param string $type    Type of list.
440
	 *
441
	 * @return string content formatted as a list item
442
	 */
443
	private static function output_list_content( $content, $type ) {
444
		$html = '';
445
446
		switch ( $type ) {
447
			case 'directions':
448
				$list_item_replacement = '<li class="jetpack-recipe-directions">${1}</li>';
449
				$itemprop              = ' itemprop="recipeInstructions"';
450
				$listtype              = 'ol';
0 ignored issues
show
Unused Code introduced by
$listtype is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
451
				break;
452
			case 'ingredients':
453
				$list_item_replacement = '<li class="jetpack-recipe-ingredient p-ingredient ingredient" itemprop="recipeIngredient">${1}</li>';
454
				$itemprop              = '';
455
				$listtype              = 'ul';
0 ignored issues
show
Unused Code introduced by
$listtype is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
456
				break;
457
			case 'nutrition':
458
				$list_item_replacement = '<li class="jetpack-recipe-nutrition">${1}</li>';
459
				$itemprop              = ' itemprop="nutrition"';
460
				$listtype              = 'ul';
0 ignored issues
show
Unused Code introduced by
$listtype is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
461
				break;
462
			case 'nutrition':
463
				$list_item_replacement = '<li class="jetpack-recipe-nutrition nutrition">${1}</li>';
464
				$itemprop              = ' itemprop="nutrition"';
465
				$listtype              = 'ul';
0 ignored issues
show
Unused Code introduced by
$listtype is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
466
				break;
467
			default:
468
				$list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>';
469
				$itemprop              = '';
470
				$listtype              = 'ul';
0 ignored issues
show
Unused Code introduced by
$listtype is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
471
		}
472
473
		// Check to see if the user is trying to use shortened formatting.
474
		if (
475
			strpos( $content, '&#8211;' ) !== false ||
476
			strpos( $content, '&#8212;' ) !== false ||
477
			strpos( $content, '-' ) !== false ||
478
			strpos( $content, '*' ) !== false ||
479
			strpos( $content, '#' ) !== false ||
480
			strpos( $content, '–' ) !== false || // ndash.
481
			strpos( $content, '—' ) !== false || // mdash.
482
			preg_match( '/\d+\.\s/', $content )
483
		) {
484
			// Remove breaks and extra whitespace.
485
			$content = str_replace( "<br />\n", "\n", $content );
486
			$content = trim( $content );
487
488
			$ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\&#8211;|\&#8212;|\*)+\h+(.*)/mi';
489
			$ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi';
490
491
			preg_match_all( $ul_pattern, $content, $ul_matches );
492
			preg_match_all( $ol_pattern, $content, $ol_matches );
493
494
			if ( 0 !== count( $ul_matches[0] ) || 0 !== count( $ol_matches[0] ) ) {
495
496
				if ( 0 !== count( $ol_matches[0] ) ) {
497
					$listtype          = 'ol';
498
					$list_item_pattern = $ol_pattern;
499
				} else {
500
					$listtype          = 'ul';
501
					$list_item_pattern = $ul_pattern;
502
				}
503
				$html .= '<' . $listtype . $itemprop . '>';
504
				$html .= preg_replace( $list_item_pattern, $list_item_replacement, $content );
505
				$html .= '</' . $listtype . '>';
506
507
				// Strip out any empty <p> tags and stray </p> tags, because those are just silly.
508
				$empty_p_pattern = '/(<p>)*\s*<\/p>/mi';
509
				$html            = preg_replace( $empty_p_pattern, '', $html );
510
			} else {
511
				$html .= do_shortcode( $content );
512
			}
513
		} else {
514
			$html .= do_shortcode( $content );
515
		}
516
517
		// Return our formatted content.
518
		return $html;
519
	}
520
521
	/**
522
	 * Our [recipe-directions] shortcode.
523
	 * Outputs directions, styled in a div.
524
	 *
525
	 * @param array  $atts    Array of shortcode attributes.
526
	 * @param string $content Post content.
527
	 *
528
	 * @return string HTML for recipe directions shortcode.
529
	 */
530 View Code Duplication
	public static function recipe_directions_shortcode( $atts, $content = '' ) {
531
		$atts = shortcode_atts(
532
			array(
533
				'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), // string.
534
			),
535
			$atts,
536
			'recipe-directions'
537
		);
538
539
		$html = '<div class="jetpack-recipe-directions e-instructions">';
540
541
		// Print a title unless the user has specified to exclude it.
542
		if ( 'false' !== $atts['title'] ) {
543
			$html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>';
544
		}
545
546
		// Format content using list functionality.
547
		$html .= self::output_list_content( $content, 'directions' );
548
549
		$html .= '</div>';
550
551
		// Sanitize html.
552
		$html = wp_kses( $html, self::kses_tags() );
553
554
		// Return the HTML block.
555
		return $html;
556
	}
557
558
	/**
559
	 * Outputs time meta tag.
560
	 *
561
	 * @param string $time_str  Raw time to output.
562
	 * @param string $time_type Type of time to show.
563
	 *
564
	 * @return string HTML for recipe time meta.
565
	 */
566
	private static function output_time( $time_str, $time_type ) {
567
		// Get a time that's supported by Schema.org.
568
		$duration = WPCOM_JSON_API_Date::format_duration( $time_str );
569
		// If no duration can be calculated, let's output what the user provided.
570
		if ( ! $duration ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $duration of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
571
			$duration = $time_str;
572
		}
573
574
		switch ( $time_type ) {
575
			case 'cooktime':
576
				$title    = _x( 'Cook Time', 'recipe', 'jetpack' );
577
				$itemprop = 'cookTime';
578
				break;
579
			case 'preptime':
580
				$title    = _x( 'Prep Time', 'recipe', 'jetpack' );
581
				$itemprop = 'prepTime';
582
				break;
583
			default:
584
				$title    = _x( 'Time', 'recipe', 'jetpack' );
585
				$itemprop = 'totalTime';
586
				break;
587
		}
588
589
		return sprintf(
590
			'<li class="jetpack-recipe-%3$s">
591
				<time itemprop="%4$s" datetime="%5$s"><strong>%1$s:</strong> <span class="%3$s">%2$s</span></time>
592
			</li>',
593
			esc_html( $title ),
594
			esc_html( $time_str ),
595
			esc_attr( $time_type ),
596
			esc_attr( $itemprop ),
597
			esc_attr( $duration )
598
		);
599
	}
600
601
	/**
602
	 * Outputs image tag for recipe.
603
	 *
604
	 * @param string $src The image source.
605
	 *
606
	 * @return string
607
	 */
608
	private static function output_image_html( $src ) {
609
		// Exit if there is no provided source.
610
		if ( ! $src ) {
611
			return '';
612
		}
613
614
		// If it's numeric, this may be an attachment.
615
		if ( is_numeric( $src ) ) {
616
			return wp_get_attachment_image(
617
				$src,
618
				'full',
619
				false,
620
				array(
621
					'class'    => 'jetpack-recipe-image u-photo photo',
622
					'itemprop' => 'image',
623
				)
624
			);
625
		}
626
627
		// Check if it's an absolute or relative URL, and return if not.
628
		if (
629
			0 !== strpos( $src, '/' )
630
			&& false === filter_var( $src, FILTER_VALIDATE_URL )
631
		) {
632
			return '';
633
		}
634
635
		return sprintf(
636
			'<img class="jetpack-recipe-image u-photo photo" itemprop="image" src="%1$s" />',
637
			esc_url( $src )
638
		);
639
	}
640
641
	/**
642
	 * Use $themecolors array to style the Recipes shortcode
643
	 *
644
	 * @print style block
645
	 * @return string $style
646
	 */
647
	public function themecolor_styles() {
648
		global $themecolors;
649
		$style = '';
650
651
		if ( isset( $themecolors ) ) {
652
			$style .= '.jetpack-recipe { border-color: #' . esc_attr( $themecolors['border'] ) . '; }';
653
			$style .= '.jetpack-recipe-title { border-bottom-color: #' . esc_attr( $themecolors['link'] ) . '; }';
654
		}
655
656
		return $style;
657
	}
658
659
}
660
661
new Jetpack_Recipes();
662