Completed
Push — add/gutenblocks ( 345f65...5e5364 )
by
unknown
16:26 queued 08:25
created

Jetpack_Recipes   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 504
Duplicated Lines 18.06 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 0
Metric Value
dl 91
loc 504
rs 6.8
c 0
b 0
f 0
wmc 55
lcom 2
cbo 1

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
B add_recipes_kses_rules() 0 30 3
A add_kses_rule() 0 12 2
A action_init() 0 10 1
A render_recipe_block() 0 23 2
C add_scripts() 6 38 7
A recipe_shortcode() 0 17 1
F recipe_shortcode_html() 14 114 16
B recipe_notes_shortcode() 25 25 2
A recipe_ingredients_shortcode() 23 23 2
C output_list_content() 0 67 14
A recipe_directions_shortcode() 23 23 2
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
2
3
/**
4
 * Embed recipe 'cards' in post, with basic styling and print functionality
5
 *
6
 * To Do
7
 * - defaults settings
8
 * - basic styles/themecolor styles
9
 * - validation/sanitization
10
 * - print styles
11
 */
12
class Jetpack_Recipes {
13
14
	private $scripts_and_style_included = false;
15
16
	function __construct() {
17
		add_action( 'init', array( $this, 'action_init' ) );
18
19
		add_filter( 'wp_kses_allowed_html', array( $this, 'add_recipes_kses_rules' ), 10, 2 );
20
21
		// Register Gutenberg block jetpack/recipe
22
		add_action( 'enqueue_block_assets', array( $this, 'render_recipe_block' ) );
23
	}
24
25
	/**
26
	 * Add Schema-specific attributes to our allowed tags in wp_kses,
27
	 * so we can have better Schema.org compliance.
28
	 *
29
	 * @param array $allowedtags Array of allowed HTML tags in recipes.
30
	 * @param array $context Context to judge allowed tags by.
31
	 */
32
	function add_recipes_kses_rules( $allowedtags, $context ) {
33
		if ( in_array( $context, array( '', 'post', 'data' ) ) ) :
34
			// Create an array of all the tags we'd like to add the itemprop attribute to.
35
			$tags = array( 'li', 'ol', 'ul', 'img', 'p', 'h3', 'time' );
36
			foreach ( $tags as $tag ) {
37
				$allowedtags = $this->add_kses_rule(
38
					$allowedtags,
39
					$tag,
40
					array(
41
						'class'    => array(),
42
						'itemprop' => array(),
43
						'datetime'  => array(),
44
					)
45
				);
46
			}
47
48
			// Allow itemscope and itemtype for divs.
49
			$allowedtags = $this->add_kses_rule(
50
				$allowedtags,
51
				'div',
52
				array(
53
					'class'    => array(),
54
					'itemscope' => array(),
55
					'itemtype' => array(),
56
				)
57
			);
58
		endif;
59
60
		return $allowedtags;
61
	}
62
63
	/**
64
	 * Function to add a new property rule to our kses array.
65
	 * Used by add_recipe_kses_rules() above.
66
	 *
67
	 * @param array  $all_tags Array of allowed HTML tags in recipes.
68
	 * @param string $tag      New HTML tag to add to the array of allowed HTML.
69
	 * @param array  $rules    Array of allowed attributes for that HTML tag.
70
	 */
71
	private function add_kses_rule( $all_tags, $tag, $rules ) {
72
73
		// If the tag doesn't already exist, add it.
74
		if ( ! isset( $all_tags[ $tag ] ) ) {
75
			$all_tags[ $tag ] = array();
76
		}
77
78
		// Merge the new tags with existing tags.
79
		$all_tags[ $tag ] = array_merge( $all_tags[ $tag ], $rules );
80
81
		return $all_tags;
82
	}
83
84
	/**
85
	 * Register our shortcode and enqueue necessary files.
86
	 */
87
	function action_init() {
88
		// Enqueue styles if [recipe] exists.
89
		add_action( 'wp_head', array( $this, 'add_scripts' ), 1 );
90
91
		// Render [recipe], along with other shortcodes that can be nested within.
92
		add_shortcode( 'recipe', array( $this, 'recipe_shortcode' ) );
93
		add_shortcode( 'recipe-notes', array( $this, 'recipe_notes_shortcode' ) );
94
		add_shortcode( 'recipe-ingredients', array( $this, 'recipe_ingredients_shortcode' ) );
95
		add_shortcode( 'recipe-directions', array( $this, 'recipe_directions_shortcode' ) );
96
	}
97
98
	function render_recipe_block() {
99
		wp_enqueue_style( 'jetpack-recipes-style', plugins_url( '/css/recipes.css', __FILE__ ), array(), '20170917' );
100
		wp_style_add_data( 'jetpack-recipes-style', 'rtl', 'replace' );
101
102
		// add $themecolors-defined styles.
103
		wp_add_inline_style( 'jetpack-recipes-style', self::themecolor_styles() );
104
105
		wp_enqueue_script( 'jetpack-recipes-printthis', plugins_url( '/js/recipes-printthis.js', __FILE__ ), array( 'jquery' ), '20170202' );
106
		wp_enqueue_script( 'jetpack-recipes-js',        plugins_url( '/js/recipes.js', __FILE__ ), array( 'jquery', 'jetpack-recipes-printthis' ), '20131230' );
107
108
		$title_var     = wp_title( '|', false, 'right' );
109
		$rtl           = is_rtl() ? '-rtl' : '';
110
		$print_css_var = plugins_url( "/css/recipes-print{$rtl}.css", __FILE__ );
111
112
		wp_localize_script(
113
			'jetpack-recipes-js',
114
			'jetpack_recipes_vars',
115
			array(
116
				'pageTitle' => $title_var,
117
				'loadCSS' => $print_css_var,
118
			)
119
		);
120
	}
121
122
	/**
123
	 * Enqueue scripts and styles
124
	 */
125
	function add_scripts() {
126
		if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) {
127
			return;
128
		}
129
130 View Code Duplication
		foreach ( $GLOBALS['posts'] as $p ) {
131
			if ( has_shortcode( $p->post_content, 'recipe' ) ) {
132
				$this->scripts_and_style_included = true;
133
				break;
134
			}
135
		}
136
137
		if ( ! $this->scripts_and_style_included ) {
138
			return;
139
		}
140
141
		wp_enqueue_style( 'jetpack-recipes-style', plugins_url( '/css/recipes.css', __FILE__ ), array(), '20130919' );
142
		wp_style_add_data( 'jetpack-recipes-style', 'rtl', 'replace' );
143
144
		// add $themecolors-defined styles.
145
		wp_add_inline_style( 'jetpack-recipes-style', self::themecolor_styles() );
146
147
		wp_enqueue_script( 'jetpack-recipes-printthis', plugins_url( '/js/recipes-printthis.js', __FILE__ ), array( 'jquery' ), '20170202' );
148
		wp_enqueue_script( 'jetpack-recipes-js',        plugins_url( '/js/recipes.js', __FILE__ ), array( 'jquery', 'jetpack-recipes-printthis' ), '20131230' );
149
150
		$title_var     = wp_title( '|', false, 'right' );
151
		$rtl           = is_rtl() ? '-rtl' : '';
152
		$print_css_var = plugins_url( "/css/recipes-print{$rtl}.css", __FILE__ );
153
154
		wp_localize_script(
155
			'jetpack-recipes-js',
156
			'jetpack_recipes_vars',
157
			array(
158
				'pageTitle' => $title_var,
159
				'loadCSS' => $print_css_var,
160
			)
161
		);
162
	}
