暫無描述

recipe.php 19KB

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