class-schema-howto.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Frontend\Schema
  6. */
  7. /**
  8. * Returns schema FAQ data.
  9. *
  10. * @since 11.5
  11. */
  12. class WPSEO_Schema_HowTo implements WPSEO_Graph_Piece {
  13. /**
  14. * Determine whether this graph piece is needed or not.
  15. *
  16. * Always false, because this graph piece adds itself using the filter API.
  17. *
  18. * @var bool
  19. */
  20. private $is_needed = false;
  21. /**
  22. * The FAQ blocks count on the current page.
  23. *
  24. * @var int
  25. */
  26. private $counter;
  27. /**
  28. * A value object with context variables.
  29. *
  30. * @var WPSEO_Schema_Context
  31. */
  32. private $context;
  33. /**
  34. * Holds the allowed HTML tags for the jsonText.
  35. *
  36. * @var string
  37. */
  38. private $allowed_json_text_tags = '<h1><h2><h3><h4><h5><h6><br><ol><ul><li><a><p><b><strong><i><em>';
  39. /**
  40. * WPSEO_Schema_FAQ constructor.
  41. *
  42. * @param WPSEO_Schema_Context $context A value object with context variables.
  43. *
  44. * @codeCoverageIgnore
  45. */
  46. public function __construct( WPSEO_Schema_Context $context ) {
  47. $this->counter = 0;
  48. $this->context = $context;
  49. add_filter( 'wpseo_schema_block_yoast/how-to-block', [ $this, 'render' ], 10, 2 );
  50. }
  51. /**
  52. * Renders a list of questions, referencing them by ID.
  53. *
  54. * @return array $data Our Schema graph.
  55. */
  56. public function generate() {
  57. return [];
  58. }
  59. /**
  60. * Renders the How-To block into our graph.
  61. *
  62. * @param array $graph Our Schema data.
  63. * @param array $block The How-To block content.
  64. *
  65. * @return mixed
  66. */
  67. public function render( $graph, $block ) {
  68. $this->counter++;
  69. $data = [
  70. '@type' => 'HowTo',
  71. '@id' => $this->context->canonical . '#howto-' . $this->counter,
  72. 'name' => $this->context->title,
  73. 'mainEntityOfPage' => [ '@id' => $this->get_main_schema_id() ],
  74. 'description' => '',
  75. ];
  76. $json_description = strip_tags( $block['attrs']['jsonDescription'], '<h1><h2><h3><h4><h5><h6><br><ol><ul><li><a><p><b><strong><i><em>' );
  77. if ( isset( $json_description ) ) {
  78. $data['description'] = $json_description;
  79. }
  80. $this->add_duration( $data, $block['attrs'] );
  81. $this->add_steps( $data, $block['attrs']['steps'] );
  82. $graph[] = $data;
  83. return $graph;
  84. }
  85. /**
  86. * Adds the duration of the task to the Schema.
  87. *
  88. * @param array $data Our How-To schema data.
  89. * @param array $attributes The block data attributes.
  90. *
  91. * @return array $data Our schema data.
  92. */
  93. private function add_duration( &$data, $attributes ) {
  94. if ( ! empty( $attributes['hasDuration'] ) && $attributes['hasDuration'] ) {
  95. $days = empty( $attributes['days'] ) ? 0 : $attributes['days'];
  96. $hours = empty( $attributes['hours'] ) ? 0 : $attributes['hours'];
  97. $minutes = empty( $attributes['minutes'] ) ? 0 : $attributes['minutes'];
  98. if ( ( $days + $hours + $minutes ) > 0 ) {
  99. $data['totalTime'] = esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' );
  100. }
  101. }
  102. return $data;
  103. }
  104. /**
  105. * Determines whether we're part of an article or a webpage.
  106. *
  107. * @return string A reference URL.
  108. */
  109. protected function get_main_schema_id() {
  110. if ( $this->context->site_represents !== false && WPSEO_Schema_Article::is_article_post_type() ) {
  111. return $this->context->canonical . WPSEO_Schema_IDs::ARTICLE_HASH;
  112. }
  113. return $this->context->canonical . WPSEO_Schema_IDs::WEBPAGE_HASH;
  114. }
  115. /**
  116. * Determines whether or not a piece should be added to the graph.
  117. *
  118. * @return bool
  119. */
  120. public function is_needed() {
  121. return $this->is_needed;
  122. }
  123. /**
  124. * Adds the steps to our How-To output.
  125. *
  126. * @param array $data Our How-To schema data.
  127. * @param array $steps Our How-To block's steps.
  128. */
  129. private function add_steps( &$data, $steps ) {
  130. foreach ( $steps as $step ) {
  131. $schema_id = $this->context->canonical . '#' . esc_attr( $step['id'] );
  132. $schema_step = [
  133. '@type' => 'HowToStep',
  134. 'url' => $schema_id,
  135. ];
  136. $json_text = strip_tags( $step['jsonText'], $this->allowed_json_text_tags );
  137. $json_name = wp_strip_all_tags( $step['jsonName'] );
  138. if ( empty( $json_name ) ) {
  139. if ( empty( $step['text'] ) ) {
  140. continue;
  141. }
  142. $schema_step['text'] = '';
  143. $this->add_step_image( $schema_step, $step );
  144. // If there is no text and no image, don't output the step.
  145. if ( empty( $json_text ) && empty( $schema_step['image'] ) ) {
  146. continue;
  147. }
  148. if ( ! empty( $json_text ) ) {
  149. $schema_step['text'] = $json_text;
  150. }
  151. }
  152. elseif ( empty( $json_text ) ) {
  153. $schema_step['text'] = $json_name;
  154. }
  155. else {
  156. $schema_step['name'] = $json_name;
  157. $this->add_step_description( $schema_step, $step );
  158. $this->add_step_image( $schema_step, $step );
  159. }
  160. $data['step'][] = $schema_step;
  161. }
  162. }
  163. /**
  164. * Checks if we have a step description, if we do, add it.
  165. *
  166. * @param array $schema_step Our Schema output for the Step.
  167. * @param array $step The step block data.
  168. */
  169. private function add_step_description( &$schema_step, $step ) {
  170. $json_text = strip_tags( $step['jsonText'], $this->allowed_json_text_tags );
  171. if ( empty( $json_text ) ) {
  172. return;
  173. }
  174. $schema_step['itemListElement'] = [];
  175. $schema_step['itemListElement'][] = [
  176. '@type' => 'HowToDirection',
  177. 'text' => $json_text,
  178. ];
  179. }
  180. /**
  181. * Checks if we have a step image, if we do, add it.
  182. *
  183. * @param array $schema_step Our Schema output for the Step.
  184. * @param array $step The step block data.
  185. */
  186. private function add_step_image( &$schema_step, $step ) {
  187. foreach ( $step['text'] as $line ) {
  188. if ( is_array( $line ) && isset( $line['type'] ) && $line['type'] === 'img' ) {
  189. $schema_step['image'] = $this->get_image_schema( esc_url( $line['props']['src'] ) );
  190. }
  191. }
  192. }
  193. /**
  194. * Generates the image schema from the attachment $url.
  195. *
  196. * @param string $url Attachment url.
  197. *
  198. * @return array Image schema.
  199. *
  200. * @codeCoverageIgnore
  201. */
  202. protected function get_image_schema( $url ) {
  203. $image = new WPSEO_Schema_Image( $this->context->canonical . '#schema-image-' . md5( $url ) );
  204. return $image->generate_from_url( $url );
  205. }
  206. }