163
164
	/**
165
	 * Our [recipe] shortcode.
166
	 * Prints recipe data styled to look good on *any* theme.
167
	 *
168
	 * @param array  $atts    Array of shortcode attributes.
169
	 * @param string $content Post content.
170
	 *
171
	 * @return string HTML for recipe shortcode.
172
	 */
173
	static function recipe_shortcode( $atts, $content = '' ) {
174
		$atts = shortcode_atts(
175
			array(
176
				'title'       => '', // string.
177
				'servings'    => '', // intval.
178
				'time'        => '', // string.
179
				'difficulty'  => '', // string.
180
				'print'       => '', // string.
181
				'source'      => '', // string.
182
				'sourceurl'   => '', // string.
183
				'image'       => '', // string.
184
				'description' => '', // string.
185
			), $atts, 'recipe'
186
		);
187
188
		return self::recipe_shortcode_html( $atts, $content );
189
	}
190
191
	/**
192
	 * The recipe output
193
	 *
194
	 * @param array  $atts    Array of shortcode attributes.
195
	 * @param string $content Post content.
196
	 *
197
	 * @return string HTML output
198
	 */
199
	static function recipe_shortcode_html( $atts, $content = '' ) {
200
201
		$html = '<div class="hrecipe jetpack-recipe" itemscope itemtype="https://schema.org/Recipe">';
202
203
		// Print the recipe title if exists.
204
		if ( '' !== $atts['title'] ) {
205
			$html .= '<h3 class="jetpack-recipe-title" itemprop="name">' . esc_html( $atts['title'] ) . '</h3>';
206
		}
207
208
		// Print the recipe meta if exists.
209
		if ( '' !== $atts['servings'] || '' != $atts['time'] || '' != $atts['difficulty'] || '' != $atts['print'] ) {
210
			$html .= '<ul class="jetpack-recipe-meta">';
211
212 View Code Duplication
			if ( '' !== $atts['servings'] ) {
213
				$html .= sprintf(
214
					'<li class="jetpack-recipe-servings" itemprop="recipeYield"><strong>%1$s: </strong>%2$s</li>',
215
					esc_html_x( 'Servings', 'recipe', 'jetpack' ),
216
					esc_html( $atts['servings'] )
217
				);
218
			}
219
220
			if ( '' !== $atts['time'] ) {
221
				// Get a time that's supported by Schema.org.
222
				$duration = WPCOM_JSON_API_Date::format_duration( $atts['time'] );
223
				// If no duration can be calculated, let's output what the user provided.
224
				if ( empty( $duration ) ) {
225
					$duration = $atts['time'];
226
				}
227
228
				$html .= sprintf(
229
					'<li class="jetpack-recipe-time">
230
					<time itemprop="totalTime" datetime="%3$s"><strong>%1$s: </strong>%2$s</time>
231
					</li>',
232
					esc_html_x( 'Time', 'recipe', 'jetpack' ),
233
					esc_html( $atts['time'] ),
234
					esc_attr( $duration )
235
				);
236
			}
237
238 View Code Duplication
			if ( '' !== $atts['difficulty'] ) {
239
				$html .= sprintf(
240
					'<li class="jetpack-recipe-difficulty"><strong>%1$s: </strong>%2$s</li>',
241
					esc_html_x( 'Difficulty', 'recipe', 'jetpack' ),
242
					esc_html( $atts['difficulty'] )
243
				);
244
			}
245
246
			if ( '' !== $atts['source'] ) {
247
				$html .= sprintf(
248
					'<li class="jetpack-recipe-source"><strong>%1$s: </strong>',
249
					esc_html_x( 'Source', 'recipe', 'jetpack' )
250
				);
251
252
				if ( '' !== $atts['sourceurl'] ) :
253
					// Show the link if we have one.
254
					$html .= sprintf(
255
						'<a href="%2$s">%1$s</a>',
256
						esc_html( $atts['source'] ),
257
						esc_url( $atts['sourceurl'] )
258
					);
259
				else :
260
					// Skip the link.
261
					$html .= sprintf(
262
						'%1$s',
263
						esc_html( $atts['source'] )
264
					);
265
				endif;
266
267
				$html .= '</li>';
268
			}
269
270
			if ( 'false' !== $atts['print'] ) {
271
				$html .= sprintf(
272
					'<li class="jetpack-recipe-print"><a href="#">%1$s</a></li>',
273
					esc_html_x( 'Print', 'recipe', 'jetpack' )
274
				);
275
			}
276
277
			$html .= '</ul>';
278
		} // End if().
279
280
		// Output the image, if we have one.
281
		if ( '' !== $atts['image'] ) {
282
			$html .= sprintf(
283
				'<img class="jetpack-recipe-image" itemprop="image" src="%1$s" />',
284
				esc_url( $atts['image'] )
285
			);
286
		}
287
288
		// Output the description, if we have one.
289
		if ( '' !== $atts['description'] ) {
290
			$html .= sprintf(
291
				'<p class="jetpack-recipe-description" itemprop="description">%1$s</p>',
292
				esc_html( $atts['description'] )
293
			);
294
		}
295
296
		// Print content between codes.
297
		$html .= '<div class="jetpack-recipe-content">' . do_shortcode( $content ) . '</div>';
298
299
		// Close it up.
300
		$html .= '</div>';
301
302
		// If there is a recipe within a recipe, remove the shortcode.
303
		if ( has_shortcode( $html, 'recipe' ) ) {
304
			remove_shortcode( 'recipe' );
305
		}
306
307
		// Sanitize html.
308
		$html = wp_kses_post( $html );
309
310
		// Return the HTML block.
311
		return $html;
312
	}
