You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
514 lines
13 KiB
PHP
514 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* The HowTo Block
|
|
*
|
|
* @since 0.9.0
|
|
* @package RankMath
|
|
* @subpackage RankMath\Schema
|
|
* @author Rank Math <support@rankmath.com>
|
|
*/
|
|
|
|
namespace RankMath\Schema;
|
|
|
|
use RankMath\Paper\Paper;
|
|
use RankMath\Helpers\Str;
|
|
use RankMath\Helpers\Attachment;
|
|
use WP_Block_Type_Registry;
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* HowTo Block class.
|
|
*/
|
|
class Block_HowTo extends Block {
|
|
|
|
/**
|
|
* Block type name.
|
|
*
|
|
* @var string
|
|
*/
|
|
private $block_type = 'rank-math/howto-block';
|
|
|
|
/**
|
|
* The single instance of the class.
|
|
*
|
|
* @var Block_HowTo
|
|
*/
|
|
protected static $instance = null;
|
|
|
|
/**
|
|
* Retrieve main Block_HowTo instance.
|
|
*
|
|
* Ensure only one instance is loaded or can be loaded.
|
|
*
|
|
* @return Block_HowTo
|
|
*/
|
|
public static function get() {
|
|
if ( is_null( self::$instance ) && ! ( self::$instance instanceof Block_HowTo ) ) {
|
|
self::$instance = new Block_HowTo();
|
|
}
|
|
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* The Constructor.
|
|
*/
|
|
public function __construct() {
|
|
if ( WP_Block_Type_Registry::get_instance()->is_registered( $this->block_type ) ) {
|
|
return;
|
|
}
|
|
|
|
register_block_type(
|
|
$this->block_type,
|
|
[
|
|
'render_callback' => [ $this, 'render' ],
|
|
'editor_style' => 'rank-math-block-admin',
|
|
'attributes' => [
|
|
'hasDuration' => [
|
|
'type' => 'boolean',
|
|
'default' => false,
|
|
],
|
|
'days' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'hours' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'minutes' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'description' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'steps' => [
|
|
'type' => 'array',
|
|
'default' => [],
|
|
'items' => [ 'type' => 'object' ],
|
|
],
|
|
'sizeSlug' => [
|
|
'type' => 'string',
|
|
'default' => 'full',
|
|
],
|
|
'imageID' => [
|
|
'type' => 'integer',
|
|
],
|
|
'mainSizeSlug' => [
|
|
'type' => 'string',
|
|
'default' => 'full',
|
|
],
|
|
'listStyle' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'timeLabel' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'titleWrapper' => [
|
|
'type' => 'string',
|
|
'default' => 'h3',
|
|
],
|
|
'listCssClasses' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'titleCssClasses' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'contentCssClasses' => [
|
|
'type' => 'string',
|
|
'default' => '',
|
|
],
|
|
'textAlign' => [
|
|
'type' => 'string',
|
|
'default' => 'left',
|
|
],
|
|
],
|
|
]
|
|
);
|
|
|
|
add_filter( 'rank_math/schema/block/howto-block', [ $this, 'add_graph' ], 10, 2 );
|
|
}
|
|
|
|
/**
|
|
* Add HowTO schema data in JSON-LD array..
|
|
*
|
|
* @param array $data Array of JSON-LD data.
|
|
* @param array $block JsonLD Instance.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_graph( $data, $block ) {
|
|
// Early bail.
|
|
if ( ! $this->has_steps( $block['attrs'] ) ) {
|
|
return $data;
|
|
}
|
|
|
|
$attrs = $block['attrs'];
|
|
|
|
if ( ! isset( $data['howto'] ) ) {
|
|
$data['howto'] = [
|
|
'@type' => 'HowTo',
|
|
'name' => Paper::get()->get_title(),
|
|
'description' => isset( $attrs['description'] ) ? $this->clean_text( do_shortcode( $attrs['description'] ) ) : '',
|
|
'totalTime' => '',
|
|
'step' => [],
|
|
];
|
|
}
|
|
|
|
$this->add_step_image( $data['howto'], $attrs );
|
|
$this->add_duration( $data['howto'], $attrs );
|
|
$permalink = get_permalink() . '#';
|
|
|
|
foreach ( $attrs['steps'] as $index => $step ) {
|
|
if ( empty( $step['visible'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$schema_step = $this->add_step( $step, $permalink . $step['id'] );
|
|
if ( $schema_step ) {
|
|
$data['howto']['step'][] = $schema_step;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Render block content.
|
|
*
|
|
* @param array $attributes Array of atributes.
|
|
* @return string
|
|
*/
|
|
public static function markup( $attributes = [] ) {
|
|
$list_style = isset( $attributes['listStyle'] ) ? esc_attr( $attributes['listStyle'] ) : '';
|
|
$list_css_classes = isset( $attributes['listCssClasses'] ) ? esc_attr( $attributes['listCssClasses'] ) : '';
|
|
$title_wrapper = isset( $attributes['titleWrapper'] ) ? esc_attr( $attributes['titleWrapper'] ) : 'h2';
|
|
$title_css_classes = isset( $attributes['titleCssClasses'] ) ? esc_attr( $attributes['titleCssClasses'] ) : '';
|
|
$content_css_classes = isset( $attributes['contentCssClasses'] ) ? esc_attr( $attributes['contentCssClasses'] ) : '';
|
|
$size_slug = isset( $attributes['sizeSlug'] ) ? esc_attr( $attributes['sizeSlug'] ) : '';
|
|
|
|
$list_tag = self::get()->get_list_style( $list_style );
|
|
$item_tag = self::get()->get_list_item_style( $list_style );
|
|
$class = 'rank-math-block';
|
|
if ( ! empty( $attributes['className'] ) ) {
|
|
$class .= ' ' . esc_attr( $attributes['className'] );
|
|
}
|
|
|
|
// HTML.
|
|
$out = [];
|
|
$out[] = sprintf( '<div id="rank-math-howto" class="%1$s" %2$s>', $class, self::get()->get_styles( $attributes ) );
|
|
|
|
// HeaderContent.
|
|
$out[] = '<div class="rank-math-howto-description">';
|
|
|
|
if ( ! empty( $attributes['imageUrl'] ) ) {
|
|
$out[] = '<img src="' . esc_url( $attributes['imageUrl'] ) . '" />';
|
|
} elseif ( ! empty( $attributes['mainSizeSlug'] ) ) {
|
|
$out[] = self::get()->get_image( $attributes, $attributes['mainSizeSlug'], '' );
|
|
}
|
|
|
|
if ( ! empty( $attributes['description'] ) ) {
|
|
$out[] = self::get()->normalize_text( $attributes['description'], 'howto' );
|
|
}
|
|
|
|
$out[] = '</div>';
|
|
|
|
$out[] = self::get()->build_duration( $attributes );
|
|
|
|
$out[] = sprintf( '<%1$s class="rank-math-steps %2$s">', $list_tag, $list_css_classes );
|
|
|
|
// Steps.
|
|
foreach ( $attributes['steps'] as $index => $step ) {
|
|
if ( empty( $step['visible'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
$step_id = isset( $step['id'] ) ? esc_attr( $step['id'] ) : '';
|
|
|
|
$out[] = sprintf( '<%1$s id="%2$s" class="rank-math-step">', $item_tag, $step_id );
|
|
|
|
if ( ! empty( $step['title'] ) ) {
|
|
$out[] = sprintf(
|
|
'<%1$s class="rank-math-step-title %2$s">%3$s</%1$s>',
|
|
$title_wrapper,
|
|
$title_css_classes,
|
|
$step['title']
|
|
);
|
|
}
|
|
|
|
$step_content = ! empty( $step['content'] ) ? self::get()->normalize_text( $step['content'], 'howto' ) : '';
|
|
$step_image = ! empty( $step['imageUrl'] ) ? '<img src="' . esc_url( $step['imageUrl'] ) . '" />' : self::get()->get_image( $step, $size_slug, '' );
|
|
|
|
$out[] = sprintf(
|
|
'<div class="rank-math-step-content %2$s">%4$s%3$s</div>',
|
|
$title_wrapper,
|
|
$content_css_classes,
|
|
$step_content,
|
|
$step_image
|
|
);
|
|
|
|
$out[] = sprintf( '</%1$s>', $item_tag );
|
|
}
|
|
|
|
$out[] = sprintf( '</%1$s>', $list_tag );
|
|
$out[] = '</div>';
|
|
|
|
return apply_filters( 'rank_math/schema/block/howto/content', join( "\n", $out ), $out, $attributes );
|
|
}
|
|
|
|
/**
|
|
* Render block content.
|
|
*
|
|
* @param array $attributes Array of atributes.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function render( $attributes ) {
|
|
// Early bail.
|
|
if ( ! $this->has_steps( $attributes ) ) {
|
|
return '';
|
|
}
|
|
|
|
return self::markup( $attributes );
|
|
}
|
|
|
|
/**
|
|
* Add Step
|
|
*
|
|
* @param array $step Step.
|
|
* @param string $permalink Permalink.
|
|
*/
|
|
private function add_step( $step, $permalink ) {
|
|
$name = wp_strip_all_tags( do_shortcode( $step['title'] ) );
|
|
$text = $this->clean_text( do_shortcode( $step['content'] ) );
|
|
|
|
if ( empty( $name ) && empty( $text ) ) {
|
|
return false;
|
|
}
|
|
|
|
$schema_step = [
|
|
'@type' => 'HowToStep',
|
|
'url' => '' . $permalink,
|
|
];
|
|
|
|
if ( empty( $name ) ) {
|
|
$schema_step['text'] = '';
|
|
|
|
if ( empty( $text ) && empty( $schema_step['image'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( ! empty( $text ) ) {
|
|
$schema_step['text'] = $text;
|
|
}
|
|
} elseif ( empty( $text ) ) {
|
|
$schema_step['text'] = $name;
|
|
} else {
|
|
$schema_step['name'] = $name;
|
|
if ( ! empty( $text ) ) {
|
|
$schema_step['itemListElement'] = [
|
|
[
|
|
'@type' => 'HowToDirection',
|
|
'text' => $text,
|
|
],
|
|
];
|
|
}
|
|
}
|
|
|
|
if ( false === $this->add_step_image( $schema_step, $step ) ) {
|
|
$this->add_step_image_from_content( $schema_step, $step );
|
|
}
|
|
|
|
return $schema_step;
|
|
}
|
|
|
|
/**
|
|
* Checks if we have an inline image and add it.
|
|
*
|
|
* @param array $schema_step Our Schema output for the Step.
|
|
* @param array $step The step block data.
|
|
*/
|
|
private function add_step_image_from_content( &$schema_step, $step ) {
|
|
// Early Bail.
|
|
if ( empty( $step['content'] ) || ! Str::contains( '<img', $step['content'] ) ) {
|
|
return;
|
|
}
|
|
|
|
// Search for image.
|
|
preg_match_all( '/<img.+?src=[\'"]([^\'"]+)[\'"].*?>/i', $step['content'], $matches );
|
|
|
|
if ( ! isset( $matches[1][0] ) || empty( $matches[1][0] ) ) {
|
|
return;
|
|
}
|
|
|
|
$schema_image = [
|
|
'@type' => 'ImageObject',
|
|
'url' => $matches[1][0],
|
|
];
|
|
|
|
$image_id = Attachment::get_by_url( $schema_image['url'] );
|
|
|
|
if ( $image_id > 0 ) {
|
|
$this->add_caption( $schema_image, $image_id );
|
|
$this->add_image_size( $schema_image, $image_id );
|
|
}
|
|
|
|
$schema_step['image'] = $schema_image;
|
|
}
|
|
|
|
/**
|
|
* Checks if we have a step image and add it.
|
|
*
|
|
* @copyright Copyright (C) 2008-2019, Yoast BV
|
|
* The following code is a derivative work of the code from the Yoast(https://github.com/Yoast/wordpress-seo/), which is licensed under GPL v3.
|
|
*
|
|
* @param array $schema_step Our Schema output for the Step.
|
|
* @param array $step The step block data.
|
|
*/
|
|
private function add_step_image( &$schema_step, $step ) {
|
|
if ( empty( $step['imageID'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$image_id = absint( $step['imageID'] );
|
|
if ( ! ( $image_id > 0 ) ) {
|
|
return false;
|
|
}
|
|
|
|
$image_url = wp_get_attachment_image_url( $image_id, 'full' );
|
|
if ( ! $image_url ) {
|
|
return false;
|
|
}
|
|
|
|
$schema_image = [
|
|
'@type' => 'ImageObject',
|
|
'url' => $image_url,
|
|
];
|
|
|
|
$this->add_caption( $schema_image, $image_id );
|
|
$this->add_image_size( $schema_image, $image_id );
|
|
|
|
$schema_step['image'] = $schema_image;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add caption to schema.
|
|
*
|
|
* @param array $schema_image Our Schema output for the Image.
|
|
* @param int $image_id The image ID.
|
|
*/
|
|
private function add_caption( &$schema_image, $image_id ) {
|
|
$caption = wp_get_attachment_caption( $image_id );
|
|
if ( ! empty( $caption ) ) {
|
|
$schema_image['caption'] = $caption;
|
|
return;
|
|
}
|
|
|
|
$caption = Attachment::get_alt_tag( $image_id );
|
|
if ( ! empty( $caption ) ) {
|
|
$schema_image['caption'] = $caption;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add image size to schema.
|
|
*
|
|
* @param array $schema_image Our Schema output for the Image.
|
|
* @param int $image_id The image ID.
|
|
*/
|
|
private function add_image_size( &$schema_image, $image_id ) {
|
|
$image_meta = wp_get_attachment_metadata( $image_id );
|
|
if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) {
|
|
return;
|
|
}
|
|
|
|
$schema_image['width'] = $image_meta['width'];
|
|
$schema_image['height'] = $image_meta['height'];
|
|
}
|
|
|
|
/**
|
|
* Add duration to schema.
|
|
*
|
|
* @param array $data Our Schema output.
|
|
* @param array $attrs The block attributes.
|
|
*/
|
|
private function add_duration( &$data, $attrs ) {
|
|
if ( ! empty( $attrs['hasDuration'] ) ) {
|
|
$days = absint( $attrs['days'] ?? 0 );
|
|
$hours = absint( $attrs['hours'] ?? 0 );
|
|
$minutes = absint( $attrs['minutes'] ?? 0 );
|
|
if ( ( $days + $hours + $minutes ) > 0 ) {
|
|
$data['totalTime'] = esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate HowTo duration property.
|
|
*
|
|
* @param array $attrs The block attributes.
|
|
*
|
|
* @return string
|
|
*/
|
|
private function build_duration( $attrs ) {
|
|
if ( empty( $attrs['hasDuration'] ) ) {
|
|
return '';
|
|
}
|
|
|
|
$days = isset( $attrs['days'] ) ? absint( $attrs['days'] ) : 0;
|
|
$hours = isset( $attrs['hours'] ) ? absint( $attrs['hours'] ) : 0;
|
|
$minutes = isset( $attrs['minutes'] ) ? absint( $attrs['minutes'] ) : 0;
|
|
|
|
$elements = [];
|
|
if ( $days > 0 ) {
|
|
/* translators: %d is the number of days. */
|
|
$elements[] = sprintf( _n( '%d day', '%d days', $days, 'rank-math' ), $days );
|
|
}
|
|
|
|
if ( $hours > 0 ) {
|
|
/* translators: %d is the number of hours. */
|
|
$elements[] = sprintf( _n( '%d hour', '%d hours', $hours, 'rank-math' ), $hours );
|
|
}
|
|
|
|
if ( $minutes > 0 ) {
|
|
/* translators: %d is the number of minutes. */
|
|
$elements[] = sprintf( _n( '%d minute', '%d minutes', $minutes, 'rank-math' ), $minutes );
|
|
}
|
|
|
|
$count = count( $elements );
|
|
$formats = [
|
|
1 => '%1$s',
|
|
/* translators: placeholders are units of time, e.g. '1 hour and 30 minutes' */
|
|
2 => __( '%1$s and %2$s', 'rank-math' ),
|
|
/* translators: placeholders are units of time, e.g. '1 day, 8 hours and 30 minutes' */
|
|
3 => __( '%1$s, %2$s and %3$s', 'rank-math' ),
|
|
];
|
|
|
|
return sprintf(
|
|
'<p class="rank-math-howto-duration"><strong>%2$s</strong> <span>%1$s</span></p>',
|
|
isset( $formats[ $count ] ) ? vsprintf( $formats[ $count ], $elements ) : '',
|
|
empty( $attrs['timeLabel'] ) ? __( 'Total Time:', 'rank-math' ) : esc_html( $attrs['timeLabel'] )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Function to check the HowTo block have steps data.
|
|
*
|
|
* @param array $attributes Array of attributes.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
private function has_steps( $attributes ) {
|
|
return ! isset( $attributes['steps'] ) || empty( $attributes['steps'] ) ? false : true;
|
|
}
|
|
}
|