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

Yoast / wordpress-seo / 480d45f7558361ddb9f0510021bd9bb60479cd22

14 Apr 2025 12:31PM UTC coverage: 52.454% (-2.1%) from 54.594%
480d45f7558361ddb9f0510021bd9bb60479cd22

Pull #22077

github

enricobattocchi
Update composer.lock
Pull Request #22077: Drop compatibility with PHP 7.2 and 7.3

7827 of 13877 branches covered (56.4%)

Branch coverage included in aggregate %.

29025 of 56379 relevant lines covered (51.48%)

42277.18 hits per line

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

78.89
/src/generators/schema/article.php
1
<?php
2

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

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

8
/**
9
 * Returns schema Article data.
10
 */
11
class Article extends Abstract_Schema_Piece {
12

13
        /**
14
         * Determines whether or not a piece should be added to the graph.
15
         *
16
         * @return bool
17
         */
18
        public function is_needed() {
8✔
19
                if ( $this->context->indexable->object_type !== 'post' ) {
8✔
20
                        return false;
2✔
21
                }
22

23
                // If we cannot output a publisher, we shouldn't output an Article.
24
                if ( $this->context->site_represents === false ) {
6✔
25
                        return false;
2✔
26
                }
27

28
                // If we cannot output an author, we shouldn't output an Article.
29
                if ( ! $this->helpers->schema->article->is_author_supported( $this->context->indexable->object_sub_type ) ) {
4✔
30
                        return false;
2✔
31
                }
32

33
                if ( $this->context->schema_article_type !== 'None' ) {
2✔
34
                        $this->context->has_article = true;
2✔
35
                        return true;
2✔
36
                }
37

38
                return false;
×
39
        }
40

41
        /**
42
         * Returns Article data.
43
         *
44
         * @return array Article data.
45
         */
46
        public function generate() {
14✔
47
                $author = \get_userdata( $this->context->post->post_author );
14✔
48
                $data   = [
14✔
49
                        '@type'            => $this->context->schema_article_type,
14✔
50
                        '@id'              => $this->context->canonical . Schema_IDs::ARTICLE_HASH,
14✔
51
                        'isPartOf'         => [ '@id' => $this->context->main_schema_id ],
14✔
52
                        'author'           => [
14✔
53
                                'name' => ( $author instanceof WP_User ) ? $this->helpers->schema->html->smart_strip_tags( $author->display_name ) : '',
14✔
54
                                '@id'  => $this->helpers->schema->id->get_user_schema_id( $this->context->post->post_author, $this->context ),
14✔
55
                        ],
14✔
56
                        'headline'         => $this->helpers->schema->html->smart_strip_tags( $this->helpers->post->get_post_title_with_fallback( $this->context->id ) ),
14✔
57
                        'datePublished'    => $this->helpers->date->format( $this->context->post->post_date_gmt ),
14✔
58
                ];
14✔
59

60
                if ( \strtotime( $this->context->post->post_modified_gmt ) > \strtotime( $this->context->post->post_date_gmt ) ) {
14✔
61
                        $data['dateModified'] = $this->helpers->date->format( $this->context->post->post_modified_gmt );
14✔
62
                }
63

64
                $data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id ];
14✔
65
                $data['wordCount']        = $this->word_count( $this->context->post->post_content, $this->context->post->post_title );
14✔
66

67
                if ( $this->context->post->comment_status === 'open' ) {
14✔
68
                        $data['commentCount'] = \intval( $this->context->post->comment_count, 10 );
10✔
69
                }
70

71
                if ( $this->context->site_represents_reference ) {
14✔
72
                        $data['publisher'] = $this->context->site_represents_reference;
2✔
73
                }
74

75
                $data = $this->add_image( $data );
14✔
76
                $data = $this->add_keywords( $data );
14✔
77
                $data = $this->add_sections( $data );
14✔
78
                $data = $this->helpers->schema->language->add_piece_language( $data );
14✔
79

80
                if ( \post_type_supports( $this->context->post->post_type, 'comments' ) && $this->context->post->comment_status === 'open' ) {
14✔
81
                        $data = $this->add_potential_action( $data );
8✔
82
                }
83

84
                return $data;
14✔
85
        }
86

87
        /**
88
         * Adds tags as keywords, if tags are assigned.
89
         *
90
         * @param array $data Article data.
91
         *
92
         * @return array Article data.
93
         */
94
        private function add_keywords( $data ) {
14✔
95
                /**
96
                 * Filter: 'wpseo_schema_article_keywords_taxonomy' - Allow changing the taxonomy used to assign keywords to a post type Article data.
97
                 *
98
                 * @param string $taxonomy The chosen taxonomy.
99
                 */
100
                $taxonomy = \apply_filters( 'wpseo_schema_article_keywords_taxonomy', 'post_tag' );
14✔
101

102
                return $this->add_terms( $data, 'keywords', $taxonomy );
14✔
103
        }
104

105
        /**
106
         * Adds categories as sections, if categories are assigned.
107
         *
108
         * @param array $data Article data.
109
         *
110
         * @return array Article data.
111
         */