313
314
	/**
315
	 * Our [recipe-notes] shortcode.
316
	 * Outputs ingredients, styled in a div.
317
	 *
318
	 * @param array  $atts    Array of shortcode attributes.
319
	 * @param string $content Post content.
320
	 *
321
	 * @return string HTML for recipe notes shortcode.
322
	 */
323 View Code Duplication
	static function recipe_notes_shortcode( $atts, $content = '' ) {
324
		$atts = shortcode_atts( array(
325
			'title' => '', // string.
326
		), $atts, 'recipe-notes' );
327
328
		$html = '';
329
330
		// Print a title if one exists.
331
		if ( '' !== $atts['title'] ) {
332
			$html .= '<h4 class="jetpack-recipe-notes-title">' . esc_html( $atts['title'] ) . '</h4>';
333
		}
334
335
		$html .= '<div class="jetpack-recipe-notes">';
336
337
		// Format content using list functionality, if desired.
338
		$html .= self::output_list_content( $content, 'notes' );
339
340
		$html .= '</div>';
341
342
		// Sanitize html.
343
		$html = wp_kses_post( $html );
344
345
		// Return the HTML block.
346
		return $html;
347
	}
348
349
	/**
350
	 * Our [recipe-ingredients] shortcode.
351
	 * Outputs notes, styled in a div.
352
	 *
353
	 * @param array  $atts    Array of shortcode attributes.
354
	 * @param string $content Post content.
355
	 *
356
	 * @return string HTML for recipe ingredients shortcode.
357
	 */
