Completed
Push — update/shortcode-recipe ( eb0486 )
by Jeremy
10:25
created

Jetpack_Recipes::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 8
c 1
b 0
f 1
nc 3
nop 0
dl 0
loc 13
rs 9.4285
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 itemprop to allowed tags for wp_kses_post, so we can use it for better Schema compliance.
20
		global $allowedposttags;
21
		$tags = array( 'li', 'ol', 'img' );
22
		foreach ( $tags as $tag ) {
23
			if ( ! is_array( $allowedposttags[ $tag ] ) ) {
24
				$allowedposttags[ $tag ] = array();
25
			}
26
			$allowedposttags[ $tag ]['itemprop'] = array();
27
		}
28
	}
29
30
	function action_init() {
31
		// Enqueue styles if [recipe] exists
32
		add_action( 'wp_head', array( $this, 'add_scripts' ), 1 );
33
34
		// Render [recipe], along with other shortcodes that can be nested within.
35
		add_shortcode( 'recipe', array( $this, 'recipe_shortcode' ) );
36
		add_shortcode( 'recipe-notes', array( $this, 'recipe_notes_shortcode' ) );
37
		add_shortcode( 'recipe-ingredients', array( $this, 'recipe_ingredients_shortcode' ) );
38
		add_shortcode( 'recipe-directions', array( $this, 'recipe_directions_shortcode' ) );
39
	}
40
41
	/**
42
	 * Enqueue scripts and styles
43
	 */
44
	function add_scripts() {
45
		if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) {
46
			return;
47
		}
48
49 View Code Duplication
		foreach ( $GLOBALS['posts'] as $p ) {
50
			if ( has_shortcode( $p->post_content, 'recipe' ) ) {
51
				$this->scripts_and_style_included = true;
52
				break;
53
			}
54
		}
55
56
		if ( ! $this->scripts_and_style_included ) {
57
			return;
58
		}
59
60
		if( is_rtl() ) {
61
			wp_enqueue_style( 'jetpack-recipes-style',  plugins_url( '/css/rtl/recipes-rtl.css',  __FILE__ ), array(), '20130919' );
62
		} else {
63
			wp_enqueue_style( 'jetpack-recipes-style',  plugins_url( '/css/recipes.css',  __FILE__ ), array(), '20130919' );
64
		}
65
66
67
		wp_enqueue_script( 'jetpack-recipes-printthis', plugins_url( '/js/recipes-printthis.js', __FILE__ ), array( 'jquery' ), '20131230' );
68
		wp_enqueue_script( 'jetpack-recipes-js',        plugins_url( '/js/recipes.js', __FILE__ ),   array( 'jquery', 'jetpack-recipes-printthis' ), '20131230' );
69
70
		$title_var     = wp_title( '|', false, 'right' );
71
		$print_css_var = plugins_url( '/css/recipes-print.css', __FILE__ );
72
73
		wp_localize_script( 'jetpack-recipes-js', 'jetpack_recipes_vars', array( 'pageTitle' => $title_var, 'loadCSS' => $print_css_var ) );
74
	}
75
76
	/**
77
	 * Our [recipe] shortcode.
78
	 * Prints recipe data styled to look good on *any* theme.
79
	 *
80
	 * @return string HTML for resume shortcode.
81
	 */
82
	static function recipe_shortcode( $atts, $content = '' ) {
83
		$atts = shortcode_atts(
84
			array(
85
				'title'       => '', //string
86
				'servings'    => '', //intval
87
				'time'        => '', //string
88
				'difficulty'  => '', //string
89
				'print'       => '', //string
90
				'image'       => '', //string
91
				'description' => '', //string
92
			), $atts, 'recipe'
93
		);
94
95
		return self::recipe_shortcode_html( $atts, $content );
96
	}
97
98
	/**
99
	 * The recipe output
100
	 *
101
	 * @return string HTML output
102
	 */
