PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.9
Jetpack – WP Security, Backup, Speed, & Growth v6.9
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / shortcodes / recipe.php

recipe.php in Jetpack – WP Security, Backup, Speed, & Growth 6.9, at modules/shortcodes/recipe.php

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