Completed
Push — fix/infinite-scroll-post-injec... ( e8bbbf...ccd363 )
by
unknown
30:43 queued 23:06
created

Jetpack_Recipes   F

Complexity

Total Complexity 75

Size/Duplication

Total Lines 666
Duplicated Lines 21.02 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 140
loc 666
rs 2.334
c 0
b 0
f 0
wmc 75
lcom 2
cbo 2

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A kses_tags() 0 22 4
A action_init() 0 12 1
B add_scripts() 6 50 7
A recipe_shortcode() 0 22 1
F recipe_shortcode_html() 24 122 21
A recipe_image_shortcode() 0 15 2
A recipe_notes_shortcode() 29 29 2
A recipe_ingredients_shortcode() 27 27 2
A recipe_nutrition_shortcode() 27 27 2
C output_list_content() 0 77 16
A recipe_directions_shortcode() 27 27 2
A output_time() 0 34 4
B output_image_html() 0 51 8
A themecolor_styles() 0 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Recipes often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Recipes, and based on these observations, apply Extract Interface, too.

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
		add_action( 'init', array( $this, 'action_init' ) );
34
	}
35
36
	/**
37
	 * Returns KSES tags with Schema-specific attributes.
38
	 *
39
	 * @since 8.0.0
40
	 *
41
	 * @return array Array to be used by KSES.
42
	 */
43
	private static function kses_tags() {
44
		$allowedtags = wp_kses_allowed_html( 'post' );
45
		// Create an array of all the tags we'd like to add the itemprop attribute to.
46
		$tags = array( 'li', 'ol', 'ul', 'img', 'p', 'h3', 'time', 'span' );
47
		foreach ( $tags as $tag ) {
48
			if ( ! isset( $allowedtags[ $tag ] ) ) {
49
				$allowedtags[ $tag ] = array();
50
			}
51
			$allowedtags[ $tag ]['class']    = array();
52
			$allowedtags[ $tag ]['itemprop'] = array();
53
			$allowedtags[ $tag ]['datetime'] = array();
54
		}
55
56
		// Allow itemscope and itemtype for divs.
57
		if ( ! isset( $allowedtags['div'] ) ) {
58
			$allowedtags['div'] = array();
59
		}
60
		$allowedtags['div']['class']     = array();
61
		$allowedtags['div']['itemscope'] = array();
62
		$allowedtags['div']['itemtype']  = array();
63
		return $allowedtags;
64
	}
65
66
	/**
67
	 * Register our shortcode and enqueue necessary files.
68
	 */
69
	public function action_init() {
70
		// Enqueue styles if [recipe] exists.
71
		add_action( 'wp_head', array( $this, 'add_scripts' ), 1 );
72
73
		// Render [recipe], along with other shortcodes that can be nested within.
74
		add_shortcode( 'recipe', array( $this, 'recipe_shortcode' ) );
75
		add_shortcode( 'recipe-notes', array( $this, 'recipe_notes_shortcode' ) );
76
		add_shortcode( 'recipe-ingredients', array( $this, 'recipe_ingredients_shortcode' ) );
77
		add_shortcode( 'recipe-directions', array( $this, 'recipe_directions_shortcode' ) );
78
		add_shortcode( 'recipe-nutrition', array( $this, 'recipe_nutrition_shortcode' ) );
79
		add_shortcode( 'recipe-image', array( $this, 'recipe_image_shortcode' ) );
80
	}
81
82
	/**
83
	 * Enqueue scripts and styles
84
	 */
85
	public function add_scripts() {
86
		if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) {
87
			return;
88
		}
89
90 View Code Duplication
		foreach ( $GLOBALS['posts'] as $p ) {
91
			if ( has_shortcode( $p->post_content, 'recipe' ) ) {
92
				$this->scripts_and_style_included = true;
93
				break;
94
			}
95
		}
96
97
		if ( ! $this->scripts_and_style_included ) {
98
			return;
99
		}
100
101
		wp_enqueue_style( 'jetpack-recipes-style', plugins_url( '/css/recipes.css', __FILE__ ), array(), '20130919' );
102
		wp_style_add_data( 'jetpack-recipes-style', 'rtl', 'replace' );