112
        private function add_sections( $data ) {
14✔
113
                /**
114
                 * Filter: 'wpseo_schema_article_sections_taxonomy' - Allow changing the taxonomy used to assign keywords to a post type Article data.
115
                 *
116
                 * @param string $taxonomy The chosen taxonomy.
117
                 */
118
                $taxonomy = \apply_filters( 'wpseo_schema_article_sections_taxonomy', 'category' );
14✔
119

120
                return $this->add_terms( $data, 'articleSection', $taxonomy );
14✔
121
        }
122

123
        /**
124
         * Adds a term or multiple terms, comma separated, to a field.
125
         *
126
         * @param array  $data     Article data.
127
         * @param string $key      The key in data to save the terms in.
128
         * @param string $taxonomy The taxonomy to retrieve the terms from.
129
         *
130
         * @return mixed Article data.
131
         */
132
        protected function add_terms( $data, $key, $taxonomy ) {
14✔
133
                $terms = \get_the_terms( $this->context->id, $taxonomy );
14✔
134

135
                if ( ! \is_array( $terms ) ) {
14✔
136
                        return $data;
2✔
137
                }
138

139
                $callback = static function ( $term ) {
12✔
140
                        // We are using the WordPress internal translation.
141
                        return $term->name !== \__( 'Uncategorized', 'default' );
10✔
142
                };
12✔
143
                $terms    = \array_filter( $terms, $callback );
12✔
144

145
                if ( empty( $terms ) ) {
12✔
146
                        return $data;
2✔
147
                }
148

149
                $data[ $key ] = \wp_list_pluck( $terms, 'name' );
10✔
150

151
                return $data;
10✔
152
        }
153

154
        /**
155
         * Adds an image node if the post has a featured image.
156
         *
157
         * @param array $data The Article data.
158
         *
159
         * @return array The Article data.
160
         */
161
        private function add_image( $data ) {
14✔
162
                if ( $this->context->main_image_url !== null ) {
14✔
163
                        $data['image']        = [
14✔
164
                                '@id' => $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH,
14✔
165
                        ];
14✔
166
                        $data['thumbnailUrl'] = $this->context->main_image_url;
14✔
167
                }
168

169
                return $data;
14✔
170
        }
171

172
        /**
173
         * Adds the potential action property to the Article Schema piece.
174
         *
175
         * @param array $data The Article data.
176
         *
177
         * @return array The Article data with the potential action added.
178
         */
179
        private function add_potential_action( $data ) {
8✔
180
                /**
181
                 * Filter: 'wpseo_schema_article_potential_action_target' - Allows filtering of the schema Article potentialAction target.
182
                 *
183
                 * @param array $targets The URLs for the Article potentialAction target.
184
                 */
185
                $targets = \apply_filters( 'wpseo_schema_article_potential_action_target', [ $this->context->canonical . '#respond' ] );
8✔
186

187
                $data['potentialAction'][] = [
8✔
188
                        '@type'  => 'CommentAction',
8✔
189
                        'name'   => 'Comment',
8✔
190
                        'target' => $targets,
8✔
191
                ];
8✔
192

193
                return $data;
8✔
194
        }
195

196
        /**
197
         * Does a simple word count but tries to be relatively smart about it.
198
         *
199
         * @param string $post_content The post content.
200
         * @param string $post_title   The post title.
201
         *
202
         * @return int The number of words in the content.
203
         */
204
        private function word_count( $post_content, $post_title = '' ) {
×
205
                // Add the title to our word count.
206
                $post_content = $post_title . ' ' . $post_content;
×
207

208
                // Strip pre/code blocks and their content.
209
                $post_content = \preg_replace( '@<(pre|code)[^>]*?>.*?</\\1>@si', '', $post_content );
×
210

211
                // Add space between tags that don't have it.
212
                $post_content = \preg_replace( '@><@', '> <', $post_content );
×
213

214
                // Strips all other tags.
215
                $post_content = \wp_strip_all_tags( $post_content );
×
216

217
                $characters = '';
×
218

219
                if ( \preg_match( '@[а-я]@ui', $post_content ) ) {
×
220
                        // Correct counting of the number of words in the Russian and Ukrainian languages.
221
                        $alphabet = [
×
222
                                'ru' => 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя',
×
223
                                'ua' => 'абвгґдеєжзиіїйклмнопрстуфхцчшщьюя',
×
224
                        ];
×
225

226
                        $characters  = \implode( '', $alphabet );
×
227
                        $characters  = \preg_split( '//u', $characters, -1, \PREG_SPLIT_NO_EMPTY );
×
228
                        $characters  = \array_unique( $characters );
×
229
                        $characters  = \implode( '', $characters );
×
230
                        $characters .= \mb_strtoupper( $characters );
×
231
                }
232

233
                // Remove characters from HTML entities.
234
                $post_content = \preg_replace( '@&[a-z0-9]+;@i', ' ', \htmlentities( $post_content ) );
×
235

236
                return \str_word_count( $post_content, 0, $characters );
×
237
        }
238
}
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