103
	static function recipe_shortcode_html( $atts, $content = '' ) {
104
		// Add itemprop to allowed tags for wp_kses_post, so we can use it for better Schema compliance.
105
		global $allowedtags;
106
		$allowedtags['li'] = array( 'itemprop' => array () );
107
108
		$html = '<div class="hrecipe jetpack-recipe" itemscope itemtype="http://schema.org/Recipe">';
109
110
		// Print the recipe title if exists
111
		if ( '' !== $atts['title'] ) {
112
			$html .= '<h3 class="jetpack-recipe-title" itemprop="name">' . esc_html( $atts['title'] ) . '</h3>';
113
		}
114
115
		// Print the recipe meta if exists
116
		if ( '' !== $atts['servings'] || '' != $atts['time'] || '' != $atts['difficulty'] || '' != $atts['print'] ) {
117
			$html .= '<ul class="jetpack-recipe-meta">';
118
119 View Code Duplication
			if ( '' !== $atts['servings'] ) {
120
				$html .= sprintf(
121
					'<li class="jetpack-recipe-servings" itemprop="recipeYield"><strong>%1$s: </strong>%2$s</li>',
122
					esc_html_x( 'Servings', 'recipe', 'jetpack' ),
123
					esc_html( $atts['servings'] )
124
				);
125
			}
126
127 View Code Duplication
			if ( '' !== $atts['time'] ) {
128
				$html .= sprintf(
129
					'<li class="jetpack-recipe-time" itemprop="totalTime"><strong>%1$s: </strong>%2$s</li>',
130
					esc_html_x( 'Time', 'recipe', 'jetpack' ),
131
					esc_html( $atts['time'] )
132
				);
133
			}
134
135 View Code Duplication
			if ( '' !== $atts['difficulty'] ) {
136
				$html .= sprintf(
137
					'<li class="jetpack-recipe-difficulty"><strong>%1$s: </strong>%2$s</li>',
138
					esc_html_x( 'Difficulty', 'recipe', 'jetpack' ),
139
					esc_html( $atts['difficulty'] )
140
				);
141
			}
142
143
			if ( 'false' !== $atts['print'] ) {
144
				$html .= sprintf(
145
					'<li class="jetpack-recipe-print"><a href="#">%1$s</a></li>',
146
					esc_html_x( 'Print', 'recipe', 'jetpack' )
147
				);
148
			}
149
150
			$html .= '</ul>';
151
		}
152
153
		// Output the image, if we have one.
154
		if ( '' !== $atts['image'] ) {
155
			$html .= sprintf(
156
				'<img class="jetpack-recipe-image" itemprop="thumbnailUrl" src="%1$s" />',
157
				esc_url( $atts['image'] )
158
			);
159
		}
160
161
		// Output the description, if we have one.
162
		if ( '' !== $atts['description'] ) {
163
			$html .= sprintf(
164
				'<p class="jetpack-recipe-description">%1$s</p>',
165
				esc_html( $atts['description'] )
166
			);
167
		}
168
169
		// Print content between codes
170
		$html .= '<div class="jetpack-recipe-content">' . do_shortcode( $content ) . '</div>';
171
172
		// Close it up
173
		$html .= '</div>';
174
175
		// If there is a recipe within a recipe, remove the shortcode
176
		if ( has_shortcode( $html, 'recipe' ) ) {
177
			remove_shortcode( 'recipe' );
178
		}
179
180
		// Sanitize html
181
		$html = wp_kses_post( $html );
182
183
		// Return the HTML block
184
		return $html;
185
	}
186
187
	/**
188
	 * Our [recipe-notes] shortcode.
189
	 * Outputs notes, styled in a div.
190
	 *
191
	 * @return string HTML for recipe notes shortcode.
192
	 */
193 View Code Duplication
	static function recipe_notes_shortcode( $atts, $content = '' ) {
194
		$atts = shortcode_atts( array(
195
			'title' => '', //string
196
		), $atts, 'recipe-notes' );
197
198
		// Print a title if one exists.
199
		if ( '' !== $atts['title'] ) {
200
			$html .= '<h4 class="jetpack-recipe-notes-title">' . esc_html( $atts['title'] ) . '</h4>';
0 ignored issues
show
Bug introduced by
The variable $html seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
201
		}
202
203
		$html .= '<div class="jetpack-recipe-notes">';
0 ignored issues
show
Bug introduced by
The variable $html does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
204
205
		// Format content using list functionality, if desired.
206
		$html .= self::output_list_content( $content, 'notes' );
207
208
		$html .= '</div>';
209
210
		// Sanitize html.
211
		$html = wp_kses_post( $html );
212
213
		// Return the HTML block.
214
		return $html;
215
	}
216
217
	/**
218
	 * Our [recipe-ingredients] shortcode.
219
	 * Outputs notes, styled in a div.
220
	 *
221
	 * @return string HTML for recipe ingredients shortcode.
222
	 */
223 View Code Duplication
	static function recipe_ingredients_shortcode( $atts, $content = '' ) {
224
		$atts = shortcode_atts( array(
225
			'title' => esc_html_x( 'Ingredients', 'recipe', 'jetpack' ), //string
226
		), $atts, 'recipe-ingredients' );
227
228
		$html = '<div class="jetpack-recipe-ingredients">';
229
230
		// Print a title unless the user has opted to exclude it.
231
		if ( 'false' !== $atts['title'] ) {
232
			$html .= '<h4 class="jetpack-recipe-ingredients-title">' . esc_html( $atts['title'] ) . '</h4>';
233
		}
234
235
		// Format content using list functionality.
236
		$html .= self::output_list_content( $content, 'ingredients' );
237
238
		$html .= '</div>';
239
240
		// Sanitize html.
241
		$html = wp_kses_post( $html );
242
243
		// Return the HTML block.
244
		return $html;
245
	}
