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

Yoast / wordpress-seo / 7cd8af37a7bbbd7b964cfa8db91dfdc0d3c167de

27 Mar 2026 10:12AM UTC coverage: 52.787% (-1.1%) from 53.917%
7cd8af37a7bbbd7b964cfa8db91dfdc0d3c167de

Pull #23062

github

web-flow
Merge 77b1c5f2b into 70d176237
Pull Request #23062: Register Yoast SEO abilities about analysis scores

8535 of 16034 branches covered (53.23%)

Branch coverage included in aggregate %.

143 of 307 new or added lines in 5 files covered. (46.58%)

794 existing lines in 37 files now uncovered.

33679 of 63936 relevant lines covered (52.68%)

46793.79 hits per line

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

35.11
/src/abilities/application/score-retriever.php
1
<?php
2

3
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
4
namespace Yoast\WP\SEO\Abilities\Application;
5

6
use WPSEO_Rank;
7
use Yoast\WP\SEO\Abilities\Domain\Score_Result;
8
use Yoast\WP\SEO\Abilities\Infrastructure\Enabled_Analysis_Features_Checker;
9
use Yoast\WP\SEO\Repositories\Indexable_Repository;
10

11
/**
12
 * Application service that retrieves SEO, readability, and inclusive language scores
13
 * for the most recently modified posts.
14
 */
15
class Score_Retriever {
16

17
        /**
18
         * The default number of posts to retrieve.
19
         *
20
         * @var int
21
         */
22
        private const DEFAULT_NUMBER_OF_POSTS = 10;
23

24
        /**
25
         * The indexable repository.
26
         *
27
         * @var Indexable_Repository
28
         */
29
        private $indexable_repository;
30

31
        /**
32
         * The enabled analysis features checker.
33
         *
34
         * @var Enabled_Analysis_Features_Checker
35
         */
36
        private $enabled_analysis_features_checker;
37

38
        /**
39
         * Constructor.
40
         *
41
         * @param Indexable_Repository              $indexable_repository              The indexable repository.
42
         * @param Enabled_Analysis_Features_Checker $enabled_analysis_features_checker The enabled analysis features checker.
43
         */
NEW
44
        public function __construct(
×
45
                Indexable_Repository $indexable_repository,
46
                Enabled_Analysis_Features_Checker $enabled_analysis_features_checker
47
        ) {
NEW
48
                $this->indexable_repository              = $indexable_repository;
×
NEW
49
                $this->enabled_analysis_features_checker = $enabled_analysis_features_checker;
×
50
        }
51

52
        /**
53
         * Retrieves the SEO scores for the most recently modified posts.
54
         *
55
         * @param array<string, int> $input The input containing optional 'number_of_posts'.
56
         *
57
         * @return array<int, array<string, int|string|null>> The SEO score data for each post.
58
         */
59
        public function get_seo_scores( array $input ): array {
14✔
60
                $indexables = $this->get_recent_indexables( $this->get_number_of_posts( $input ) );
14✔
61

62
                return \array_map( [ $this, 'build_seo_score_for_indexable' ], $indexables );
14✔
63
        }
64

65
        /**
66
         * Retrieves the readability scores for the most recently modified posts.
67
         *
68
         * @param array<string, int> $input The input containing optional 'number_of_posts'.
69
         *
70
         * @return array<int, array<string, int|string>> The readability score data for each post.
71
         */
72
        public function get_readability_scores( array $input ): array {
2✔
73
                $indexables = $this->get_recent_indexables( $this->get_number_of_posts( $input ) );
2✔
74

75
                return \array_map( [ $this, 'build_readability_score_for_indexable' ], $indexables );
2✔
76
        }
77

78
        /**
79
         * Retrieves the inclusive language scores for the most recently modified posts.
80
         *
81
         * @param array<string, int> $input The input containing optional 'number_of_posts'.
82
         *
83
         * @return array<int, array<string, int|string>> The inclusive language score data for each post.
84
         */
85
        public function get_inclusive_language_scores( array $input ): array {
4✔
86
                $indexables = $this->get_recent_indexables( $this->get_number_of_posts( $input ) );
4✔
87

88
                return \array_map( [ $this, 'build_inclusive_language_score_for_indexable' ], $indexables );
4✔
89
        }
90

91
        /**
92
         * Retrieves all scores for the most recently modified posts.
93
         *
94
         * @param array<string, int> $input The input containing optional 'number_of_posts'.
95
         *
96
         * @return array<int, array<string, array<string, int|string|null>|string|null>> The combined score data for each post.
97
         */
98
        public function get_all_scores( array $input ): array {
4✔
99
                $indexables = $this->get_recent_indexables( $this->get_number_of_posts( $input ) );
4✔
100
                $results    = [];
4✔
101

102
                foreach ( $indexables as $indexable ) {
4✔
103
                        $title = $this->get_indexable_title( $indexable );
4✔
104

105
                        $seo_score = null;
4✔
106
                        if ( $this->enabled_analysis_features_checker->is_keyword_analysis_enabled() ) {
4✔
107
                                $seo_score = $this->build_seo_score_for_indexable( $indexable );
4✔
108
                                unset( $seo_score['title'] );
4✔
109
                        }
110

111
                        $readability_score = null;
4✔
112
                        if ( $this->enabled_analysis_features_checker->is_content_analysis_enabled() ) {
4✔
113
                                $readability_score = $this->build_readability_score_for_indexable( $indexable );
4✔
114
                                unset( $readability_score['title'] );
4✔
115
                        }
116

117
                        $inclusive_language_score = null;
4✔
118
                        if ( $this->enabled_analysis_features_checker->is_inclusive_language_enabled() ) {
4✔
119
                                $inclusive_language_score = $this->build_inclusive_language_score_for_indexable( $indexable );
2✔
120
                                unset( $inclusive_language_score['title'] );
2✔
121
                        }
122

123
                        $results[] = [
4✔
124
                                'title'              => $title,
4✔
125
                                'seo'                => $seo_score,
4✔
126
                                'readability'        => $readability_score,
4✔
127
                                'inclusive_language' => $inclusive_language_score,
4✔
128
                        ];
4✔
129
                }
130

131
                return $results;
4✔
132
        }
133

134
        /**
135
         * Builds the SEO score result for a single indexable.
136
         *
137
         * @param object $indexable The indexable object.
138
         *
139
         * @return array<string, int|string|null> The SEO score data.
140
         */
NEW
141
        private function build_seo_score_for_indexable( $indexable ): array {
×
NEW
142
                $title = $this->get_indexable_title( $indexable );
×
143

NEW
144
                if ( $indexable->is_robots_noindex ) {
×
NEW
145
                        $rank   = new WPSEO_Rank( WPSEO_Rank::NO_INDEX );
×
NEW
146
                        $result = ( new Score_Result(
×
NEW
147
                                $title,
×
NEW
148
                                0,
×
NEW
149
                                $rank->get_rank(),
×
NEW
150
                                $rank->get_label(),
×
NEW
151
                        ) )->to_array();
×
152

NEW
153
                        $result['focus_keyphrase'] = $indexable->primary_focus_keyword;
×
NEW
154
                        return $result;
×
155
                }
156

NEW
157
                $score  = (int) $indexable->primary_focus_keyword_score;
×
NEW
158
                $rank   = WPSEO_Rank::from_numeric_score( $score );
×
NEW
159
                $result = ( new Score_Result(
×
NEW
160
                        $title,
×
NEW
161
                        $score,
×
NEW
162
                        $rank->get_rank(),
×
NEW
163
                        $rank->get_label(),
×
NEW
164
                ) )->to_array();
×
165

NEW
166
                $result['focus_keyphrase'] = $indexable->primary_focus_keyword;
×
NEW
167
                return $result;
×
168
        }
169

170
        /**
171
         * Builds the readability score result for a single indexable.
172
         *
173
         * @param object $indexable The indexable object.
174
         *
175
         * @return array<string, int|string> The readability score data.
176
         */
NEW
177
        private function build_readability_score_for_indexable( $indexable ): array {
×
NEW
178
                $title = $this->get_indexable_title( $indexable );
×
NEW
179
                $score = (int) $indexable->readability_score;
×
NEW
180
                $rank  = WPSEO_Rank::from_numeric_score( $score );
×
181

NEW
182
                return ( new Score_Result(
×
NEW
183
                        $title,
×
NEW
184
                        $score,
×
NEW
185
                        $rank->get_rank(),
×
NEW
186
                        $rank->get_label(),
×
NEW
187
                ) )->to_array();
×
188
        }
189

190
        /**
191
         * Builds the inclusive language score result for a single indexable.
192
         *
193
         * @param object $indexable The indexable object.
194
         *
195
         * @return array<string, int|string> The inclusive language score data.
196
         */
NEW
197
        private function build_inclusive_language_score_for_indexable( $indexable ): array {
×
NEW
198
                $title = $this->get_indexable_title( $indexable );
×
NEW
199
                $score = (int) $indexable->inclusive_language_score;
×
200

NEW
201
                if ( $score === 0 ) {
×
NEW
202
                        return ( new Score_Result(
×
NEW
203
                                $title,
×
NEW
204
                                0,
×
NEW
205
                                'na',
×
NEW
206
                                \__( 'Not available', 'wordpress-seo' ),
×
NEW
207
                        ) )->to_array();
×
208
                }
209

NEW
210
                $rank = WPSEO_Rank::from_numeric_score( $score );
×
211

NEW
212
                return ( new Score_Result(
×
NEW
213
                        $title,
×
NEW
214
                        $score,
×
NEW
215
                        $rank->get_rank(),
×
NEW
216
                        $rank->get_inclusive_language_label(),
×
NEW
217
                ) )->to_array();
×
218
        }
219

220
        /**
221
         * Returns the title for an indexable, with a fallback.
222
         *
223
         * @param object $indexable The indexable object.
224
         *
225
         * @return string The post title.
226
         */
NEW
227
        private function get_indexable_title( $indexable ): string {
×
NEW
228
                if ( ! empty( $indexable->breadcrumb_title ) ) {
×
NEW
229
                        return $indexable->breadcrumb_title;
×
230
                }
231

NEW
232
                return \__( '(no title)', 'wordpress-seo' );
×
233
        }
234

235
        /**
236
         * Retrieves the most recently modified post indexables.
237
         *
238
         * @param int $number_of_posts The number of posts to retrieve.
239
         *
240
         * @return array<object> The indexable objects.
241
         */
NEW
242
        private function get_recent_indexables( int $number_of_posts ): array {
×
NEW
243
                return $this->indexable_repository->get_recently_modified_posts( 'post', $number_of_posts, false );
×
244
        }
245

246
        /**
247
         * Extracts and clamps the number of posts from the input.
248
         *
249
         * @param array<string, int> $input The input array.
250
         *
251
         * @return int The clamped number of posts.
252
         */
NEW
253
        private function get_number_of_posts( array $input ): int {
×
NEW
254
                $number = ( $input['number_of_posts'] ?? self::DEFAULT_NUMBER_OF_POSTS );
×
255

NEW
256
                return \max( 1, (int) $number );
×
257
        }
258
}
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