PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 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 1 month ago images 1 year ago img 2 months ago js 7 months ago archiveorg-book.php 7 months ago archiveorg.php 1 month ago archives.php 1 week ago bandcamp.php 7 months ago brightcove.php 6 months ago cartodb.php 7 months ago class.filter-embedded-html-objects.php 7 months ago codepen.php 7 months ago crowdsignal.php 6 months ago dailymotion.php 7 months ago descript.php 7 months ago facebook.php 7 months ago flatio.php 7 months ago flickr.php 7 months ago getty.php 7 months ago gist.php 7 months ago googleapps.php 7 months ago googlemaps.php 1 month ago googleplus.php 7 months ago gravatar.php 7 months ago houzz.php 7 months ago inline-pdfs.php 7 months ago instagram.php 7 months ago kickstarter.php 7 months ago mailchimp.php 1 month ago medium.php 7 months ago mixcloud.php 7 months ago others.php 7 months ago pinterest.php 7 months ago presentations.php 7 months ago quiz.php 7 months ago recipe.php 2 weeks ago scribd.php 7 months ago shortcode-utils.php 7 months ago sitemap.php 7 months ago slideshare.php 7 months ago slideshow.php 2 weeks ago smartframe.php 7 months ago soundcloud.php 1 month ago spotify.php 7 months ago ted.php 7 months ago tweet.php 7 months ago twitchtv.php 2 weeks ago twitter-timeline.php 7 months ago twitter.php 7 months ago unavailable.php 7 months ago untappd-menu.php 7 months ago upcoming-events.php 7 months ago ustream.php 7 months ago videopress.php 7 months ago vimeo.php 1 month ago vine.php 7 months ago vr.php 1 month ago wufoo.php 7 months ago youtube.php 4 months ago
recipe.php
699 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 default:
488 $list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>';
489 $itemprop = '';
490 $listtype = 'ul';
491 }
492
493 // Check to see if the user is trying to use shortened formatting.
494 if (
495 str_contains( $content, '&#8211;' ) ||
496 str_contains( $content, '&#8212;' ) ||
497 str_contains( $content, '-' ) ||
498 str_contains( $content, '*' ) ||
499 str_contains( $content, '#' ) ||
500 str_contains( $content, '' ) || // ndash.
501 str_contains( $content, '' ) || // mdash.
502 preg_match( '/\d+\.\s/', $content )
503 ) {
504 // Remove breaks and extra whitespace.
505 $content = str_replace( "<br />\n", "\n", $content );
506 $content = trim( $content );
507
508 $ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\&#8211;|\&#8212;|\*)+\h+(.*)/mi';
509 $ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi';
510
511 preg_match_all( $ul_pattern, $content, $ul_matches );
512 preg_match_all( $ol_pattern, $content, $ol_matches );
513
514 if ( ( is_countable( $ul_matches[0] ) && count( $ul_matches[0] ) > 0 ) || ( is_countable( $ol_matches[0] ) && count( $ol_matches[0] ) > 0 ) ) {
515
516 if ( is_countable( $ol_matches[0] ) && count( $ol_matches[0] ) > 0 ) {
517 $listtype = 'ol';
518 $list_item_pattern = $ol_pattern;
519 } else {
520 $listtype = 'ul';
521 $list_item_pattern = $ul_pattern;
522 }
523 $html .= '<' . $listtype . $itemprop . '>';
524 $html .= preg_replace( $list_item_pattern, $list_item_replacement, $content );
525 $html .= '</' . $listtype . '>';
526
527 // Strip out any empty <p> tags and stray </p> tags, because those are just silly.
528 $empty_p_pattern = '/(<p>)*\s*<\/p>/mi';
529 $html = preg_replace( $empty_p_pattern, '', $html );
530 } else {
531 $html .= do_shortcode( $content );
532 }
533 } else {
534 $html .= do_shortcode( $content );
535 }
536
537 // Return our formatted content.
538 return $html;
539 }
540
541 /**
542 * Our [recipe-directions] shortcode.
543 * Outputs directions, styled in a div.
544 *
545 * @param array $atts Array of shortcode attributes.
546 * @param string $content Post content.
547 *
548 * @return string HTML for recipe directions shortcode.
549 */
550 public static function recipe_directions_shortcode( $atts, $content = '' ) {
551 $atts = shortcode_atts(
552 array(
553 'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), // string.
554 ),
555 $atts,
556 'recipe-directions'
557 );
558
559 $html = '<div class="jetpack-recipe-directions e-instructions">';
560
561 // Print a title unless the user has specified to exclude it.
562 if ( 'false' !== $atts['title'] ) {
563 $html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>';
564 }
565
566 // Format content using list functionality.
567 $html .= self::output_list_content( $content, 'directions' );
568
569 $html .= '</div>';
570
571 // Sanitize html.
572 $html = wp_kses( $html, self::kses_tags() );
573
574 // Return the HTML block.
575 return $html;
576 }
577
578 /**
579 * Outputs time meta tag.
580 *
581 * @param string $time_str Raw time to output.
582 * @param string $time_type Type of time to show.
583 *
584 * @return string HTML for recipe time meta.
585 */
586 private static function output_time( $time_str, $time_type ) {
587 // Get a time that's supported by Schema.org.
588 $duration = WPCOM_JSON_API_Date::format_duration( $time_str );
589 // If no duration can be calculated, let's output what the user provided.
590 if ( ! $duration ) {
591 $duration = $time_str;
592 }
593
594 switch ( $time_type ) {
595 case 'cooktime':
596 $title = _x( 'Cook Time', 'recipe', 'jetpack' );
597 $itemprop = 'cookTime';
598 break;
599 case 'preptime':
600 $title = _x( 'Prep Time', 'recipe', 'jetpack' );
601 $itemprop = 'prepTime';
602 break;
603 default:
604 $title = _x( 'Time', 'recipe', 'jetpack' );
605 $itemprop = 'totalTime';
606 break;
607 }
608
609 return sprintf(
610 '<li class="jetpack-recipe-%3$s">
611 <time itemprop="%4$s" datetime="%5$s"><strong>%1$s:</strong> <span class="%3$s">%2$s</span></time>
612 </li>',
613 esc_html( $title ),
614 esc_html( $time_str ),
615 esc_attr( $time_type ),
616 esc_attr( $itemprop ),
617 esc_attr( $duration )
618 );
619 }
620
621 /**
622 * Outputs image tag for recipe.
623 *
624 * @param string $src The image source.
625 *
626 * @return string
627 */
628 private static function output_image_html( $src ) {
629 // Exit if there is no provided source.
630 if ( ! $src ) {
631 return '';
632 }
633
634 $image_attrs = array(
635 'class' => 'jetpack-recipe-image u-photo photo',
636 'itemprop' => 'image',
637 );
638
639 if ( wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' ) ) {
640 $image_attrs['loading'] = 'lazy';
641 }
642
643 // If it's numeric, this may be an attachment.
644 if ( is_numeric( $src ) ) {
645 return wp_get_attachment_image(
646 $src,
647 'full',
648 false,
649 $image_attrs
650 );
651 }
652
653 // Check if it's an absolute or relative URL, and return if not.
654 if (
655 ! str_starts_with( $src, '/' )
656 && false === filter_var( $src, FILTER_VALIDATE_URL )
657 ) {
658 return '';
659 }
660
661 $image_attrs_markup = '';
662 foreach ( $image_attrs as $name => $value ) {
663 $image_attrs_markup .= sprintf(
664 ' %1$s="%2$s"',
665 esc_attr( $name ),
666 esc_attr( $value )
667 );
668 }
669
670 return sprintf(
671 '<img%1$s src="%2$s" />',
672 $image_attrs_markup,
673 esc_url( $src )
674 );
675 }
676
677 /**
678 * Use $themecolors array to style the Recipes shortcode
679 *
680 * @print style block
681 * @return string $style
682 */
683 public function themecolor_styles() {
684 global $themecolors;
685 $style = '';
686
687 if ( isset( $themecolors['border'] ) ) {
688 $style .= '.jetpack-recipe { border-color: #' . esc_attr( $themecolors['border'] ) . '; }';
689 }
690 if ( isset( $themecolors['link'] ) ) {
691 $style .= '.jetpack-recipe-title { border-bottom-color: #' . esc_attr( $themecolors['link'] ) . '; }';
692 }
693
694 return $style;
695 }
696 }
697
698 new Jetpack_Recipes();
699