103
104
		// add $themecolors-defined styles.
105
		wp_add_inline_style( 'jetpack-recipes-style', self::themecolor_styles() );
106
		wp_enqueue_script(
107
			'jetpack-recipes-printthis',
108
			Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes-printthis.min.js', 'modules/shortcodes/js/recipes-printthis.js' ),
109
			array( 'jquery' ),
110
			'20170202',
111
			false
112
		);
113
114
		wp_enqueue_script(
115
			'jetpack-recipes-js',
116
			Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes.min.js', 'modules/shortcodes/js/recipes.js' ),
117
			array( 'jquery', 'jetpack-recipes-printthis' ),
118
			'20131230',
119
			false
120
		);
121
122
		$title_var     = wp_title( '|', false, 'right' );
123
		$rtl           = is_rtl() ? '-rtl' : '';
124
		$print_css_var = plugins_url( "/css/recipes-print{$rtl}.css", __FILE__ );
125
126
		wp_localize_script(
127
			'jetpack-recipes-js',
128
			'jetpack_recipes_vars',
129
			array(
130
				'pageTitle' => $title_var,
131
				'loadCSS'   => $print_css_var,
132
			)
133
		);
134
	}
135
136
	/**
137
	 * Our [recipe] shortcode.
138
	 * Prints recipe data styled to look good on *any* theme.
139
	 *
140
	 * @param array  $atts    Array of shortcode attributes.
141
	 * @param string $content Post content.
142
	 *
143
	 * @return string HTML for recipe shortcode.
144
	 */
145
	public static function recipe_shortcode( $atts, $content = '' ) {
146
		$atts = shortcode_atts(
147
			array(
148
				'title'       => '', // string.
149
				'servings'    => '', // intval.
150
				'time'        => '', // strtotime-compatible time description.
151
				'difficulty'  => '', // string.
152
				'print'       => '', // URL for external print version.
153
				'source'      => '', // string.
154
				'sourceurl'   => '', // URL string. Only used if source set.
155
				'image'       => '', // URL or attachment ID.
156
				'description' => '', // string.
157
				'cooktime'    => '', // strtotime-compatible time description.
158
				'preptime'    => '', // strtotime-compatible time description.
159
				'rating'      => '', // string.
160
			),
161
			$atts,
162
			'recipe'
163
		);
164
165
		return self::recipe_shortcode_html( $atts, $content );
166
	}
167
168
	/**
169
	 * The recipe output
170
	 *
171
	 * @param array  $atts    Array of shortcode attributes.
172
	 * @param string $content Post content.
173
	 *
174
	 * @return string HTML output
175
	 */
176
	private static function recipe_shortcode_html( $atts, $content = '' ) {
177
178
		$html = '<div class="hrecipe h-recipe jetpack-recipe" itemscope itemtype="https://schema.org/Recipe">';
179
180
		// Print the recipe title if exists.
181
		if ( '' !== $atts['title'] ) {
182
			$html .= '<h3 class="p-name jetpack-recipe-title fn" itemprop="name">' . esc_html( $atts['title'] ) . '</h3>';
183
		}
184
185
		// Print the recipe meta if exists.
186
		if (
187
			'' !== $atts['servings']
188
			|| '' !== $atts['time']
189
			|| '' !== $atts['difficulty']
190
			|| '' !== $atts['print']
191
			|| '' !== $atts['preptime']
192
			|| '' !== $atts['cooktime']
193
			|| '' !== $atts['rating']
194
		) {
195
			$html .= '<ul class="jetpack-recipe-meta">';
196
197 View Code Duplication
			if ( '' !== $atts['servings'] ) {
198
				$html .= sprintf(
199
					'<li class="jetpack-recipe-servings p-yield yield" itemprop="recipeYield"><strong>%1$s: </strong>%2$s</li>',
200
					esc_html_x( 'Servings', 'recipe', 'jetpack' ),
201
					esc_html( $atts['servings'] )
202
				);
203
			}
204
205
			$time_types = array( 'cooktime', 'preptime', 'time' );
206
			foreach ( $time_types as $time_type ) {
207
				if ( '' === $atts[ $time_type ] ) {
208
					continue;
209
				}
210
				$html .= self::output_time( $atts[ $time_type ], $time_type );
211
			}
212
213 View Code Duplication
			if ( '' !== $atts['difficulty'] ) {
214
				$html .= sprintf(
215
					'<li class="jetpack-recipe-difficulty"><strong>%1$s: </strong>%2$s</li>',
216
					esc_html_x( 'Difficulty', 'recipe', 'jetpack' ),
217
					esc_html( $atts['difficulty'] )
218
				);
219
			}
220
221 View Code Duplication
			if ( '' !== $atts['rating'] ) {
222
				$html .= sprintf(
223
					'<li class="jetpack-recipe-rating">
224
						<strong>%1$s: </strong>
225
						<span itemprop="contentRating">%2$s</span>
226
					</li>',
227
					esc_html_x( 'Rating', 'recipe', 'jetpack' ),
228
					esc_html( $atts['rating'] )
229
				);
230
			}
231
232
			if ( '' !== $atts['source'] ) {
233
				$html .= sprintf(
234
					'<li class="jetpack-recipe-source"><strong>%1$s: </strong>',
235
					esc_html_x( 'Source', 'recipe', 'jetpack' )
236
				);
237
238
				if ( '' !== $atts['sourceurl'] ) :
239
					// Show the link if we have one.
240
					$html .= sprintf(
241
						'<a href="%2$s">%1$s</a>',
242
						esc_html( $atts['source'] ),
243
						esc_url( $atts['sourceurl'] )
244
					);
245
				else :
246
					// Skip the link.
247
					$html .= sprintf(
248
						'%1$s',
249
						esc_html( $atts['source'] )
250
					);
251
				endif;
252
253
				$html .= '</li>';
254
			}
255
256
			if ( 'false' !== $atts['print'] ) {
257
				$html .= sprintf(
258
					'<li class="jetpack-recipe-print"><a href="#">%1$s</a></li>',
259
					esc_html_x( 'Print', 'recipe', 'jetpack' )
260
				);
261
			}
262
263
			$html .= '</ul>';
264
		}
265
266
		// Output the image if we have one and it's not shown elsewhere.
267
		if ( '' !== $atts['image'] ) {
268
			if ( ! has_shortcode( $content, 'recipe-image' ) ) {
269
				$html .= self::output_image_html( $atts['image'] );
270
			}
271
		}
272
273
		// Output the description, if we have one.
274
		if ( '' !== $atts['description'] ) {
275
			$html .= sprintf(
276
				'<p class="jetpack-recipe-description" itemprop="description">%1$s</p>',
277
				esc_html( $atts['description'] )
278
			);
279
		}
280
281
		// Print content between codes.
282
		$html .= '<div class="jetpack-recipe-content">' . do_shortcode( $content ) . '</div>';
283
284
		// Close it up.
285
		$html .= '</div>';
286
287
		// If there is a recipe within a recipe, remove the shortcode.
288
		if ( has_shortcode( $html, 'recipe' ) ) {
289
			remove_shortcode( 'recipe' );
290
		}
291
292
		// Sanitize html.
293
		$html = wp_kses( $html, self::kses_tags() );
294
295
		// Return the HTML block.
296
		return $html;
297
	}
298
299
	/**
300
	 * Our [recipe-image] shortcode.
301
	 * Controls placement of image in recipe.
302
	 *
303
	 * @param array $atts Array of shortcode attributes.
304
	 *
305
	 * @return string HTML for recipe notes shortcode.
306
	 */
307
	public static function recipe_image_shortcode( $atts ) {
308
		$atts = shortcode_atts(
309
			array(
310
				'image' => '', // string.
311
				0       => '', // string.
312
			),
313
			$atts,
314
			'recipe-image'
315
		);
316
		$src  = $atts['image'];
317
		if ( ! empty( $atts[0] ) ) {
318
			$src = $atts[0];
319
		}
320
		return self::output_image_html( $src );
321
	}
322
323
	/**
324
	 * Our [recipe-notes] shortcode.
325
	 * Outputs ingredients, styled in a div.
326
	 *
327
	 * @param array  $atts    Array of shortcode attributes.
328
	 * @param string $content Post content.
329
	 *
330
	 * @return string HTML for recipe notes shortcode.
331
	 */
332 View Code Duplication
	public static function recipe_notes_shortcode( $atts, $content = '' ) {
333
		$atts = shortcode_atts(
334
			array(
335
				'title' => '', // string.
336
			),
337
			$atts,
338
			'recipe-notes'
339
		);
340
341
		$html = '';
342
343
		// Print a title if one exists.
344
		if ( '' !== $atts['title'] ) {
345
			$html .= '<h4 class="jetpack-recipe-notes-title">' . esc_html( $atts['title'] ) . '</h4>';
346
		}
347
348
		$html .= '<div class="jetpack-recipe-notes">';
349
350
		// Format content using list functionality, if desired.
351
		$html .= self::output_list_content( $content, 'notes' );
352
353
		$html .= '</div>';
354
355
		// Sanitize html.
356
		$html = wp_kses( $html, self::kses_tags() );
357
358
		// Return the HTML block.
359
		return $html;
360
	}
361
362
	/**
363
	 * Our [recipe-ingredients] shortcode.
364
	 * Outputs notes, styled in a div.
365
	 *
366
	 * @param array  $atts    Array of shortcode attributes.
367
	 * @param string $content Post content.
368
	 *
369
	 * @return string HTML for recipe ingredients shortcode.
370
	 */
371 View Code Duplication
	public static function recipe_ingredients_shortcode( $atts, $content = '' ) {
372
		$atts = shortcode_atts(
373
			array(
374
				'title' => esc_html_x( 'Ingredients', 'recipe', 'jetpack' ), // string.
375
			),
376
			$atts,
377
			'recipe-ingredients'
378
		);
379
380
		$html = '<div class="jetpack-recipe-ingredients">';
381
382
		// Print a title unless the user has opted to exclude it.
383
		if ( 'false' !== $atts['title'] ) {
384
			$html .= '<h4 class="jetpack-recipe-ingredients-title">' . esc_html( $atts['title'] ) . '</h4>';
385
		}
386
387
		// Format content using list functionality.
388
		$html .= self::output_list_content( $content, 'ingredients' );
389
390
		$html .= '</div>';
391
392
		// Sanitize html.
393
		$html = wp_kses( $html, self::kses_tags() );
394
395
		// Return the HTML block.
396
		return $html;
397
	}
398
399
	/**
400
	 * Our [recipe-nutrition] shortcode.
401
	 * Outputs notes, styled in a div.
402
	 *
403
	 * @param array  $atts    Array of shortcode attributes.
404
	 * @param string $content Post content.
405
	 *
406
	 * @return string HTML for recipe nutrition shortcode.
407
	 */
408 View Code Duplication
	public static function recipe_nutrition_shortcode( $atts, $content = '' ) {
409
		$atts = shortcode_atts(
410
			array(
411
				'title' => esc_html_x( 'Nutrition', 'recipe', 'jetpack' ), // string.
412
			),
413
			$atts,
414
			'recipe-nutrition'
415
		);
416
417
		$html = '<div class="jetpack-recipe-nutrition p-nutrition nutrition">';
418
419
		// Print a title unless the user has opted to exclude it.
420
		if ( 'false' !== $atts['title'] ) {
421
			$html .= '<h4 class="jetpack-recipe-nutrition-title">' . esc_html( $atts['title'] ) . '</h4>';
422
		}
423
424
		// Format content using list functionality.
425
		$html .= self::output_list_content( $content, 'nutrition' );
426
427
		$html .= '</div>';
428
429
		// Sanitize html.
430
		$html = wp_kses( $html, self::kses_tags() );
431
432
		// Return the HTML block.
433
		return $html;
434
	}
435
436
	/**
437
	 * Reusable function to check for shortened formatting.
438
	 * Basically, users can create lists with the following shorthand:
439
	 * - item one
440
	 * - item two
441
	 * - item three
442
	 * And we'll magically convert it to a list. This has the added benefit
443
	 * of including itemprops for the recipe schema.
444
	 *
445
	 * @param string $content HTML content.
446
	 * @param string $type    Type of list.
447
	 *
448
	 * @return string content formatted as a list item
449
	 */
450
	private static function output_list_content( $content, $type ) {
451
		$html = '';
452
453
		switch ( $type ) {
454
			case 'directions':
455
				$list_item_replacement = '<li class="jetpack-recipe-directions">${1}</li>';
456
				$itemprop              = ' itemprop="recipeInstructions"';
457
				$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...
458
				break;
459
			case 'ingredients':
460
				$list_item_replacement = '<li class="jetpack-recipe-ingredient p-ingredient ingredient" itemprop="recipeIngredient">${1}</li>';
461
				$itemprop              = '';
462
				$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...
463
				break;
464
			case 'nutrition':
465
				$list_item_replacement = '<li class="jetpack-recipe-nutrition">${1}</li>';
466
				$itemprop              = ' itemprop="nutrition"';
467
				$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...
468
				break;
469
			case 'nutrition':
470
				$list_item_replacement = '<li class="jetpack-recipe-nutrition nutrition">${1}</li>';
471
				$itemprop              = ' itemprop="nutrition"';
472
				$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...
473
				break;
474
			default:
475
				$list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>';
476
				$itemprop              = '';
477
				$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...
478
		}
479
480
		// Check to see if the user is trying to use shortened formatting.
481
		if (
482
			strpos( $content, '&#8211;' ) !== false ||
483
			strpos( $content, '&#8212;' ) !== false ||
484
			strpos( $content, '-' ) !== false ||
485
			strpos( $content, '*' ) !== false ||
486
			strpos( $content, '#' ) !== false ||
487
			strpos( $content, '–' ) !== false || // ndash.
488
			strpos( $content, '—' ) !== false || // mdash.
489
			preg_match( '/\d+\.\s/', $content )
490
		) {
491
			// Remove breaks and extra whitespace.
492
			$content = str_replace( "<br />\n", "\n", $content );
493
			$content = trim( $content );
494
495
			$ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\&#8211;|\&#8212;|\*)+\h+(.*)/mi';
496
			$ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi';
497
498
			preg_match_all( $ul_pattern, $content, $ul_matches );
499
			preg_match_all( $ol_pattern, $content, $ol_matches );
500
501
			if ( 0 !== count( $ul_matches[0] ) || 0 !== count( $ol_matches[0] ) ) {
502
503
				if ( 0 !== count( $ol_matches[0] ) ) {
504
					$listtype          = 'ol';
505
					$list_item_pattern = $ol_pattern;
506
				} else {
507
					$listtype          = 'ul';
508
					$list_item_pattern = $ul_pattern;
509
				}
510
				$html .= '<' . $listtype . $itemprop . '>';
511
				$html .= preg_replace( $list_item_pattern, $list_item_replacement, $content );
512
				$html .= '</' . $listtype . '>';
513
514
				// Strip out any empty <p> tags and stray </p> tags, because those are just silly.
515
				$empty_p_pattern = '/(<p>)*\s*<\/p>/mi';
516
				$html            = preg_replace( $empty_p_pattern, '', $html );
517
			} else {
518
				$html .= do_shortcode( $content );
519
			}
520
		} else {
521
			$html .= do_shortcode( $content );
522
		}
523
524
		// Return our formatted content.
525
		return $html;
526
	}
527
528
	/**
529
	 * Our [recipe-directions] shortcode.
530
	 * Outputs directions, styled in a div.
531
	 *
532
	 * @param array  $atts    Array of shortcode attributes.
533
	 * @param string $content Post content.
534
	 *
535
	 * @return string HTML for recipe directions shortcode.
536
	 */
537 View Code Duplication
	public static function recipe_directions_shortcode( $atts, $content = '' ) {
538
		$atts = shortcode_atts(
539
			array(
540
				'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), // string.
541
			),
542
			$atts,
543
			'recipe-directions'
544
		);
545
546
		$html = '<div class="jetpack-recipe-directions e-instructions">';
547
548
		// Print a title unless the user has specified to exclude it.
549
		if ( 'false' !== $atts['title'] ) {
550
			$html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>';
551
		}
552
553
		// Format content using list functionality.
554
		$html .= self::output_list_content( $content, 'directions' );
555
556
		$html .= '</div>';
557
558
		// Sanitize html.
559
		$html = wp_kses( $html, self::kses_tags() );
560
561
		// Return the HTML block.
562
		return $html;
563
	}
