diff options
Diffstat (limited to 'plugins/jetpack/modules/shortcodes/recipe.php')
-rw-r--r-- | plugins/jetpack/modules/shortcodes/recipe.php | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/plugins/jetpack/modules/shortcodes/recipe.php b/plugins/jetpack/modules/shortcodes/recipe.php new file mode 100644 index 00000000..7846b154 --- /dev/null +++ b/plugins/jetpack/modules/shortcodes/recipe.php @@ -0,0 +1,515 @@ +<?php + +/** + * Embed recipe 'cards' in post, with basic styling and print functionality + * + * To Do + * - defaults settings + * - basic styles/themecolor styles + * - validation/sanitization + * - print styles + */ +class Jetpack_Recipes { + + private $scripts_and_style_included = false; + + function __construct() { + add_action( 'init', array( $this, 'action_init' ) ); + + add_filter( 'wp_kses_allowed_html', array( $this, 'add_recipes_kses_rules' ), 10, 2 ); + } + + /** + * Add Schema-specific attributes to our allowed tags in wp_kses, + * so we can have better Schema.org compliance. + * + * @param array $allowedtags Array of allowed HTML tags in recipes. + * @param array $context Context to judge allowed tags by. + */ + function add_recipes_kses_rules( $allowedtags, $context ) { + if ( in_array( $context, array( '', 'post', 'data' ) ) ) : + // Create an array of all the tags we'd like to add the itemprop attribute to. + $tags = array( 'li', 'ol', 'ul', 'img', 'p', 'h3', 'time' ); + foreach ( $tags as $tag ) { + $allowedtags = $this->add_kses_rule( + $allowedtags, + $tag, + array( + 'class' => array(), + 'itemprop' => array(), + 'datetime' => array(), + ) + ); + } + + // Allow itemscope and itemtype for divs. + $allowedtags = $this->add_kses_rule( + $allowedtags, + 'div', + array( + 'class' => array(), + 'itemscope' => array(), + 'itemtype' => array(), + ) + ); + endif; + + return $allowedtags; + } + + /** + * Function to add a new property rule to our kses array. + * Used by add_recipe_kses_rules() above. + * + * @param array $all_tags Array of allowed HTML tags in recipes. + * @param string $tag New HTML tag to add to the array of allowed HTML. + * @param array $rules Array of allowed attributes for that HTML tag. + */ + private function add_kses_rule( $all_tags, $tag, $rules ) { + + // If the tag doesn't already exist, add it. + if ( ! isset( $all_tags[ $tag ] ) ) { + $all_tags[ $tag ] = array(); + } + + // Merge the new tags with existing tags. + $all_tags[ $tag ] = array_merge( $all_tags[ $tag ], $rules ); + + return $all_tags; + } + + /** + * Register our shortcode and enqueue necessary files. + */ + function action_init() { + // Enqueue styles if [recipe] exists. + add_action( 'wp_head', array( $this, 'add_scripts' ), 1 ); + + // Render [recipe], along with other shortcodes that can be nested within. + add_shortcode( 'recipe', array( $this, 'recipe_shortcode' ) ); + add_shortcode( 'recipe-notes', array( $this, 'recipe_notes_shortcode' ) ); + add_shortcode( 'recipe-ingredients', array( $this, 'recipe_ingredients_shortcode' ) ); + add_shortcode( 'recipe-directions', array( $this, 'recipe_directions_shortcode' ) ); + } + + /** + * Enqueue scripts and styles + */ + function add_scripts() { + if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) { + return; + } + + foreach ( $GLOBALS['posts'] as $p ) { + if ( has_shortcode( $p->post_content, 'recipe' ) ) { + $this->scripts_and_style_included = true; + break; + } + } + + if ( ! $this->scripts_and_style_included ) { + return; + } + + wp_enqueue_style( 'jetpack-recipes-style', plugins_url( '/css/recipes.css', __FILE__ ), array(), '20130919' ); + wp_style_add_data( 'jetpack-recipes-style', 'rtl', 'replace' ); + + // add $themecolors-defined styles. + wp_add_inline_style( 'jetpack-recipes-style', self::themecolor_styles() ); + + wp_enqueue_script( + 'jetpack-recipes-printthis', + Jetpack::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes-printthis.min.js', 'modules/shortcodes/js/recipes-printthis.js' ), + array( 'jquery' ), + '20170202' + ); + + wp_enqueue_script( + 'jetpack-recipes-js', + Jetpack::get_file_url_for_environment( '_inc/build/shortcodes/js/recipes.min.js', 'modules/shortcodes/js/recipes.js' ), + array( 'jquery', 'jetpack-recipes-printthis' ), + '20131230' + ); + + $title_var = wp_title( '|', false, 'right' ); + $rtl = is_rtl() ? '-rtl' : ''; + $print_css_var = plugins_url( "/css/recipes-print{$rtl}.css", __FILE__ ); + + wp_localize_script( + 'jetpack-recipes-js', + 'jetpack_recipes_vars', + array( + 'pageTitle' => $title_var, + 'loadCSS' => $print_css_var, + ) + ); + } + + /** + * Our [recipe] shortcode. + * Prints recipe data styled to look good on *any* theme. + * + * @param array $atts Array of shortcode attributes. + * @param string $content Post content. + * + * @return string HTML for recipe shortcode. + */ + static function recipe_shortcode( $atts, $content = '' ) { + $atts = shortcode_atts( + array( + 'title' => '', // string. + 'servings' => '', // intval. + 'time' => '', // string. + 'difficulty' => '', // string. + 'print' => '', // string. + 'source' => '', // string. + 'sourceurl' => '', // string. + 'image' => '', // string. + 'description' => '', // string. + ), + $atts, + 'recipe' + ); + + return self::recipe_shortcode_html( $atts, $content ); + } + + /** + * The recipe output + * + * @param array $atts Array of shortcode attributes. + * @param string $content Post content. + * + * @return string HTML output + */ + static function recipe_shortcode_html( $atts, $content = '' ) { + + $html = '<div class="hrecipe jetpack-recipe" itemscope itemtype="https://schema.org/Recipe">'; + + // Print the recipe title if exists. + if ( '' !== $atts['title'] ) { + $html .= '<h3 class="jetpack-recipe-title" itemprop="name">' . esc_html( $atts['title'] ) . '</h3>'; + } + + // Print the recipe meta if exists. + if ( '' !== $atts['servings'] || '' != $atts['time'] || '' != $atts['difficulty'] || '' != $atts['print'] ) { + $html .= '<ul class="jetpack-recipe-meta">'; + + if ( '' !== $atts['servings'] ) { + $html .= sprintf( + '<li class="jetpack-recipe-servings" itemprop="recipeYield"><strong>%1$s: </strong>%2$s</li>', + esc_html_x( 'Servings', 'recipe', 'jetpack' ), + esc_html( $atts['servings'] ) + ); + } + + if ( '' !== $atts['time'] ) { + // Get a time that's supported by Schema.org. + $duration = WPCOM_JSON_API_Date::format_duration( $atts['time'] ); + // If no duration can be calculated, let's output what the user provided. + if ( empty( $duration ) ) { + $duration = $atts['time']; + } + + $html .= sprintf( + '<li class="jetpack-recipe-time"> + <time itemprop="totalTime" datetime="%3$s"><strong>%1$s: </strong>%2$s</time> + </li>', + esc_html_x( 'Time', 'recipe', 'jetpack' ), + esc_html( $atts['time'] ), + esc_attr( $duration ) + ); + } + + if ( '' !== $atts['difficulty'] ) { + $html .= sprintf( + '<li class="jetpack-recipe-difficulty"><strong>%1$s: </strong>%2$s</li>', + esc_html_x( 'Difficulty', 'recipe', 'jetpack' ), + esc_html( $atts['difficulty'] ) + ); + } + + if ( '' !== $atts['source'] ) { + $html .= sprintf( + '<li class="jetpack-recipe-source"><strong>%1$s: </strong>', + esc_html_x( 'Source', 'recipe', 'jetpack' ) + ); + + if ( '' !== $atts['sourceurl'] ) : + // Show the link if we have one. + $html .= sprintf( + '<a href="%2$s">%1$s</a>', + esc_html( $atts['source'] ), + esc_url( $atts['sourceurl'] ) + ); + else : + // Skip the link. + $html .= sprintf( + '%1$s', + esc_html( $atts['source'] ) + ); + endif; + + $html .= '</li>'; + } + + if ( 'false' !== $atts['print'] ) { + $html .= sprintf( + '<li class="jetpack-recipe-print"><a href="#">%1$s</a></li>', + esc_html_x( 'Print', 'recipe', 'jetpack' ) + ); + } + + $html .= '</ul>'; + } // End if(). + + // Output the image, if we have one. + if ( '' !== $atts['image'] ) { + $html .= sprintf( + '<img class="jetpack-recipe-image" itemprop="image" src="%1$s" />', + esc_url( $atts['image'] ) + ); + } + + // Output the description, if we have one. + if ( '' !== $atts['description'] ) { + $html .= sprintf( + '<p class="jetpack-recipe-description" itemprop="description">%1$s</p>', + esc_html( $atts['description'] ) + ); + } + + // Print content between codes. + $html .= '<div class="jetpack-recipe-content">' . do_shortcode( $content ) . '</div>'; + + // Close it up. + $html .= '</div>'; + + // If there is a recipe within a recipe, remove the shortcode. + if ( has_shortcode( $html, 'recipe' ) ) { + remove_shortcode( 'recipe' ); + } + + // Sanitize html. + $html = wp_kses_post( $html ); + + // Return the HTML block. + return $html; + } + + /** + * Our [recipe-notes] shortcode. + * Outputs ingredients, styled in a div. + * + * @param array $atts Array of shortcode attributes. + * @param string $content Post content. + * + * @return string HTML for recipe notes shortcode. + */ + static function recipe_notes_shortcode( $atts, $content = '' ) { + $atts = shortcode_atts( + array( + 'title' => '', // string. + ), + $atts, + 'recipe-notes' + ); + + $html = ''; + + // Print a title if one exists. + if ( '' !== $atts['title'] ) { + $html .= '<h4 class="jetpack-recipe-notes-title">' . esc_html( $atts['title'] ) . '</h4>'; + } + + $html .= '<div class="jetpack-recipe-notes">'; + + // Format content using list functionality, if desired. + $html .= self::output_list_content( $content, 'notes' ); + + $html .= '</div>'; + + // Sanitize html. + $html = wp_kses_post( $html ); + + // Return the HTML block. + return $html; + } + + /** + * Our [recipe-ingredients] shortcode. + * Outputs notes, styled in a div. + * + * @param array $atts Array of shortcode attributes. + * @param string $content Post content. + * + * @return string HTML for recipe ingredients shortcode. + */ + static function recipe_ingredients_shortcode( $atts, $content = '' ) { + $atts = shortcode_atts( + array( + 'title' => esc_html_x( 'Ingredients', 'recipe', 'jetpack' ), // string. + ), + $atts, + 'recipe-ingredients' + ); + + $html = '<div class="jetpack-recipe-ingredients">'; + + // Print a title unless the user has opted to exclude it. + if ( 'false' !== $atts['title'] ) { + $html .= '<h4 class="jetpack-recipe-ingredients-title">' . esc_html( $atts['title'] ) . '</h4>'; + } + + // Format content using list functionality. + $html .= self::output_list_content( $content, 'ingredients' ); + + $html .= '</div>'; + + // Sanitize html. + $html = wp_kses_post( $html ); + + // Return the HTML block. + return $html; + } + + /** + * Reusable function to check for shortened formatting. + * Basically, users can create lists with the following shorthand: + * - item one + * - item two + * - item three + * And we'll magically convert it to a list. This has the added benefit + * of including itemprops for the recipe schema. + * + * @param string $content HTML content. + * @param string $type Type of list. + * + * @return string content formatted as a list item + */ + static function output_list_content( $content, $type ) { + $html = ''; + + switch ( $type ) { + case 'directions': + $list_item_replacement = '<li class="jetpack-recipe-directions">${1}</li>'; + $itemprop = ' itemprop="recipeInstructions"'; + $listtype = 'ol'; + break; + case 'ingredients': + $list_item_replacement = '<li class="jetpack-recipe-ingredient" itemprop="recipeIngredient">${1}</li>'; + $itemprop = ''; + $listtype = 'ul'; + break; + default: + $list_item_replacement = '<li class="jetpack-recipe-notes">${1}</li>'; + $itemprop = ''; + $listtype = 'ul'; + } + + // Check to see if the user is trying to use shortened formatting. + if ( + strpos( $content, '–' ) !== false || + strpos( $content, '—' ) !== false || + strpos( $content, '-' ) !== false || + strpos( $content, '*' ) !== false || + strpos( $content, '#' ) !== false || + strpos( $content, '–' ) !== false || // ndash. + strpos( $content, '—' ) !== false || // mdash. + preg_match( '/\d+\.\s/', $content ) + ) { + // Remove breaks and extra whitespace. + $content = str_replace( "<br />\n", "\n", $content ); + $content = trim( $content ); + + $ul_pattern = '/(?:^|\n|\<p\>)+(?:[\-–—]+|\–|\—|\*)+\h+(.*)/mi'; + $ol_pattern = '/(?:^|\n|\<p\>)+(?:\d+\.|#+)+\h+(.*)/mi'; + + preg_match_all( $ul_pattern, $content, $ul_matches ); + preg_match_all( $ol_pattern, $content, $ol_matches ); + + if ( 0 !== count( $ul_matches[0] ) || 0 !== count( $ol_matches[0] ) ) { + + if ( 0 !== count( $ol_matches[0] ) ) { + $listtype = 'ol'; + $list_item_pattern = $ol_pattern; + } else { + $listtype = 'ul'; + $list_item_pattern = $ul_pattern; + } + $html .= '<' . $listtype . $itemprop . '>'; + $html .= preg_replace( $list_item_pattern, $list_item_replacement, $content ); + $html .= '</' . $listtype . '>'; + + // Strip out any empty <p> tags and stray </p> tags, because those are just silly. + $empty_p_pattern = '/(<p>)*\s*<\/p>/mi'; + $html = preg_replace( $empty_p_pattern, '', $html ); + } else { + $html .= do_shortcode( $content ); + } + } else { + $html .= do_shortcode( $content ); + } + + // Return our formatted content. + return $html; + } + + /** + * Our [recipe-directions] shortcode. + * Outputs directions, styled in a div. + * + * @param array $atts Array of shortcode attributes. + * @param string $content Post content. + * + * @return string HTML for recipe directions shortcode. + */ + static function recipe_directions_shortcode( $atts, $content = '' ) { + $atts = shortcode_atts( + array( + 'title' => esc_html_x( 'Directions', 'recipe', 'jetpack' ), // string. + ), + $atts, + 'recipe-directions' + ); + + $html = '<div class="jetpack-recipe-directions">'; + + // Print a title unless the user has specified to exclude it. + if ( 'false' !== $atts['title'] ) { + $html .= '<h4 class="jetpack-recipe-directions-title">' . esc_html( $atts['title'] ) . '</h4>'; + } + + // Format content using list functionality. + $html .= self::output_list_content( $content, 'directions' ); + + $html .= '</div>'; + + // Sanitize html. + $html = wp_kses_post( $html ); + + // Return the HTML block. + return $html; + } + + /** + * Use $themecolors array to style the Recipes shortcode + * + * @print style block + * @return string $style + */ + function themecolor_styles() { + global $themecolors; + $style = ''; + + if ( isset( $themecolors ) ) { + $style .= '.jetpack-recipe { border-color: #' . esc_attr( $themecolors['border'] ) . '; }'; + $style .= '.jetpack-recipe-title { border-bottom-color: #' . esc_attr( $themecolors['link'] ) . '; }'; + } + + return $style; + } + +} + +new Jetpack_Recipes(); |