• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 2933c98962a853be2048beeb812f0fac9b2a8197

15 Jan 2026 04:12PM UTC coverage: 53.19% (+0.4%) from 52.832%
2933c98962a853be2048beeb812f0fac9b2a8197

Pull #22887

github

web-flow
Merge edcd36026 into d394743dd
Pull Request #22887: Add a new key for step attribute and adapt the relevant code and make…

8774 of 16370 branches covered (53.6%)

Branch coverage included in aggregate %.

5 of 135 new or added lines in 14 files covered. (3.7%)

42 existing lines in 4 files now uncovered.

32905 of 61988 relevant lines covered (53.08%)

46516.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.75
/src/generators/schema/howto.php
1
<?php
2

3
namespace Yoast\WP\SEO\Generators\Schema;
4

5
use Yoast\WP\SEO\Config\Schema_IDs;
6

7
/**
8
 * Returns schema HowTo data.
9
 */
10
class HowTo extends Abstract_Schema_Piece {
11

12
        /**
13
         * Determines whether or not a piece should be added to the graph.
14
         *
15
         * @return bool
16
         */
17
        public function is_needed() {
2✔
18
                return ! empty( $this->context->blocks['yoast/how-to-block'] );
2✔
19
        }
20

21
        /**
22
         * Renders a list of questions, referencing them by ID.
23
         *
24
         * @return array Our Schema graph.
25
         */
26
        public function generate() {
14✔
27
                $graph = [];
14✔
28

29
                foreach ( $this->context->blocks['yoast/how-to-block'] as $index => $block ) {
14✔
30
                        $this->add_how_to( $graph, $block, $index );
14✔
31
                }
32

33
                return $graph;
14✔
34
        }
35

36
        /**
37
         * Adds the duration of the task to the Schema.
38
         *
39
         * @param array $data       Our How-To schema data.
40
         * @param array $attributes The block data attributes.
41
         *
42
         * @return void
43
         */
44
        private function add_duration( &$data, $attributes ) {
4✔
45
                if ( empty( $attributes['hasDuration'] ) ) {
4✔
46
                        return;
2✔
47
                }
48

49
                $days    = empty( $attributes['days'] ) ? 0 : $attributes['days'];
2✔
50
                $hours   = empty( $attributes['hours'] ) ? 0 : $attributes['hours'];
2✔
51
                $minutes = empty( $attributes['minutes'] ) ? 0 : $attributes['minutes'];
2✔
52

53
                if ( ( $days + $hours + $minutes ) > 0 ) {
2✔
54
                        $data['totalTime'] = \esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' );
2✔
55
                }
56
        }
57

58
        /**
59
         * Adds the steps to our How-To output.
60
         *
61
         * @param array $data  Our How-To schema data.
62
         * @param array $steps Our How-To block's steps.
63
         *
64
         * @return void
65
         */
66
        private function add_steps( &$data, $steps ) {
10✔
67
                foreach ( $steps as $step ) {
10✔
68
                        $schema_id   = $this->context->canonical . '#' . \esc_attr( $step['id'] );
10✔
69
                        $schema_step = [
10✔
70
                                '@type' => 'HowToStep',
10✔
71
                                'url'   => $schema_id,
10✔
72
                        ];
10✔
73

74
                        if ( isset( $step['jsonText'] ) ) {
10✔
75
                                $json_text = $this->helpers->schema->html->sanitize( $step['jsonText'] );
6✔
76
                        }
77

78
                        if ( isset( $step['jsonName'] ) ) {
10✔
79
                                $json_name = $this->helpers->schema->html->smart_strip_tags( $step['jsonName'] );
8✔
80
                        }
81

82
                        if ( empty( $json_name ) ) {
10✔
83
                                if ( empty( $step['text'] ) ) {
4✔
84
                                        continue;
×
85
                                }
86

87
                                $schema_step['text'] = '';
4✔
88

89
                                $this->add_step_image( $schema_step, $step );
4✔
90

91
                                // If there is no text and no image, don't output the step.
92
                                if ( empty( $json_text ) && empty( $schema_step['image'] ) ) {
4✔
93
                                        continue;
2✔
94
                                }
95

96
                                if ( ! empty( $json_text ) ) {
2✔
97
                                        $schema_step['text'] = $json_text;
2✔
98
                                }
99
                        }
100

101
                        elseif ( empty( $json_text ) ) {
3✔
102
                                $schema_step['text'] = $json_name;
2✔
103
                        }
104
                        else {
105
                                $schema_step['name'] = $json_name;
4✔
106

107
                                $this->add_step_description( $schema_step, $json_text );
4✔
108
                                $this->add_step_image( $schema_step, $step );
4✔
109
                        }
110

111
                        $data['step'][] = $schema_step;
8✔
112
                }
113
        }
114

115
        /**
116
         * Checks if we have a step description, if we do, add it.
117
         *
118
         * @param array  $schema_step Our Schema output for the Step.
119
         * @param string $json_text   The step text.
120
         *
121
         * @return void
122
         */
123
        private function add_step_description( &$schema_step, $json_text ) {
2✔
124
                // Decode HTML entities.
125
                $json_text = \html_entity_decode( $json_text );
2✔
126
                // Remove the image from the text if it exists. Search and replace the img tag.
127
                $json_text = \preg_replace( '/<img[^>]+>/i', '', $json_text );
2✔
128
                // Trim whitespace.
129
                $json_text = \trim( $json_text );
2✔
130

131
                $schema_step['itemListElement'] = [
2✔
132
                        [
2✔
133
                                '@type' => 'HowToDirection',
2✔
134
                                'text'  => $json_text,
2✔
135
                        ],
2✔
136
                ];
2✔
137
        }
138

139
        /**
140
         * Checks if we have a step image, if we do, add it.
141
         *
142
         * @param array $schema_step Our Schema output for the Step.
143
         * @param array $step        The step block data.
144
         *
145
         * @return void
146
         */
147
        private function add_step_image( &$schema_step, $step ) {
2✔
148
                if ( isset( $step['images'] ) && \is_array( $step['images'] ) ) {
2✔
NEW
149
                        foreach ( $step['images'] as $image ) {
×
NEW
150
                                if ( isset( $image['type'] ) && $image['type'] === 'img' ) {
×
NEW
151
                                        $schema_step['image'] = $this->get_image_schema( \esc_url( $image['props']['src'] ) );
×
152
                                }
153
                        }
154
                } elseif ( isset( $step['text'] ) && \is_array( $step['text'] ) ) {
2✔
155
                // Backwards compatibility for older How-To blocks.
156
                        foreach ( $step['text'] as $line ) {
2✔
157
                                if ( \is_array( $line ) && isset( $line['type'] ) && $line['type'] === 'img' ) {
2✔
158
                                        $schema_step['image'] = $this->get_image_schema( \esc_url( $line['props']['src'] ) );
2✔
159
                                }
160
                        }
161
                }
162
        }
163

164
        /**
165
         * Generates the HowTo schema for a block.
166
         *
167
         * @param array $graph Our Schema data.
168
         * @param array $block The How-To block content.
169
         * @param int   $index The index of the current block.
170
         *
171
         * @return void
172
         */
173
        protected function add_how_to( &$graph, $block, $index ) {
12✔
174
                $data = [
12✔
175
                        '@type'            => 'HowTo',
12✔
176
                        '@id'              => $this->context->canonical . '#howto-' . ( $index + 1 ),
12✔
177
                        'name'             => $this->helpers->schema->html->smart_strip_tags( $this->helpers->post->get_post_title_with_fallback( $this->context->id ) ),
12✔
178
                        'mainEntityOfPage' => [ '@id' => $this->context->main_schema_id ],
12✔
179
                        'description'      => '',
12✔
180
                ];
12✔
181

182
                if ( $this->context->has_article ) {
12✔
183
                        $data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id . Schema_IDs::ARTICLE_HASH ];
×
184
                }
185

186
                if ( isset( $block['attrs']['jsonDescription'] ) ) {
12✔
187
                        $data['description'] = $this->helpers->schema->html->sanitize( $block['attrs']['jsonDescription'] );
12✔
188
                }
189

190
                $this->add_duration( $data, $block['attrs'] );
12✔
191

192
                if ( isset( $block['attrs']['steps'] ) ) {
12✔
193
                        $this->add_steps( $data, $block['attrs']['steps'] );
12✔
194
                }
195

196
                $data = $this->helpers->schema->language->add_piece_language( $data );
12✔
197

198
                $graph[] = $data;
12✔
199
        }
200

201
        /**
202
         * Generates the image schema from the attachment $url.
203
         *
204
         * @param string $url Attachment url.
205
         *
206
         * @return array Image schema.
207
         */
208
        protected function get_image_schema( $url ) {
2✔
209
                $schema_id = $this->context->canonical . '#schema-image-' . \md5( $url );
2✔
210

211
                return $this->helpers->schema->image->generate_from_url( $schema_id, $url );
2✔
212
        }
213
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc