PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.1
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / builders / indexable-term-builder.php

indexable-term-builder.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.1, at src/builders/indexable-term-builder.php

273 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\SEO\Builders;
4
5 use wpdb;
6 use Yoast\WP\SEO\Exceptions\Indexable\Invalid_Term_Exception;
7 use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Found_Exception;
8 use Yoast\WP\SEO\Helpers\Post_Helper;
9 use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
10 use Yoast\WP\SEO\Models\Indexable;
11 use Yoast\WP\SEO\Repositories\Indexable_Repository;
12 use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
13
14 /**
15 * Term Builder for the indexables.
16 *
17 * Formats the term meta to indexable format.
18 */
19 class Indexable_Term_Builder {
20
21 use Indexable_Social_Image_Trait;
22
23 /**
24 * Holds the taxonomy helper instance.
25 *
26 * @var Taxonomy_Helper
27 */
28 protected $taxonomy_helper;
29
30 /**
31 * The latest version of the Indexable_Term_Builder.
32 *
33 * @var int
34 */
35 protected $version;
36
37 /**
38 * Holds the taxonomy helper instance.
39 *
40 * @var Post_Helper
41 */
42 protected $post_helper;
43
44 /**
45 * The WPDB instance.
46 *
47 * @var wpdb
48 */
49 protected $wpdb;
50
51 /**
52 * Indexable_Term_Builder constructor.
53 *
54 * @param Taxonomy_Helper $taxonomy_helper The taxonomy helper.
55 * @param Indexable_Builder_Versions $versions The latest version of each Indexable Builder.
56 * @param Post_Helper $post_helper The post helper.
57 * @param wpdb $wpdb The WPDB instance.
58 */
59 public function __construct(
60 Taxonomy_Helper $taxonomy_helper,
61 Indexable_Builder_Versions $versions,
62 Post_Helper $post_helper,
63 wpdb $wpdb
64 ) {
65 $this->taxonomy_helper = $taxonomy_helper;
66 $this->version = $versions->get_latest_version_for_type( 'term' );
67 $this->post_helper = $post_helper;
68 $this->wpdb = $wpdb;
69 }
70
71 /**
72 * Formats the data.
73 *
74 * @param int $term_id ID of the term to save data for.
75 * @param Indexable $indexable The indexable to format.
76 *
77 * @return bool|Indexable The extended indexable. False when unable to build.
78 *
79 * @throws Invalid_Term_Exception When the term is invalid.
80 * @throws Term_Not_Found_Exception When the term is not found.
81 */
82 public function build( $term_id, $indexable ) {
83 $term = \get_term( $term_id );
84
85 if ( $term === null ) {
86 throw new Term_Not_Found_Exception();
87 }
88
89 if ( \is_wp_error( $term ) ) {
90 throw new Invalid_Term_Exception( $term->get_error_message() );
91 }
92
93 $term_link = \get_term_link( $term, $term->taxonomy );
94
95 if ( \is_wp_error( $term_link ) ) {
96 throw new Invalid_Term_Exception( $term_link->get_error_message() );
97 }
98
99 $term_meta = $this->taxonomy_helper->get_term_meta( $term );
100
101 $indexable->object_id = $term_id;
102 $indexable->object_type = 'term';
103 $indexable->object_sub_type = $term->taxonomy;
104 $indexable->permalink = $term_link;
105 $indexable->blog_id = \get_current_blog_id();
106
107 $indexable->primary_focus_keyword_score = $this->get_keyword_score(
108 $this->get_meta_value( 'wpseo_focuskw', $term_meta ),
109 $this->get_meta_value( 'wpseo_linkdex', $term_meta )
110 );
111
112 $indexable->is_robots_noindex = $this->get_noindex_value( $this->get_meta_value( 'wpseo_noindex', $term_meta ) );
113 $indexable->is_public = ( $indexable->is_robots_noindex === null ) ? null : ! $indexable->is_robots_noindex;
114
115 $this->reset_social_images( $indexable );
116
117 foreach ( $this->get_indexable_lookup() as $meta_key => $indexable_key ) {
118 $indexable->{$indexable_key} = $this->get_meta_value( $meta_key, $term_meta );
119 }
120
121 if ( empty( $indexable->breadcrumb_title ) ) {
122 $indexable->breadcrumb_title = $term->name;
123 }
124
125 $this->handle_social_images( $indexable );
126
127 $indexable->is_cornerstone = $this->get_meta_value( 'wpseo_is_cornerstone', $term_meta );
128
129 // Not implemented yet.
130 $indexable->is_robots_nofollow = null;
131 $indexable->is_robots_noarchive = null;
132 $indexable->is_robots_noimageindex = null;
133 $indexable->is_robots_nosnippet = null;
134
135 $timestamps = $this->get_object_timestamps( $term_id, $term->taxonomy );
136 $indexable->object_published_at = $timestamps->published_at;
137 $indexable->object_last_modified = $timestamps->last_modified;
138
139 $indexable->version = $this->version;
140
141 return $indexable;
142 }
143
144 /**
145 * Converts the meta noindex value to the indexable value.
146 *
147 * @param string $meta_value Term meta to base the value on.
148 *
149 * @return bool|null
150 */
151 protected function get_noindex_value( $meta_value ) {
152 if ( $meta_value === 'noindex' ) {
153 return true;
154 }
155
156 if ( $meta_value === 'index' ) {
157 return false;
158 }
159
160 return null;
161 }
162
163 /**
164 * Determines the focus keyword score.
165 *
166 * @param string $keyword The focus keyword that is set.
167 * @param int $score The score saved on the meta data.
168 *
169 * @return int|null Score to use.
170 */
171 protected function get_keyword_score( $keyword, $score ) {
172 if ( empty( $keyword ) ) {
173 return null;
174 }
175
176 return $score;
177 }
178
179 /**
180 * Retrieves the lookup table.
181 *
182 * @return array Lookup table for the indexable fields.
183 */
184 protected function get_indexable_lookup() {
185 return [
186 'wpseo_canonical' => 'canonical',
187 'wpseo_focuskw' => 'primary_focus_keyword',
188 'wpseo_title' => 'title',
189 'wpseo_desc' => 'description',
190 'wpseo_content_score' => 'readability_score',
191 'wpseo_bctitle' => 'breadcrumb_title',
192 'wpseo_opengraph-title' => 'open_graph_title',
193 'wpseo_opengraph-description' => 'open_graph_description',
194 'wpseo_opengraph-image' => 'open_graph_image',
195 'wpseo_opengraph-image-id' => 'open_graph_image_id',
196 'wpseo_twitter-title' => 'twitter_title',
197 'wpseo_twitter-description' => 'twitter_description',
198 'wpseo_twitter-image' => 'twitter_image',
199 'wpseo_twitter-image-id' => 'twitter_image_id',
200 ];
201 }
202
203 /**
204 * Retrieves a meta value from the given meta data.
205 *
206 * @param string $meta_key The key to extract.
207 * @param array $term_meta The meta data.
208 *
209 * @return string|null The meta value.
210 */
211 protected function get_meta_value( $meta_key, $term_meta ) {
212 if ( ! $term_meta || ! \array_key_exists( $meta_key, $term_meta ) ) {
213 return null;
214 }
215
216 $value = $term_meta[ $meta_key ];
217 if ( \is_string( $value ) && $value === '' ) {
218 return null;
219 }
220
221 return $value;
222 }
223
224 /**
225 * Finds an alternative image for the social image.
226 *
227 * @param Indexable $indexable The indexable.
228 *
229 * @return array|bool False when not found, array with data when found.
230 */
231 protected function find_alternative_image( Indexable $indexable ) {
232 $content_image = $this->image->get_term_content_image( $indexable->object_id );
233 if ( $content_image ) {
234 return [
235 'image' => $content_image,
236 'source' => 'first-content-image',
237 ];
238 }
239
240 return false;
241 }
242
243 /**
244 * Returns the timestamps for a given term.
245 *
246 * @param int $term_id The term ID.
247 * @param string $taxonomy The taxonomy.
248 *
249 * @return object An object with last_modified and published_at timestamps.
250 */
251 protected function get_object_timestamps( $term_id, $taxonomy ) {
252 $post_statuses = $this->post_helper->get_public_post_statuses();
253
254 $sql = "
255 SELECT MAX(p.post_modified_gmt) AS last_modified, MIN(p.post_date_gmt) AS published_at
256 FROM {$this->wpdb->posts} AS p
257 INNER JOIN {$this->wpdb->term_relationships} AS term_rel
258 ON term_rel.object_id = p.ID
259 INNER JOIN {$this->wpdb->term_taxonomy} AS term_tax
260 ON term_tax.term_taxonomy_id = term_rel.term_taxonomy_id
261 AND term_tax.taxonomy = %s
262 AND term_tax.term_id = %d
263 WHERE p.post_status IN (" . implode( ', ', array_fill( 0, count( $post_statuses ), '%s' ) ) . ")
264 AND p.post_password = ''
265 ";
266
267 $replacements = \array_merge( [ $taxonomy, $term_id ], $post_statuses );
268
269 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- We are using wpdb prepare.
270 return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $replacements ) );
271 }
272 }
273