PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9-a.7
Jetpack – WP Security, Backup, Speed, & Growth v15.9-a.7
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / shortcodes / recipe.php
jetpack / modules / shortcodes Last commit date
css 2 weeks ago images 1 year ago img 4 weeks ago js 6 months ago archiveorg-book.php 6 months ago archiveorg.php 6 months ago archives.php 2 weeks ago bandcamp.php 6 months ago brightcove.php 5 months ago cartodb.php 6 months ago class.filter-embedded-html-objects.php 6 months ago codepen.php 6 months ago crowdsignal.php 5 months ago dailymotion.php 6 months ago descript.php 6 months ago facebook.php 6 months ago flatio.php 6 months ago flickr.php 5 months ago getty.php 6 months ago gist.php 6 months ago googleapps.php 6 months ago googlemaps.php 3 weeks ago googleplus.php 6 months ago gravatar.php 6 months ago houzz.php 6 months ago inline-pdfs.php 6 months ago instagram.php 6 months ago kickstarter.php 6 months ago mailchimp.php 5 months ago medium.php 6 months ago mixcloud.php 6 months ago others.php 6 months ago pinterest.php 6 months ago presentations.php 6 months ago quiz.php 6 months ago recipe.php 6 months ago scribd.php 6 months ago shortcode-utils.php 6 months ago sitemap.php 6 months ago slideshare.php 6 months ago slideshow.php 4 weeks ago smartframe.php 6 months ago soundcloud.php 6 months ago spotify.php 6 months ago ted.php 6 months ago tweet.php 6 months ago twitchtv.php 6 months ago twitter-timeline.php 6 months ago twitter.php 6 months ago unavailable.php 6 months ago untappd-menu.php 6 months ago upcoming-events.php 6 months ago ustream.php 6 months ago videopress.php 6 months ago vimeo.php 1 week ago vine.php 6 months ago vr.php 1 week ago wufoo.php 6 months ago youtube.php 3 months ago
recipe.php
704 lines
1 <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3 use Automattic\Jetpack\Assets;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit( 0 );
7 }
8
9 /**
10 * Embed recipe 'cards' in post, with basic styling and print functionality
11 *
12 * To Do
13 * - defaults settings
14 * - basic styles/themecolor styles
15 * - validation/sanitization
16 * - print styles
17 *
18 * @package automattic/jetpack
19 */
20
21 /**
22 * Register and display Recipes in posts.
23 *
24 * @phan-constructor-used-for-side-effects
25 */
26 class Jetpack_Recipes {
27
28 /**
29 * Have scripts and styles been enqueued already.
30 *
31 * @var bool
32 */
33 private $scripts_and_style_included = false;
34
35 /**
36 * Constructor
37 */
38 public function __construct() {
39 add_action( 'init', array( $this, 'action_init' ) );
40 }
41
42 /**
43 * Returns KSES tags with Schema-specific attributes.
44 *
45 * @since 8.0.0
46 *
47 * @return array Array to be used by KSES.
48 */
49 private static function kses_tags() {
50 $allowedtags = wp_kses_allowed_html( 'post' );
51 // Create an array of all the tags we'd like to add the itemprop attribute to.
52 $tags = array( 'li', 'ol', 'ul', 'img', 'p', 'h3', 'time', 'span' );
53 foreach ( $tags as $tag ) {
54 if ( ! isset( $allowedtags[ $tag ] ) ) {
55 $allowedtags[ $tag ] = array();
56 }
57 $allowedtags[ $tag ]['class'] = array();
58 $allowedtags[ $tag ]['itemprop'] = array();
59 $allowedtags[ $tag ]['datetime'] = array();
60 }
61
62 // Allow the handler <a on=""> in AMP.
63 $allowedtags['a']['on'] = array();
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 * Register our shortcode and enqueue necessary files.
77 */
78 public function action_init() {
79 // Enqueue styles if [recipe] exists.
80 add_action( 'wp_head', array( $this, 'add_scripts' ), 1 );
81
82 // Render [recipe], along with other shortcodes that can be nested within.
83 add_shortcode( 'recipe', array( $this, 'recipe_shortcode' ) );
84 add_shortcode( 'recipe-notes', array( $this, 'recipe_notes_shortcode' ) );
85 add_shortcode( 'recipe-ingredients', array( $this, 'recipe_ingredients_shortcode' ) );
86 add_shortcode( 'recipe-directions', array( $this, 'recipe_directions_shortcode' ) );
87 add_shortcode( 'recipe-nutrition', array( $this, 'recipe_nutrition_shortcode' ) );
88 add_shortcode( 'recipe-image', array( $this, 'recipe_image_shortcode' ) );
89 }
90
91 /**
92 * Enqueue scripts and styles
93 */
94 public function add_scripts() {
95 if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) {
96 return;
97 }
98
99 foreach ( $GLOBALS['posts'] as $p ) {
100 if ( isset( $p->post_content ) && has_shortcode( $p->post_content, 'recipe' ) ) {
101 $this->scripts_and_style_included = true;
102 break;
103 }
104 }
105
106 if ( ! $this->scripts_and_style_included ) {
107 return;
108 }
109
110 wp_enqueue_style( 'jetpack-recipes-style', plugins_url( '/css/recipes.css', __FILE__ ), array(), '20130919' );
111 wp_style_add_data( 'jetpack-recipes-style', 'rtl', 'replace' );
112
113 // add $themecolors-defined styles.
114 wp_add_inline_style( 'jetpack-recipes-style', self::themecolor_styles() );
115
116 if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
117 return;
118 }
119
120 wp_register_script(
121 'jetpack-shortcode-deps',
122 plugins_url( '_inc/build/shortcodes/js/dependencies.min.js', JETPACK__PLUGIN_FILE ),
123 array( 'jquery' ),
124 '20250905',
125 true
126 );
127
128 wp_enqueue_script(
129 'jetpack-recipes-js',
130 Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes.min.js', 'modules/shortcodes/js/recipes.js' ),
131 array( 'jquery', 'jetpack-shortcode-deps' ),
132 '20131230',
133 false
134 );
135
136 $title_var = wp_title( '|', false, 'right' );
137 $rtl = is_rtl() ? '-rtl' : '';
138 $print_css_var = plugins_url( "/css/recipes-print{$rtl}.css", __FILE__ );
139
140 wp_localize_script(
141 'jetpack-recipes-js',
142 'jetpack_recipes_vars',
143 array(
144 'pageTitle' => $title_var,
145 'loadCSS' => $print_css_var,
146 )
147 );
148 }
149
150 /**
151 * Our [recipe] shortcode.
152 * Prints recipe data styled to look good on *any* theme.
153 *
154 * @param array $atts Array of shortcode attributes.
155 * @param string $content Post content.
156 *
157 * @return string HTML for recipe shortcode.
158 */
159 public static function recipe_shortcode( $atts, $content = '' ) {
160 $atts = shortcode_atts(
161 array(
162 'title' => '', // string.
163 'servings' => '', // intval.
164 'time' => '', // strtotime-compatible time description.
165 'difficulty' => '', // string.
166 'print' => '', // URL for external print version.
167 'source' => '', // string.
168 'sourceurl' => '', // URL string. Only used if source set.
169 'image' => '', // URL or attachment ID.
170 'description' => '', // string.
171 'cooktime' => '', // strtotime-compatible time description.
172 'preptime' => '', // strtotime-compatible time description.
173 'rating' => '', // string.
174 ),
175 $atts,
176 'recipe'
177 );
178
179 return self::recipe_shortcode_html( $atts, $content );
180 }
181
182 /**
183 * The recipe output
184 *
185 * @param array $atts Array of shortcode attributes.
186 * @param string $content Post content.
187 *
188 * @return string HTML output
189 */
190 private static function recipe_shortcode_html( $atts, $content = '' ) {
191
192 $html = '<div class="hrecipe h-recipe jetpack-recipe" itemscope itemtype="https://schema.org/Recipe">';
193
194 // Print the recipe title if exists.
195 if ( '' !== $atts['title'] ) {
196 $html .= '<h3 class="p-name jetpack-recipe-title fn" itemprop="name">' . esc_html( $atts['title'] ) . '</h3>';
197 }
198
199 // Print the recipe meta if exists.
200 if (
201 '' !== $atts['servings']
202 || '' !== $atts['time']
203 || '' !== $atts['difficulty']
204 || '' !== $atts['print']
205 || '' !== $atts['preptime']
206 || '' !== $atts['cooktime']
207 || '' !== $atts['rating']
208 ) {
209 $html .= '<ul class="jetpack-recipe-meta">';
210
211 if ( '' !== $atts['servings'] ) {
212 $html .= sprintf(
213 '<li class="jetpack-recipe-servings p-yield yield" itemprop="recipeYield"><strong>%1$s: </strong>%2$s</li>',
214 esc_html_x( 'Servings', 'recipe', 'jetpack' ),
215 esc_html( $atts['servings'] )
216 );
217 }
218
219 $time_types = array( 'preptime', 'cooktime', 'time' );
220 foreach ( $time_types as $time_type ) {
221 if ( '' === $atts[ $time_type ] ) {
222 continue;
223 }
224 $html .= self::output_time( $atts[ $time_type ], $time_type );
225 }
226
227 if ( '' !== $atts['difficulty'] ) {
228 $html .= sprintf(
229 '<li class="jetpack-recipe-difficulty"><strong>%1$s: </strong>%2$s</li>',
230 esc_html_x( 'Difficulty', 'recipe', 'jetpack' ),
231 esc_html( $atts['difficulty'] )
232 );
233 }
234
235 if ( '' !== $atts['rating'] ) {
236 $html .= sprintf(
237 '<li class="jetpack-recipe-rating">
238 <strong>%1$s: </strong>
239 <span itemprop="contentRating">%2$s</span>
240 </li>',
241 esc_html_x( 'Rating', 'recipe', 'jetpack' ),
242 esc_html( $atts['rating'] )
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 $is_amp = class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request();
272 $print_action = $is_amp ? 'on="tap:AMP.print"' : '';
273 $print_text = $is_amp ? esc_html__( 'Print page', 'jetpack' ) : esc_html_x( 'Print', 'recipe', 'jetpack' );
274 $html .= sprintf(
275 '<li class="jetpack-recipe-print"><a href="#" %1$s>%2$s</a></li>',
276 $print_action,
277 $print_text
278 );
279 }
280
281 $html .= '</ul>';
282 }
283
284 // Output the image if we have one and it's not shown elsewhere.
285 if ( '' !== $atts['image'] ) {
286 if ( ! has_shortcode( $content, 'recipe-image' ) ) {
287 $html .= self::output_image_html( $atts['image'] );
288 }
289 }
290
291 // Output the description, if we have one.
292 if ( '' !== $atts['description'] ) {
293 $html .= sprintf(
294 '<p class="jetpack-recipe-description" itemprop="description">%1$s</p>',
295 esc_html( $atts['description'] )
296 );
297 }
298
299 // Print content between codes.
300 $html .= '<div class="jetpack-recipe-content">' . do_shortcode( $content ) . '</div>';
301
302 // Close it up.
303 $html .= '</div>';
304
305 // If there is a recipe within a recipe, remove the shortcode.
306 if ( has_shortcode( $html, 'recipe' ) ) {
307 remove_shortcode( 'recipe' );
308 }
309
310 // Sanitize html.
311 $html = wp_kses( $html, self::kses_tags() );
312
313 // Return the HTML block.
314 return $html;
315 }
316
317 /**
318 * Our [recipe-image] shortcode.
319 * Controls placement of image in recipe.
320 *
321 * @param array $atts Array of shortcode attributes.
322 *
323 * @return string HTML for recipe notes shortcode.
324 */
325 public static function recipe_image_shortcode( $atts ) {
326 $atts = shortcode_atts(
327 array(
328 'image' => '', // string.
329 0 => '', // string.
330 ),
331 $atts,
332 'recipe-image'
333 );
334 $src = $atts['image'];
335 if ( ! empty( $atts[0] ) ) {
336 $src = $atts[0];
337 }
338 return self::output_image_html( $src );
339 }
340
341 /**
342 * Our [recipe-notes] shortcode.
343 * Outputs ingredients, styled in a div.
344 *
345 * @param array $atts Array of shortcode attributes.
346 * @param string $content Post content.
347 *
348 * @return string HTML for recipe notes shortcode.
349 */
350 public static function recipe_notes_shortcode( $atts, $content = '' ) {
351 $atts = shortcode_atts(
352 array(
353 'title' => '', // string.
354 ),
355 $atts,
356 'recipe-notes'
357 );
358
359 $html = '';
360
361 // Print a title if one exists.
362 if ( '' !== $atts['title'] ) {
363 $html .= '<h4 class="jetpack-recipe-notes-title">' . esc_html( $atts['title'] ) . '</h4>';
364 }
365
366 $html .= '<div class="jetpack-recipe-notes">';
367
368 // Format content using list functionality, if desired.
369 $html .= self::output_list_content( $content, 'notes' );
370
371 $html .= '</div>';
372
373 // Sanitize html.
374 $html = wp_kses( $html, self::kses_tags() );
375
376 // Return the HTML block.
377 return $html;
378 }
379
380 /**
381 * Our [recipe-ingredients] shortcode.
382 * Outputs notes, styled in a div.
383 *
384 * @param array $atts Array of shortcode attributes.
385 * @param string $content Post content.
386 *
387 * @return string HTML for recipe ingredients shortcode.
388 */
389 public static function recipe_ingredients_shortcode( $atts, $content = '' ) {
390 $atts = shortcode_atts(
391 array(
392 'title' => esc_html_x( 'Ingredients', 'recipe', 'jetpack' ), // string.
393 ),
394 $atts,
395 'recipe-ingredients'
396 );
397
398 $html = '<div class="jetpack-recipe-ingredients">';
399
400 // Print a title unless the user has opted to exclude it.
401 if ( 'false' !== $atts['title'] ) {
402 $html .= '<h4 class="jetpack-recipe-ingredients-title">' . esc_html( $atts['title'] ) . '</h4>';
403 }
404
405 // Format content using list functionality.
406 $html .= self::output_list_content( $content, 'ingredients' );
407
408 $html .= '</div>';
409
410 // Sanitize html.
411 $html = wp_kses( $html, self::kses_tags() );
412
413 // Return the HTML block.
414 return $html;
415 }
416
417 /**
418 * Our [recipe-nutrition] shortcode.
419 * Outputs notes, styled in a div.
420 *
421 * @param array $atts Array of shortcode attributes.
422 * @param string $content Post content.
423 *
424 * @return string HTML for recipe nutrition shortcode.
425 */
426 public static function recipe_nutrition_shortcode( $atts, $content = '' ) {
427 $atts = shortcode_atts(
428 array(
429 'title' => esc_html_x( 'Nutrition', 'recipe', 'jetpack' ), // string.
430 ),
431 $atts,
432 'recipe-nutrition'
433 );
434
435 $html = '<div class="jetpack-recipe-nutrition p-nutrition nutrition">';
436
437 // Print a title unless the user has opted to exclude it.
438 if ( 'false' !== $atts['title'] ) {
439 $html .= '<h4 class="jetpack-recipe-nutrition-title">' . esc_html( $atts['title'] ) . '</h4>';
440 }
441
442 // Format content using list functionality.
443 $html .= self::output_list_content( $content, 'nutrition' );
444
445 $html .= '</div>';
446
447 // Sanitize html.
448 $html = wp_kses( $html, self::kses_tags() );
449
450 // Return the HTML block.
451 return $html;
452 }
453
454 /**
455 * Reusable function to check for shortened formatting.
456 * Basically, users can create lists with the following shorthand:
457 * - item one
458 * - item two
459 * - item three
460 * And we'll magically convert it to a list. This has the added benefit
461 * of including itemprops for the recipe schema.
462 *
463 * @param string $content HTML content.
464 * @param string $type Type of list.
465 *
466 * @return string content formatted as a list item
467 */
468 private static function output_list_content( $content, $type ) {
469 $html = '';
470
471 switch ( $type ) {
472 case 'directions':
473 $list_item_replacement = '<li class="jetpack-recipe-directions">${1}</li>';
474 $itemprop = ' itemprop="recipeInstructions"';
475 $listtype = 'ol';
476 break;
477 case 'ingredients':
478 $list_item_replacement = '<li class="jetpack-recipe-ingredient p-ingredient ingredient" itemprop="recipeIngredient">${1}</li>';
479 $itemprop = '';
480 $listtype = 'ul';
481 break;
482 case 'nutrition':
483 $list_item_replacement = '<li class="jetpack-recipe-nutrition">${1}</li>';
484 $itemprop = ' itemprop="nutrition"';
485 $listtype = 'ul';
486 break;
487 case 'nutrition':
488 $list_item_replacement = '<li class="jetpack-recipe-nutrition nutrition">${1}</li>';
489 $itemprop = ' itemprop="nutrition"';
490 $listtype = 'ul';
491 break;
492 default:
493 $list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>';
494 $itemprop = '';
495 $listtype = 'ul';
496 }
497
498 // Check to see if the user is trying to use shortened formatting.
499 if (
500 str_contains( $content, '&#8211;' ) ||
501 str_contains( $content, '&#8212;' ) ||
502 str_contains( $content, '-' ) ||
503 str_contains( $content, '*' ) ||
504 str_contains( $content, '#' ) ||
505 str_contains( $content, '' ) || // ndash.
506 str_contains( $content, '' ) || // mdash.
507 preg_match( '/\d+\.\s/', $content )
508 ) {
509 // Remove breaks and extra whitespace.
510 $content = str_replace( "<br />\n", "\n", $content );
511 $content = trim( $content );
512
513 $ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\&#8211;|\&#8212;|\*)+\h+(.*)/mi';
514 $ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi';
515
516 preg_match_all( $ul_pattern, $content, $ul_matches );
517 preg_match_all( $ol_pattern, $content, $ol_matches );
518
519 if ( ( is_countable( $ul_matches[0] ) && count( $ul_matches[0] ) > 0 ) || ( is_countable( $ol_matches[0] ) && count( $ol_matches[0] ) > 0 ) ) {
520
521 if ( is_countable( $ol_matches[0] ) && count( $ol_matches[0] ) > 0 ) {
522 $listtype = 'ol';
523 $list_item_pattern = $ol_pattern;
524 } else {
525 $listtype = 'ul';
526 $list_item_pattern = $ul_pattern;
527 }
528 $html .= '<' . $listtype . $itemprop . '>';
529 $html .= preg_replace( $list_item_pattern, $list_item_replacement, $content );
530 $html .= '</' . $listtype . '>';
531
532 // Strip out any empty <p> tags and stray </p> tags, because those are just silly.
533 $empty_p_pattern = '/(<p>)*\s*<\/p>/mi';
534 $html = preg_replace( $empty_p_pattern, '', $html );
535 } else {
536 $html .= do_shortcode( $content );
537 }
538 } else {
539 $html .= do_shortcode( $content );
540 }
541
542 // Return our formatted content.
543 return $html;
544 }
545
546 /**
547 * Our [recipe-directions] shortcode.
548 * Outputs directions, styled in a div.
549 *
550 * @param array $atts Array of shortcode attributes.
551 * @param string $content Post content.
552 *
553 * @return string HTML for recipe directions shortcode.
554 */
555 public static function recipe_directions_shortcode( $atts, $content = '' ) {
556 $atts = shortcode_atts(
557 array(
558 'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), // string.
559 ),
560 $atts,
561 'recipe-directions'
562 );
563
564 $html = '<div class="jetpack-recipe-directions e-instructions">';
565
566 // Print a title unless the user has specified to exclude it.
567 if ( 'false' !== $atts['title'] ) {
568 $html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>';
569 }
570
571 // Format content using list functionality.
572 $html .= self::output_list_content( $content, 'directions' );
573
574 $html .= '</div>';
575
576 // Sanitize html.
577 $html = wp_kses( $html, self::kses_tags() );
578
579 // Return the HTML block.
580 return $html;
581 }
582
583 /**
584 * Outputs time meta tag.
585 *
586 * @param string $time_str Raw time to output.
587 * @param string $time_type Type of time to show.
588 *
589 * @return string HTML for recipe time meta.
590 */
591 private static function output_time( $time_str, $time_type ) {
592 // Get a time that's supported by Schema.org.
593 $duration = WPCOM_JSON_API_Date::format_duration( $time_str );
594 // If no duration can be calculated, let's output what the user provided.
595 if ( ! $duration ) {
596 $duration = $time_str;
597 }
598
599 switch ( $time_type ) {
600 case 'cooktime':
601 $title = _x( 'Cook Time', 'recipe', 'jetpack' );
602 $itemprop = 'cookTime';
603 break;
604 case 'preptime':
605 $title = _x( 'Prep Time', 'recipe', 'jetpack' );
606 $itemprop = 'prepTime';
607 break;
608 default:
609 $title = _x( 'Time', 'recipe', 'jetpack' );
610 $itemprop = 'totalTime';
611 break;
612 }
613
614 return sprintf(
615 '<li class="jetpack-recipe-%3$s">
616 <time itemprop="%4$s" datetime="%5$s"><strong>%1$s:</strong> <span class="%3$s">%2$s</span></time>
617 </li>',
618 esc_html( $title ),
619 esc_html( $time_str ),
620 esc_attr( $time_type ),
621 esc_attr( $itemprop ),
622 esc_attr( $duration )
623 );
624 }
625
626 /**
627 * Outputs image tag for recipe.
628 *
629 * @param string $src The image source.
630 *
631 * @return string
632 */
633 private static function output_image_html( $src ) {
634 // Exit if there is no provided source.
635 if ( ! $src ) {
636 return '';
637 }
638
639 $image_attrs = array(
640 'class' => 'jetpack-recipe-image u-photo photo',
641 'itemprop' => 'image',
642 );
643
644 if ( wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' ) ) {
645 $image_attrs['loading'] = 'lazy';
646 }
647
648 // If it's numeric, this may be an attachment.
649 if ( is_numeric( $src ) ) {
650 return wp_get_attachment_image(
651 $src,
652 'full',
653 false,
654 $image_attrs
655 );
656 }
657
658 // Check if it's an absolute or relative URL, and return if not.
659 if (
660 ! str_starts_with( $src, '/' )
661 && false === filter_var( $src, FILTER_VALIDATE_URL )
662 ) {
663 return '';
664 }
665
666 $image_attrs_markup = '';
667 foreach ( $image_attrs as $name => $value ) {
668 $image_attrs_markup .= sprintf(
669 ' %1$s="%2$s"',
670 esc_attr( $name ),
671 esc_attr( $value )
672 );
673 }
674
675 return sprintf(
676 '<img%1$s src="%2$s" />',
677 $image_attrs_markup,
678 esc_url( $src )
679 );
680 }
681
682 /**
683 * Use $themecolors array to style the Recipes shortcode
684 *
685 * @print style block
686 * @return string $style
687 */
688 public function themecolor_styles() {
689 global $themecolors;
690 $style = '';
691
692 if ( isset( $themecolors['border'] ) ) {
693 $style .= '.jetpack-recipe { border-color: #' . esc_attr( $themecolors['border'] ) . '; }';
694 }
695 if ( isset( $themecolors['link'] ) ) {
696 $style .= '.jetpack-recipe-title { border-bottom-color: #' . esc_attr( $themecolors['link'] ) . '; }';
697 }
698
699 return $style;
700 }
701 }
702
703 new Jetpack_Recipes();
704