246
247
	/**
248
	 * Reusable function to check for shortened formatting.
249
	 * Basically, users can create lists with the following shorthand:
250
	 * - item one
251
	 * - item two
252
	 * - item three
253
	 * And we'll magically convert it to a list. This has the added benefit
254
	 * of including itemprops for the recipe schema.
255
	 *
256
	 * @return string content formatted as a list item
257
	 */
258
	static function output_list_content( $content, $type ) {
259
		switch ( $type ) {
260
			case 'directions' :
261
				$list_item_replacement = '<li class="jetpack-recipe-directions">${1}</li>';
262
				$itemprop = ' itemprop="recipeInstructions"';
263
				break;
264
			case 'ingredients' :
265
				$list_item_replacement = '<li class="jetpack-recipe-ingredient" itemprop="recipeIngredient">${1}</li>';
266
				$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...
267
				break;
268
			default:
269
				$list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>';
270
				$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...
271
		}
272
273
		// Check to see if the user is trying to use shortened formatting.
274
		if (
275
			strpos( $content, '&#8211;' ) !== false ||
276
			strpos( $content, '&#8212;' ) !== false ||
277
			strpos( $content, '-' )       !== false ||
278
			strpos( $content, '*' )       !== false ||
279
			strpos( $content, '#' )       !== false ||
280
			strpos( $content, '–' )   !== false || // ndash
281
			strpos( $content, '—' )   !== false || // mdash
282
			preg_match( '/\d+\.\s/', $content )
283
		) {
284
			// Remove breaks and extra whitespace
285
			$content = str_replace( "<br />\n", "\n", $content );
286
			$content = trim( $content );
287
288
			$ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\&#8211;|\&#8212;|\*)+\h+(.*)/mi';
289
			$ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi';
290
291
			preg_match_all( $ul_pattern, $content, $ul_matches );
292
			preg_match_all( $ol_pattern, $content, $ol_matches );
293
294
			if ( 0 !== count( $ul_matches[0] ) || 0 !== count( $ol_matches[0] ) ) {
295
296
				if ( 0 !== count( $ol_matches[0] ) ) {
297
					$listtype = 'ol';
298
					$list_item_pattern = $ol_pattern;
299
				} else {
300
					$listtype = 'ul';
301
					$list_item_pattern = $ul_pattern;
302
				}
303
				$html .= '<' . $listtype . $itemprop . '>';
0 ignored issues
show
Bug introduced by
The variable $html seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $itemprop does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
304
				$html .= preg_replace( $list_item_pattern, $list_item_replacement, $content );
305
				$html .= '</' . $listtype . '>';
306
307
				// Strip out any empty <p> tags and stray </p> tags, because those are just silly.
308
				$empty_p_pattern = '/(<p>)*\s*<\/p>/mi';
309
				$html = preg_replace( $empty_p_pattern, '', $html );
310
			} else {
311
				$html .= do_shortcode( $content );
0 ignored issues
show
Bug introduced by
The variable $html seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
312
			}
313
		} else {
314
			$html .= do_shortcode( $content );
0 ignored issues
show
Bug introduced by
The variable $html seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
315
		}
316
317
		// Return our formatted content.
318
		return $html;
319
	}
320
321
	/**
322
	 * Our [recipe-directions] shortcode.
323
	 * Outputs notes, styled in a div.
324
	 *
325
	 * @return string HTML for recipe notes shortcode.
326
	 */
327 View Code Duplication
	static function recipe_directions_shortcode( $atts, $content = '' ) {
328
		$atts = shortcode_atts( array(
329
				'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), //string
330
		), $atts, 'recipe-directions' );
331
332
		$html = '<div class="jetpack-recipe-directions">';
333
334
		// Print a title unless the user has specified to exclude it.
335
		if ( 'false' !== $atts['title'] ) {
336
			$html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>';
337
		}
338
339
		// Format content using list functionality.
340
		$html .= self::output_list_content( $content, 'directions' );
341
342
		$html .= '</div>';
343
344
		// Sanitize html.
345
		$html = wp_kses_post( $html );
346
347
		// Return the HTML block.
348
		return $html;
349
	}
350
}
351
352
new Jetpack_Recipes();
353