358 View Code Duplication
	static function recipe_ingredients_shortcode( $atts, $content = '' ) {
359
		$atts = shortcode_atts( array(
360
			'title' => esc_html_x( 'Ingredients', 'recipe', 'jetpack' ), // string.
361
		), $atts, 'recipe-ingredients' );
362
363
		$html = '<div class="jetpack-recipe-ingredients">';
364
365
		// Print a title unless the user has opted to exclude it.
366
		if ( 'false' !== $atts['title'] ) {
367
			$html .= '<h4 class="jetpack-recipe-ingredients-title">' . esc_html( $atts['title'] ) . '</h4>';
368
		}
369
370
		// Format content using list functionality.
371
		$html .= self::output_list_content( $content, 'ingredients' );
372
373
		$html .= '</div>';
374
375
		// Sanitize html.
376
		$html = wp_kses_post( $html );
377
378
		// Return the HTML block.
379
		return $html;
380
	}
381
382
	/**
383
	 * Reusable function to check for shortened formatting.
384
	 * Basically, users can create lists with the following shorthand:
385
	 * - item one
386
	 * - item two
387
	 * - item three
388
	 * And we'll magically convert it to a list. This has the added benefit
389
	 * of including itemprops for the recipe schema.
390
	 *
391
	 * @param string $content HTML content.
392
	 * @param string $type    Type of list.
393
	 *
394
	 * @return string content formatted as a list item
395
	 */
