PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 14.9.2
Jetpack – WP Security, Backup, Speed, & Growth v14.9.2
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 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 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 year ago images 2 years ago img 2 years ago js 1 year ago archiveorg-book.php 5 years ago archiveorg.php 5 years ago archives.php 5 years ago bandcamp.php 3 years ago brightcove.php 5 years ago cartodb.php 5 years ago class.filter-embedded-html-objects.php 1 year ago codepen.php 5 years ago crowdsignal.php 1 year ago dailymotion.php 3 years ago descript.php 4 years ago facebook.php 1 year ago flatio.php 5 years ago flickr.php 1 year ago getty.php 2 years ago gist.php 1 year ago googleapps.php 2 years ago googlemaps.php 2 years ago googleplus.php 5 years ago gravatar.php 2 years ago houzz.php 5 years ago inline-pdfs.php 4 years ago instagram.php 1 year ago kickstarter.php 3 years ago mailchimp.php 3 years ago medium.php 3 years ago mixcloud.php 1 year ago others.php 1 year ago pinterest.php 2 years ago presentations.php 2 years ago quiz.php 4 years ago recipe.php 1 year ago scribd.php 5 years ago sitemap.php 5 years ago slideshare.php 5 years ago slideshow.php 1 year ago smartframe.php 3 years ago soundcloud.php 3 years ago spotify.php 2 years ago ted.php 5 years ago tweet.php 2 years ago twitchtv.php 5 years ago twitter-timeline.php 5 years ago twitter.php 1 year ago unavailable.php 1 year ago untappd-menu.php 1 year ago upcoming-events.php 1 year ago ustream.php 5 years ago videopress.php 4 years ago vimeo.php 2 years ago vine.php 5 years ago vr.php 2 years ago wufoo.php 3 years ago youtube.php 1 year ago
recipe.php
700 lines
1 <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3 use Automattic\Jetpack\Assets;
4
5 /**
6 * Embed recipe 'cards' in post, with basic styling and print functionality
7 *
8 * To Do
9 * - defaults settings
10 * - basic styles/themecolor styles
11 * - validation/sanitization
12 * - print styles
13 *
14 * @package automattic/jetpack
15 */
16
17 /**
18 * Register and display Recipes in posts.
19 *
20 * @phan-constructor-used-for-side-effects
21 */
22 class Jetpack_Recipes {
23
24 /**
25 * Have scripts and styles been enqueued already.
26 *
27 * @var bool
28 */
29 private $scripts_and_style_included = false;
30
31 /**
32 * Constructor
33 */
34 public function __construct() {
35 add_action( 'init', array( $this, 'action_init' ) );
36 }
37
38 /**
39 * Returns KSES tags with Schema-specific attributes.
40 *
41 * @since 8.0.0
42 *
43 * @return array Array to be used by KSES.
44 */
45 private static function kses_tags() {
46 $allowedtags = wp_kses_allowed_html( 'post' );
47 // Create an array of all the tags we'd like to add the itemprop attribute to.
48 $tags = array( 'li', 'ol', 'ul', 'img', 'p', 'h3', 'time', 'span' );
49 foreach ( $tags as $tag ) {
50 if ( ! isset( $allowedtags[ $tag ] ) ) {
51 $allowedtags[ $tag ] = array();
52 }
53 $allowedtags[ $tag ]['class'] = array();
54 $allowedtags[ $tag ]['itemprop'] = array();
55 $allowedtags[ $tag ]['datetime'] = array();
56 }
57
58 // Allow the handler <a on=""> in AMP.
59 $allowedtags['a']['on'] = array();
60
61 // Allow itemscope and itemtype for divs.
62 if ( ! isset( $allowedtags['div'] ) ) {
63 $allowedtags['div'] = array();
64 }
65 $allowedtags['div']['class'] = array();
66 $allowedtags['div']['itemscope'] = array();
67 $allowedtags['div']['itemtype'] = array();
68 return $allowedtags;
69 }
70
71 /**
72 * Register our shortcode and enqueue necessary files.
73 */
74 public function action_init() {
75 // Enqueue styles if [recipe] exists.
76 add_action( 'wp_head', array( $this, 'add_scripts' ), 1 );
77
78 // Render [recipe], along with other shortcodes that can be nested within.
79 add_shortcode( 'recipe', array( $this, 'recipe_shortcode' ) );
80 add_shortcode( 'recipe-notes', array( $this, 'recipe_notes_shortcode' ) );
81 add_shortcode( 'recipe-ingredients', array( $this, 'recipe_ingredients_shortcode' ) );
82 add_shortcode( 'recipe-directions', array( $this, 'recipe_directions_shortcode' ) );
83 add_shortcode( 'recipe-nutrition', array( $this, 'recipe_nutrition_shortcode' ) );
84 add_shortcode( 'recipe-image', array( $this, 'recipe_image_shortcode' ) );
85 }
86
87 /**
88 * Enqueue scripts and styles
89 */
90 public function add_scripts() {
91 if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) {
92 return;
93 }
94
95 foreach ( $GLOBALS['posts'] as $p ) {
96 if ( isset( $p->post_content ) && has_shortcode( $p->post_content, 'recipe' ) ) {
97 $this->scripts_and_style_included = true;
98 break;
99 }
100 }
101
102 if ( ! $this->scripts_and_style_included ) {
103 return;
104 }
105
106 wp_enqueue_style( 'jetpack-recipes-style', plugins_url( '/css/recipes.css', __FILE__ ), array(), '20130919' );
107 wp_style_add_data( 'jetpack-recipes-style', 'rtl', 'replace' );
108
109 // add $themecolors-defined styles.
110 wp_add_inline_style( 'jetpack-recipes-style', self::themecolor_styles() );
111
112 if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
113 return;
114 }
115
116 wp_enqueue_script(
117 'jetpack-recipes-printthis',
118 Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes-printthis.min.js', 'modules/shortcodes/js/recipes-printthis.js' ),
119 array( 'jquery' ),
120 '20170202',
121 false
122 );
123
124 wp_enqueue_script(
125 'jetpack-recipes-js',
126 Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes.min.js', 'modules/shortcodes/js/recipes.js' ),
127 array( 'jquery', 'jetpack-recipes-printthis' ),
128 '20131230',
129 false
130 );
131
132 $title_var = wp_title( '|', false, 'right' );
133 $rtl = is_rtl() ? '-rtl' : '';
134 $print_css_var = plugins_url( "/css/recipes-print{$rtl}.css", __FILE__ );
135
136 wp_localize_script(
137 'jetpack-recipes-js',
138 'jetpack_recipes_vars',
139 array(
140 'pageTitle' => $title_var,
141 'loadCSS' => $print_css_var,
142 )
143 );
144 }
145
146 /**
147 * Our [recipe] shortcode.
148 * Prints recipe data styled to look good on *any* theme.
149 *
150 * @param array $atts Array of shortcode attributes.
151 * @param string $content Post content.
152 *
153 * @return string HTML for recipe shortcode.
154 */
155 public static function recipe_shortcode( $atts, $content = '' ) {
156 $atts = shortcode_atts(
157 array(
158 'title' => '', // string.
159 'servings' => '', // intval.
160 'time' => '', // strtotime-compatible time description.
161 'difficulty' => '', // string.
162 'print' => '', // URL for external print version.
163 'source' => '', // string.
164 'sourceurl' => '', // URL string. Only used if source set.
165 'image' => '', // URL or attachment ID.
166 'description' => '', // string.
167 'cooktime' => '', // strtotime-compatible time description.
168 'preptime' => '', // strtotime-compatible time description.
169 'rating' => '', // string.
170 ),
171 $atts,
172 'recipe'
173 );
174
175 return self::recipe_shortcode_html( $atts, $content );
176 }
177
178 /**
179 * The recipe output
180 *
181 * @param array $atts Array of shortcode attributes.
182 * @param string $content Post content.
183 *
184 * @return string HTML output
185 */
186 private static function recipe_shortcode_html( $atts, $content = '' ) {
187
188 $html = '<div class="hrecipe h-recipe jetpack-recipe" itemscope itemtype="https://schema.org/Recipe">';
189
190 // Print the recipe title if exists.
191 if ( '' !== $atts['title'] ) {
192 $html .= '<h3 class="p-name jetpack-recipe-title fn" itemprop="name">' . esc_html( $atts['title'] ) . '</h3>';
193 }
194
195 // Print the recipe meta if exists.
196 if (
197 '' !== $atts['servings']
198 || '' !== $atts['time']
199 || '' !== $atts['difficulty']
200 || '' !== $atts['print']
201 || '' !== $atts['preptime']
202 || '' !== $atts['cooktime']
203 || '' !== $atts['rating']
204 ) {
205 $html .= '<ul class="jetpack-recipe-meta">';
206
207 if ( '' !== $atts['servings'] ) {
208 $html .= sprintf(
209 '<li class="jetpack-recipe-servings p-yield yield" itemprop="recipeYield"><strong>%1$s: </strong>%2$s</li>',
210 esc_html_x( 'Servings', 'recipe', 'jetpack' ),
211 esc_html( $atts['servings'] )
212 );
213 }
214
215 $time_types = array( 'preptime', 'cooktime', 'time' );
216 foreach ( $time_types as $time_type ) {
217 if ( '' === $atts[ $time_type ] ) {
218 continue;
219 }
220 $html .= self::output_time( $atts[ $time_type ], $time_type );
221 }
222
223 if ( '' !== $atts['difficulty'] ) {
224 $html .= sprintf(
225 '<li class="jetpack-recipe-difficulty"><strong>%1$s: </strong>%2$s</li>',
226 esc_html_x( 'Difficulty', 'recipe', 'jetpack' ),
227 esc_html( $atts['difficulty'] )
228 );
229 }
230
231 if ( '' !== $atts['rating'] ) {
232 $html .= sprintf(
233 '<li class="jetpack-recipe-rating">
234 <strong>%1$s: </strong>
235 <span itemprop="contentRating">%2$s</span>
236 </li>',
237 esc_html_x( 'Rating', 'recipe', 'jetpack' ),
238 esc_html( $atts['rating'] )
239 );
240 }
241
242 if ( '' !== $atts['source'] ) {
243 $html .= sprintf(
244 '<li class="jetpack-recipe-source"><strong>%1$s: </strong>',
245 esc_html_x( 'Source', 'recipe', 'jetpack' )
246 );
247
248 if ( '' !== $atts['sourceurl'] ) :
249 // Show the link if we have one.
250 $html .= sprintf(
251 '<a href="%2$s">%1$s</a>',
252 esc_html( $atts['source'] ),
253 esc_url( $atts['sourceurl'] )
254 );
255 else :
256 // Skip the link.
257 $html .= sprintf(
258 '%1$s',
259 esc_html( $atts['source'] )
260 );
261 endif;
262
263 $html .= '</li>';
264 }
265
266 if ( 'false' !== $atts['print'] ) {
267 $is_amp = class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request();
268 $print_action = $is_amp ? 'on="tap:AMP.print"' : '';
269 $print_text = $is_amp ? esc_html__( 'Print page', 'jetpack' ) : esc_html_x( 'Print', 'recipe', 'jetpack' );
270 $html .= sprintf(
271 '<li class="jetpack-recipe-print"><a href="#" %1$s>%2$s</a></li>',
272 $print_action,
273 $print_text
274 );
275 }
276
277 $html .= '</ul>';
278 }
279
280 // Output the image if we have one and it's not shown elsewhere.
281 if ( '' !== $atts['image'] ) {
282 if ( ! has_shortcode( $content, 'recipe-image' ) ) {
283 $html .= self::output_image_html( $atts['image'] );
284 }
285 }
286
287 // Output the description, if we have one.
288 if ( '' !== $atts['description'] ) {
289 $html .= sprintf(
290 '<p class="jetpack-recipe-description" itemprop="description">%1$s</p>',
291 esc_html( $atts['description'] )
292 );
293 }
294
295 // Print content between codes.
296 $html .= '<div class="jetpack-recipe-content">' . do_shortcode( $content ) . '</div>';
297
298 // Close it up.
299 $html .= '</div>';
300
301 // If there is a recipe within a recipe, remove the shortcode.
302 if ( has_shortcode( $html, 'recipe' ) ) {
303 remove_shortcode( 'recipe' );
304 }
305
306 // Sanitize html.
307 $html = wp_kses( $html, self::kses_tags() );
308
309 // Return the HTML block.
310 return $html;
311 }
312
313 /**
314 * Our [recipe-image] shortcode.
315 * Controls placement of image in recipe.
316 *
317 * @param array $atts Array of shortcode attributes.
318 *
319 * @return string HTML for recipe notes shortcode.
320 */
321 public static function recipe_image_shortcode( $atts ) {
322 $atts = shortcode_atts(
323 array(
324 'image' => '', // string.
325 0 => '', // string.
326 ),
327 $atts,
328 'recipe-image'
329 );
330 $src = $atts['image'];
331 if ( ! empty( $atts[0] ) ) {
332 $src = $atts[0];
333 }
334 return self::output_image_html( $src );
335 }
336
337 /**
338 * Our [recipe-notes] shortcode.
339 * Outputs ingredients, styled in a div.
340 *
341 * @param array $atts Array of shortcode attributes.
342 * @param string $content Post content.
343 *
344 * @return string HTML for recipe notes shortcode.
345 */
346 public static function recipe_notes_shortcode( $atts, $content = '' ) {
347 $atts = shortcode_atts(
348 array(
349 'title' => '', // string.
350 ),
351 $atts,
352 'recipe-notes'
353 );
354
355 $html = '';
356
357 // Print a title if one exists.
358 if ( '' !== $atts['title'] ) {
359 $html .= '<h4 class="jetpack-recipe-notes-title">' . esc_html( $atts['title'] ) . '</h4>';
360 }
361
362 $html .= '<div class="jetpack-recipe-notes">';
363
364 // Format content using list functionality, if desired.
365 $html .= self::output_list_content( $content, 'notes' );
366
367 $html .= '</div>';
368
369 // Sanitize html.
370 $html = wp_kses( $html, self::kses_tags() );
371
372 // Return the HTML block.
373 return $html;
374 }
375
376 /**
377 * Our [recipe-ingredients] shortcode.
378 * Outputs notes, styled in a div.
379 *
380 * @param array $atts Array of shortcode attributes.
381 * @param string $content Post content.
382 *
383 * @return string HTML for recipe ingredients shortcode.
384 */
385 public static function recipe_ingredients_shortcode( $atts, $content = '' ) {
386 $atts = shortcode_atts(
387 array(
388 'title' => esc_html_x( 'Ingredients', 'recipe', 'jetpack' ), // string.
389 ),
390 $atts,
391 'recipe-ingredients'
392 );
393
394 $html = '<div class="jetpack-recipe-ingredients">';
395
396 // Print a title unless the user has opted to exclude it.
397 if ( 'false' !== $atts['title'] ) {
398 $html .= '<h4 class="jetpack-recipe-ingredients-title">' . esc_html( $atts['title'] ) . '</h4>';
399 }
400
401 // Format content using list functionality.
402 $html .= self::output_list_content( $content, 'ingredients' );
403
404 $html .= '</div>';
405
406 // Sanitize html.
407 $html = wp_kses( $html, self::kses_tags() );
408
409 // Return the HTML block.
410 return $html;
411 }
412
413 /**
414 * Our [recipe-nutrition] shortcode.
415 * Outputs notes, styled in a div.
416 *
417 * @param array $atts Array of shortcode attributes.
418 * @param string $content Post content.
419 *
420 * @return string HTML for recipe nutrition shortcode.
421 */
422 public static function recipe_nutrition_shortcode( $atts, $content = '' ) {
423 $atts = shortcode_atts(
424 array(
425 'title' => esc_html_x( 'Nutrition', 'recipe', 'jetpack' ), // string.
426 ),
427 $atts,
428 'recipe-nutrition'
429 );
430
431 $html = '<div class="jetpack-recipe-nutrition p-nutrition nutrition">';
432
433 // Print a title unless the user has opted to exclude it.
434 if ( 'false' !== $atts['title'] ) {
435 $html .= '<h4 class="jetpack-recipe-nutrition-title">' . esc_html( $atts['title'] ) . '</h4>';
436 }
437
438 // Format content using list functionality.
439 $html .= self::output_list_content( $content, 'nutrition' );
440
441 $html .= '</div>';
442
443 // Sanitize html.
444 $html = wp_kses( $html, self::kses_tags() );
445
446 // Return the HTML block.
447 return $html;
448 }
449
450 /**
451 * Reusable function to check for shortened formatting.
452 * Basically, users can create lists with the following shorthand:
453 * - item one
454 * - item two
455 * - item three
456 * And we'll magically convert it to a list. This has the added benefit
457 * of including itemprops for the recipe schema.
458 *
459 * @param string $content HTML content.
460 * @param string $type Type of list.
461 *
462 * @return string content formatted as a list item
463 */
464 private static function output_list_content( $content, $type ) {
465 $html = '';
466
467 switch ( $type ) {
468 case 'directions':
469 $list_item_replacement = '<li class="jetpack-recipe-directions">${1}</li>';
470 $itemprop = ' itemprop="recipeInstructions"';
471 $listtype = 'ol';
472 break;
473 case 'ingredients':
474 $list_item_replacement = '<li class="jetpack-recipe-ingredient p-ingredient ingredient" itemprop="recipeIngredient">${1}</li>';
475 $itemprop = '';
476 $listtype = 'ul';
477 break;
478 case 'nutrition':
479 $list_item_replacement = '<li class="jetpack-recipe-nutrition">${1}</li>';
480 $itemprop = ' itemprop="nutrition"';
481 $listtype = 'ul';
482 break;
483 case 'nutrition':
484 $list_item_replacement = '<li class="jetpack-recipe-nutrition nutrition">${1}</li>';
485 $itemprop = ' itemprop="nutrition"';
486 $listtype = 'ul';
487 break;
488 default:
489 $list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>';
490 $itemprop = '';
491 $listtype = 'ul';
492 }
493
494 // Check to see if the user is trying to use shortened formatting.
495 if (
496 str_contains( $content, '&#8211;' ) ||
497 str_contains( $content, '&#8212;' ) ||
498 str_contains( $content, '-' ) ||
499 str_contains( $content, '*' ) ||
500 str_contains( $content, '#' ) ||
501 str_contains( $content, '' ) || // ndash.
502 str_contains( $content, '' ) || // mdash.
503 preg_match( '/\d+\.\s/', $content )
504 ) {
505 // Remove breaks and extra whitespace.
506 $content = str_replace( "<br />\n", "\n", $content );
507 $content = trim( $content );
508
509 $ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\&#8211;|\&#8212;|\*)+\h+(.*)/mi';
510 $ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi';
511
512 preg_match_all( $ul_pattern, $content, $ul_matches );
513 preg_match_all( $ol_pattern, $content, $ol_matches );
514
515 if ( ( is_countable( $ul_matches[0] ) && count( $ul_matches[0] ) > 0 ) || ( is_countable( $ol_matches[0] ) && count( $ol_matches[0] ) > 0 ) ) {
516
517 if ( is_countable( $ol_matches[0] ) && count( $ol_matches[0] ) > 0 ) {
518 $listtype = 'ol';
519 $list_item_pattern = $ol_pattern;
520 } else {
521 $listtype = 'ul';
522 $list_item_pattern = $ul_pattern;
523 }
524 $html .= '<' . $listtype . $itemprop . '>';
525 $html .= preg_replace( $list_item_pattern, $list_item_replacement, $content );
526 $html .= '</' . $listtype . '>';
527
528 // Strip out any empty <p> tags and stray </p> tags, because those are just silly.
529 $empty_p_pattern = '/(<p>)*\s*<\/p>/mi';
530 $html = preg_replace( $empty_p_pattern, '', $html );
531 } else {
532 $html .= do_shortcode( $content );
533 }
534 } else {
535 $html .= do_shortcode( $content );
536 }
537
538 // Return our formatted content.
539 return $html;
540 }
541
542 /**
543 * Our [recipe-directions] shortcode.
544 * Outputs directions, styled in a div.
545 *
546 * @param array $atts Array of shortcode attributes.
547 * @param string $content Post content.
548 *
549 * @return string HTML for recipe directions shortcode.
550 */
551 public static function recipe_directions_shortcode( $atts, $content = '' ) {
552 $atts = shortcode_atts(
553 array(
554 'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), // string.
555 ),
556 $atts,
557 'recipe-directions'
558 );
559
560 $html = '<div class="jetpack-recipe-directions e-instructions">';
561
562 // Print a title unless the user has specified to exclude it.
563 if ( 'false' !== $atts['title'] ) {
564 $html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>';
565 }
566
567 // Format content using list functionality.
568 $html .= self::output_list_content( $content, 'directions' );
569
570 $html .= '</div>';
571
572 // Sanitize html.
573 $html = wp_kses( $html, self::kses_tags() );
574
575 // Return the HTML block.
576 return $html;
577 }
578
579 /**
580 * Outputs time meta tag.
581 *
582 * @param string $time_str Raw time to output.
583 * @param string $time_type Type of time to show.
584 *
585 * @return string HTML for recipe time meta.
586 */
587 private static function output_time( $time_str, $time_type ) {
588 // Get a time that's supported by Schema.org.
589 $duration = WPCOM_JSON_API_Date::format_duration( $time_str );
590 // If no duration can be calculated, let's output what the user provided.
591 if ( ! $duration ) {
592 $duration = $time_str;
593 }
594
595 switch ( $time_type ) {
596 case 'cooktime':
597 $title = _x( 'Cook Time', 'recipe', 'jetpack' );
598 $itemprop = 'cookTime';
599 break;
600 case 'preptime':
601 $title = _x( 'Prep Time', 'recipe', 'jetpack' );
602 $itemprop = 'prepTime';
603 break;
604 default:
605 $title = _x( 'Time', 'recipe', 'jetpack' );
606 $itemprop = 'totalTime';
607 break;
608 }
609
610 return sprintf(
611 '<li class="jetpack-recipe-%3$s">
612 <time itemprop="%4$s" datetime="%5$s"><strong>%1$s:</strong> <span class="%3$s">%2$s</span></time>
613 </li>',
614 esc_html( $title ),
615 esc_html( $time_str ),
616 esc_attr( $time_type ),
617 esc_attr( $itemprop ),
618 esc_attr( $duration )
619 );
620 }
621
622 /**
623 * Outputs image tag for recipe.
624 *
625 * @param string $src The image source.
626 *
627 * @return string
628 */
629 private static function output_image_html( $src ) {
630 // Exit if there is no provided source.
631 if ( ! $src ) {
632 return '';
633 }
634
635 $image_attrs = array(
636 'class' => 'jetpack-recipe-image u-photo photo',
637 'itemprop' => 'image',
638 );
639
640 if ( wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' ) ) {
641 $image_attrs['loading'] = 'lazy';
642 }
643
644 // If it's numeric, this may be an attachment.
645 if ( is_numeric( $src ) ) {
646 return wp_get_attachment_image(
647 $src,
648 'full',
649 false,
650 $image_attrs
651 );
652 }
653
654 // Check if it's an absolute or relative URL, and return if not.
655 if (
656 ! str_starts_with( $src, '/' )
657 && false === filter_var( $src, FILTER_VALIDATE_URL )
658 ) {
659 return '';
660 }
661
662 $image_attrs_markup = '';
663 foreach ( $image_attrs as $name => $value ) {
664 $image_attrs_markup .= sprintf(
665 ' %1$s="%2$s"',
666 esc_attr( $name ),
667 esc_attr( $value )
668 );
669 }
670
671 return sprintf(
672 '<img%1$s src="%2$s" />',
673 $image_attrs_markup,
674 esc_url( $src )
675 );
676 }
677
678 /**
679 * Use $themecolors array to style the Recipes shortcode
680 *
681 * @print style block
682 * @return string $style
683 */
684 public function themecolor_styles() {
685 global $themecolors;
686 $style = '';
687
688 if ( isset( $themecolors['border'] ) ) {
689 $style .= '.jetpack-recipe { border-color: #' . esc_attr( $themecolors['border'] ) . '; }';
690 }
691 if ( isset( $themecolors['link'] ) ) {
692 $style .= '.jetpack-recipe-title { border-bottom-color: #' . esc_attr( $themecolors['link'] ) . '; }';
693 }
694
695 return $style;
696 }
697 }
698
699 new Jetpack_Recipes();
700