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