396
	static function output_list_content( $content, $type ) {
397
		$html = '';
398
399
		switch ( $type ) {
400
			case 'directions' :
401
				$list_item_replacement = '<li class="jetpack-recipe-directions">${1}</li>';
402
				$itemprop              = ' itemprop="recipeInstructions"';
403
				$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...
404
				break;
405
			case 'ingredients' :
406
				$list_item_replacement = '<li class="jetpack-recipe-ingredient" itemprop="recipeIngredient">${1}</li>';
407
				$itemprop              = '';
408
				$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...
409
				break;
410
			default:
411
				$list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>';
412
				$itemprop              = '';
413
				$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...
414
		}
415
416
		// Check to see if the user is trying to use shortened formatting.
417
		if (
418
			strpos( $content, '&#8211;' ) !== false ||
419
			strpos( $content, '&#8212;' ) !== false ||
420
			strpos( $content, '-' )       !== false ||
421
			strpos( $content, '*' )       !== false ||
422
			strpos( $content, '#' )       !== false ||
423
			strpos( $content, '–' )       !== false || // ndash.
424
			strpos( $content, '—' )       !== false || // mdash.
425
			preg_match( '/\d+\.\s/', $content )
426
		) {
427
			// Remove breaks and extra whitespace.
428
			$content = str_replace( "<br />\n", "\n", $content );
429
			$content = trim( $content );
430
431
			$ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\&#8211;|\&#8212;|\*)+\h+(.*)/mi';
432
			$ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi';
433
434
			preg_match_all( $ul_pattern, $content, $ul_matches );
435
			preg_match_all( $ol_pattern, $content, $ol_matches );
436
437
			if ( 0 !== count( $ul_matches[0] ) || 0 !== count( $ol_matches[0] ) ) {
438
439
				if ( 0 !== count( $ol_matches[0] ) ) {
440
					$listtype = 'ol';
441
					$list_item_pattern = $ol_pattern;
442
				} else {
443
					$listtype = 'ul';
444
					$list_item_pattern = $ul_pattern;
445
				}
446
				$html .= '<' . $listtype . $itemprop . '>';
447
				$html .= preg_replace( $list_item_pattern, $list_item_replacement, $content );
448
				$html .= '</' . $listtype . '>';
449
450
				// Strip out any empty <p> tags and stray </p> tags, because those are just silly.
451
				$empty_p_pattern = '/(<p>)*\s*<\/p>/mi';
452
				$html = preg_replace( $empty_p_pattern, '', $html );
453
			} else {
454
				$html .= do_shortcode( $content );
455
			}
456
		} else {
457
			$html .= do_shortcode( $content );
458
		}
459
460
		// Return our formatted content.
461
		return $html;
462
	}
463
464
	/**
465
	 * Our [recipe-directions] shortcode.
466
	 * Outputs directions, styled in a div.
467
	 *
468
	 * @param array  $atts    Array of shortcode attributes.
469
	 * @param string $content Post content.
470
	 *
471
	 * @return string HTML for recipe directions shortcode.
472
	 */
473 View Code Duplication
	static function recipe_directions_shortcode( $atts, $content = '' ) {
474
		$atts = shortcode_atts( array(
475
				'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), // string.
476
		), $atts, 'recipe-directions' );
477
478
		$html = '<div class="jetpack-recipe-directions">';
479
480
		// Print a title unless the user has specified to exclude it.
481
		if ( 'false' !== $atts['title'] ) {
482
			$html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>';
483
		}
484
485
		// Format content using list functionality.
486
		$html .= self::output_list_content( $content, 'directions' );
487
488
		$html .= '</div>';
489
490
		// Sanitize html.
491
		$html = wp_kses_post( $html );
492
493
		// Return the HTML block.
494
		return $html;
495
	}
496
497
	/**
498
	 * Use $themecolors array to style the Recipes shortcode
499
	 *
500
	 * @print style block
501
	 * @return string $style
502
	 */
503
	function themecolor_styles() {
504
		global $themecolors;
505
		$style = '';
506
507
		if ( isset( $themecolors ) ) {
508
			$style .= '.jetpack-recipe { border-color: #' . esc_attr( $themecolors['border'] ) . '; }';
509
			$style .= '.jetpack-recipe-title { border-bottom-color: #' . esc_attr( $themecolors['link'] ) . '; }';
510
		}
511
512
		return $style;
513
	}
514
515
}
516
517
new Jetpack_Recipes();
518