ProductSummary.php
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* ProductSummary class.
*/
class ProductSummary extends AbstractBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'product-summary';
/**
* API version name.
*
* @var string
*/
protected $api_version = '2';
/**
* Get block supports. Shared with the frontend.
* IMPORTANT: If you change anything here, make sure to update the JS file too.
*
* @return array
*/
protected function get_block_type_supports() {
return array(
'color' =>
array(
'link' => true,
'background' => false,
'text' => true,
),
'typography' =>
array(
'fontSize' => true,
),
'__experimentalSelector' => '.wc-block-components-product-summary',
);
}
/**
* Overwrite parent method to prevent script registration.
*
* It is necessary to register and enqueues assets during the render
* phase because we want to load assets only if the block has the content.
*/
protected function register_block_type_assets() {
return null;
}
/**
* Register the context.
*/
protected function get_block_type_uses_context() {
return [ 'query', 'queryId', 'postId' ];
}
/**
* Get product description depends on config.
*
* @param WC_Product $product Product object.
* @param bool $show_description_if_empty Defines if fallback to full description.
* @return string
*/
private function get_source( $product, $show_description_if_empty ) {
$short_description = $product->get_short_description();
if ( $short_description ) {
// Logic copied from https://github.com/woocommerce/woocommerce/blob/637dde283057ed6667ff81c73ed08774552f631d/plugins/woocommerce/includes/wc-core-functions.php#L53-L62.
$short_description = wp_kses_post( $short_description );
$short_description = $GLOBALS['wp_embed']->run_shortcode( $short_description );
$short_description = shortcode_unautop( $short_description );
$short_description = do_shortcode( $short_description );
return $short_description;
}
$description = $product->get_description();
if ( $show_description_if_empty && $description ) {
// Logic copied from https://github.com/woocommerce/woocommerce/blob/637dde283057ed6667ff81c73ed08774552f631d/plugins/woocommerce/includes/wc-core-functions.php#L53-L62.
$description = wp_kses_post( $description );
$description = $GLOBALS['wp_embed']->run_shortcode( $description );
$description = shortcode_unautop( $description );
$description = do_shortcode( $description );
return $description;
}
return '';
}
/**
* Create anchor element based on input.
*
* @param WC_Product $product Product object.
* @param string $link_text Link text.
* @return string
*/
private function create_anchor( $product, $link_text ) {
$href = esc_url( $product->get_permalink() );
$text = wp_kses_post( $link_text );
return '<a class="wp-block-woocommerce-product-summary__read_more" href="' . $href . '#tab-description">' . $text . '</a>';
}
/**
* Get first paragraph from some HTML text, or return the whole string.
*
* @param string $source Source text.
* @return string First paragraph found in string.
*/
private function get_first_paragraph( $source ) {
$p_index = strpos( $source, '</p>' );
if ( false === $p_index ) {
return $source;
}
return substr( $source, 0, $p_index + 4 );
}
/**
* Count words, characters (excluding spaces), or characters (including spaces).
*
* @param string $text Text to count.
* @param string $count_type Count type: 'words', 'characters_excluding_spaces', or 'characters_including_spaces'.
* @return int Count of specified type.
*/
private function count_text( $text, $count_type ) {
switch ( $count_type ) {
case 'characters_excluding_spaces':
return strlen( preg_replace( '/\s+/', '', $text ) );
case 'characters_including_spaces':
return strlen( $text );
case 'words':
default:
return str_word_count( wp_strip_all_tags( $text ) );
}
}
/**
* Trim characters to a specified length.
*
* @param string $text Text to trim.
* @param int $max_length Maximum length of the text.
* @param string $count_type What is being counted. One of 'words', 'characters_excluding_spaces', or 'characters_including_spaces'.
* @return string Trimmed text.
*/
private function trim_characters( $text, $max_length, $count_type ) {
$pure_text = wp_strip_all_tags( $text );
$trimmed = mb_substr( $pure_text, 0, $max_length );
if ( 'characters_including_spaces' === $count_type ) {
return $trimmed;
}
preg_match_all( '/([\s]+)/', $trimmed, $spaces );
$space_count = ! empty( $spaces[0] ) ? count( $spaces[0] ) : 0;
return mb_substr( $pure_text, 0, $max_length + $space_count );
}
/**
* Generates the summary text from a string of text. It's not ideal
* but allows keeping the editor and frontend consistent.
*
* NOTE: If editing, keep it in sync with generateSummary function from
* plugins/woocommerce-blocks/assets/js/base/components/summary/utils.ts!
*
* Once HTML API allow for HTML manipulation both functions (PHP and JS)
* should be updated to solution fully respecting the word count.
* https://github.com/woocommerce/woocommerce/issues/52835
*
* @param string $source Source text.
* @param int $max_length Limit number of items returned if text has multiple paragraphs.
* @return string Generated summary.
*/
private function generate_summary( $source, $max_length ) {
$count_type = wp_get_word_count_type();
$source_with_paragraphs = wpautop( $source );
$source_word_count = $this->count_text( $source_with_paragraphs, $count_type );
if ( $source_word_count <= $max_length ) {
return $source_with_paragraphs;
}
$first_paragraph = $this->get_first_paragraph( $source_with_paragraphs );
$first_paragraph_word_count = $this->count_text( $first_paragraph, $count_type );
if ( $first_paragraph_word_count <= $max_length ) {
return $first_paragraph;
}
if ( 'words' === $count_type ) {
return wpautop( wp_trim_words( $first_paragraph, $max_length ) );
}
return $this->trim_characters( $first_paragraph, $max_length, $count_type ) . '…';
}
/**
* Include and render the block.
*
* @param array $attributes Block attributes. Default empty array.
* @param string $content Block content. Default empty string.
* @param WP_Block $block Block instance.
* @return string Rendered block type output.
*/
protected function render( $attributes, $content, $block ) {
if ( ! empty( $content ) ) {
parent::register_block_type_assets();
$this->register_chunk_translations( [ $this->block_name ] );
return $content;
}
$post_id = $block->context['postId'] ?? '';
$product = wc_get_product( $post_id );
if ( ! $product ) {
return '';
}
$show_description_if_empty = isset( $attributes['showDescriptionIfEmpty'] ) && $attributes['showDescriptionIfEmpty'];
$source = $this->get_source( $product, $show_description_if_empty );
if ( ! $source ) {
return '';
}
$summary_length = isset( $attributes['summaryLength'] ) ? $attributes['summaryLength'] : false;
$link_text = isset( $attributes['linkText'] ) ? $attributes['linkText'] : '';
$show_link = isset( $attributes['showLink'] ) && $attributes['showLink'];
$summary = $summary_length ? $this->generate_summary( $source, $summary_length ) : wpautop( $source );
$final_summary = $show_link && $link_text ? $summary . $this->create_anchor( $product, $link_text ) : $summary;
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
return sprintf(
'<div class="wp-block-woocommerce-product-summary"><div class="wc-block-components-product-summary %1$s" style="%2$s">
%3$s
</div></div>',
esc_attr( $styles_and_classes['classes'] ),
esc_attr( $styles_and_classes['styles'] ?? '' ),
$final_summary
);
}
}