564
565
	/**
566
	 * Outputs time meta tag.
567
	 *
568
	 * @param string $time_str  Raw time to output.
569
	 * @param string $time_type Type of time to show.
570
	 *
571
	 * @return string HTML for recipe time meta.
572
	 */
573
	private static function output_time( $time_str, $time_type ) {
574
		// Get a time that's supported by Schema.org.
575
		$duration = WPCOM_JSON_API_Date::format_duration( $time_str );
576
		// If no duration can be calculated, let's output what the user provided.
577
		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...
578
			$duration = $time_str;
579
		}
580
581
		switch ( $time_type ) {
582
			case 'cooktime':
583
				$title    = _x( 'Cook Time', 'recipe', 'jetpack' );
584
				$itemprop = 'cookTime';
585
				break;
586
			case 'preptime':
587
				$title    = _x( 'Prep Time', 'recipe', 'jetpack' );
588
				$itemprop = 'prepTime';
589
				break;
590
			default:
591
				$title    = _x( 'Time', 'recipe', 'jetpack' );
592
				$itemprop = 'totalTime';
593
				break;
594
		}
595
596
		return sprintf(
597
			'<li class="jetpack-recipe-%3$s">
598
				<time itemprop="%4$s" datetime="%5$s"><strong>%1$s:</strong> <span class="%3$s">%2$s</span></time>
599
			</li>',
600
			esc_html( $title ),
601
			esc_html( $time_str ),
602
			esc_attr( $time_type ),
603
			esc_attr( $itemprop ),
604
			esc_attr( $duration )
605
		);
606
	}
607
608
	/**
609
	 * Outputs image tag for recipe.
610
	 *
611
	 * @param string $src The image source.
612
	 *
613
	 * @return string
614
	 */
615
	private static function output_image_html( $src ) {
616
		// Exit if there is no provided source.
617
		if ( ! $src ) {
618
			return '';
619
		}
620
621
		$image_attrs = array(
622
			'class'    => 'jetpack-recipe-image u-photo photo',
623
			'itemprop' => 'image',
624
		);
625
626
		if (
627
			function_exists( 'wp_lazy_loading_enabled' )
628
			&& wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' )
629
		) {
630
			$image_attrs['loading'] = 'lazy';
631
		}
632
633
		// If it's numeric, this may be an attachment.
634
		if ( is_numeric( $src ) ) {
635
			return wp_get_attachment_image(
636
				$src,
637
				'full',
638
				false,
639
				$image_attrs
640
			);
641
		}
642
643
		// Check if it's an absolute or relative URL, and return if not.
644
		if (
645
			0 !== strpos( $src, '/' )
646
			&& false === filter_var( $src, FILTER_VALIDATE_URL )
647
		) {
648
			return '';
649
		}
650
651
		$image_attrs_markup = '';
652
		foreach ( $image_attrs as $name => $value ) {
653
			$image_attrs_markup .= sprintf(
654
				' %1$s="%2$s"',
655
				esc_attr( $name ),
656
				esc_attr( $value )
657
			);
658
		}
659
660
		return sprintf(
661
			'<img%1$s src="%2$s" />',
662
			$image_attrs_markup,
663
			esc_url( $src )
664
		);
665
	}
666
667
	/**
668
	 * Use $themecolors array to style the Recipes shortcode
669
	 *
670
	 * @print style block
671
	 * @return string $style
672
	 */
673
	public function themecolor_styles() {
674
		global $themecolors;
675
		$style = '';
676
677
		if ( isset( $themecolors ) ) {
678
			$style .= '.jetpack-recipe { border-color: #' . esc_attr( $themecolors['border'] ) . '; }';
679
			$style .= '.jetpack-recipe-title { border-bottom-color: #' . esc_attr( $themecolors['link'] ) . '; }';
680
		}
681
682
		return $style;
683
	}
684
685
}
686
687
new Jetpack_Recipes();
688