PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.9
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.9
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-author-builder.php

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

248 lines 7.6 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 Yoast\WP\SEO\Exceptions\Indexable\Author_Not_Built_Exception;
6 use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
7 use Yoast\WP\SEO\Helpers\Options_Helper;
8 use Yoast\WP\SEO\Helpers\Post_Helper;
9 use Yoast\WP\SEO\Models\Indexable;
10 use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
11
12 /**
13 * Author Builder for the indexables.
14 *
15 * Formats the author meta to indexable format.
16 */
17 class Indexable_Author_Builder {
18
19 use Indexable_Social_Image_Trait;
20
21 /**
22 * The author archive helper.
23 *
24 * @var Author_Archive_Helper
25 */
26 private $author_archive;
27
28 /**
29 * The latest version of the Indexable_Author_Builder.
30 *
31 * @var int
32 */
33 protected $version;
34
35 /**
36 * Holds the options helper instance.
37 *
38 * @var Options_Helper
39 */
40 protected $options_helper;
41
42 /**
43 * Holds the taxonomy helper instance.
44 *
45 * @var Post_Helper
46 */
47 protected $post_helper;
48
49 /**
50 * Indexable_Author_Builder constructor.
51 *
52 * @param Author_Archive_Helper $author_archive The author archive helper.
53 * @param Indexable_Builder_Versions $versions The Indexable version manager.
54 * @param Options_Helper $options_helper The options helper.
55 * @param Post_Helper $post_helper The post helper.
56 */
57 public function __construct(
58 Author_Archive_Helper $author_archive,
59 Indexable_Builder_Versions $versions,
60 Options_Helper $options_helper,
61 Post_Helper $post_helper
62 ) {
63 $this->author_archive = $author_archive;
64 $this->version = $versions->get_latest_version_for_type( 'user' );
65 $this->options_helper = $options_helper;
66 $this->post_helper = $post_helper;
67 }
68
69 /**
70 * Formats the data.
71 *
72 * @param int $user_id The user to retrieve the indexable for.
73 * @param Indexable $indexable The indexable to format.
74 *
75 * @return Indexable The extended indexable.
76 *
77 * @throws Author_Not_Built_Exception When author is not built.
78 */
79 public function build( $user_id, Indexable $indexable ) {
80 $exception = $this->check_if_user_should_be_indexed( $user_id );
81 if ( $exception ) {
82 throw $exception;
83 }
84
85 $meta_data = $this->get_meta_data( $user_id );
86
87 $indexable->object_id = $user_id;
88 $indexable->object_type = 'user';
89 $indexable->permalink = \get_author_posts_url( $user_id );
90 $indexable->title = $meta_data['wpseo_title'];
91 $indexable->description = $meta_data['wpseo_metadesc'];
92 $indexable->is_cornerstone = false;
93 $indexable->is_robots_noindex = ( $meta_data['wpseo_noindex_author'] === 'on' );
94 $indexable->is_robots_nofollow = null;
95 $indexable->is_robots_noarchive = null;
96 $indexable->is_robots_noimageindex = null;
97 $indexable->is_robots_nosnippet = null;
98 $indexable->is_public = ( $indexable->is_robots_noindex ) ? false : null;
99 $indexable->has_public_posts = $this->author_archive->author_has_public_posts( $user_id );
100 $indexable->blog_id = \get_current_blog_id();
101
102 $this->reset_social_images( $indexable );
103 $this->handle_social_images( $indexable );
104
105 $timestamps = $this->get_object_timestamps( $user_id );
106 $indexable->object_published_at = $timestamps->published_at;
107 $indexable->object_last_modified = $timestamps->last_modified;
108
109 $indexable->version = $this->version;
110
111 return $indexable;
112 }
113
114 /**
115 * Retrieves the meta data for this indexable.
116 *
117 * @param int $user_id The user to retrieve the meta data for.
118 *
119 * @return array List of meta entries.
120 */
121 protected function get_meta_data( $user_id ) {
122 $keys = [
123 'wpseo_title',
124 'wpseo_metadesc',
125 'wpseo_noindex_author',
126 ];
127
128 $output = [];
129 foreach ( $keys as $key ) {
130 $output[ $key ] = $this->get_author_meta( $user_id, $key );
131 }
132
133 return $output;
134 }
135
136 /**
137 * Retrieves the author meta.
138 *
139 * @param int $user_id The user to retrieve the indexable for.
140 * @param string $key The meta entry to retrieve.
141 *
142 * @return string|null The value of the meta field.
143 */
144 protected function get_author_meta( $user_id, $key ) {
145 $value = \get_the_author_meta( $key, $user_id );
146 if ( \is_string( $value ) && $value === '' ) {
147 return null;
148 }
149
150 return $value;
151 }
152
153 /**
154 * Finds an alternative image for the social image.
155 *
156 * @param Indexable $indexable The indexable.
157 *
158 * @return array|bool False when not found, array with data when found.
159 */
160 protected function find_alternative_image( Indexable $indexable ) {
161 $gravatar_image = \get_avatar_url(
162 $indexable->object_id,
163 [
164 'size' => 500,
165 'scheme' => 'https',
166 ],
167 );
168 if ( $gravatar_image ) {
169 return [
170 'image' => $gravatar_image,
171 'source' => 'gravatar-image',
172 ];
173 }
174
175 return false;
176 }
177
178 /**
179 * Returns the timestamps for a given author.
180 *
181 * @param int $author_id The author ID.
182 *
183 * @return object An object with last_modified and published_at timestamps.
184 */
185 protected function get_object_timestamps( $author_id ) {
186 global $wpdb;
187 $post_statuses = $this->post_helper->get_public_post_statuses();
188
189 $replacements = [];
190 $replacements[] = 'post_modified_gmt';
191 $replacements[] = 'post_date_gmt';
192 $replacements[] = $wpdb->posts;
193 $replacements[] = 'post_status';
194 $replacements = \array_merge( $replacements, $post_statuses );
195 $replacements[] = 'post_password';
196 $replacements[] = 'post_author';
197 $replacements[] = $author_id;
198
199 //phpcs:disable WordPress.DB.PreparedSQLPlaceholders -- %i placeholder is still not recognized.
200 //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
201 //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
202 return $wpdb->get_row(
203 $wpdb->prepare(
204 '
205 SELECT MAX(p.%i) AS last_modified, MIN(p.%i) AS published_at
206 FROM %i AS p
207 WHERE p.%i IN (' . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ")
208 AND p.%i = ''
209 AND p.%i = %d
210 ",
211 $replacements,
212 ),
213 );
214 //phpcs:enable
215 }
216
217 /**
218 * Checks if the user should be indexed.
219 * Returns an exception with an appropriate message if not.
220 *
221 * @param string $user_id The user id.
222 *
223 * @return Author_Not_Built_Exception|null The exception if it should not be indexed, or `null` if it should.
224 */
225 protected function check_if_user_should_be_indexed( $user_id ) {
226 $exception = null;
227
228 if ( $this->author_archive->are_disabled() ) {
229 $exception = Author_Not_Built_Exception::author_archives_are_disabled( $user_id );
230 }
231 // We will check if the author has public posts the WP way, instead of the indexable way, to make sure we get proper results even if SEO optimization is not run.
232 // In case the user has no public posts, we check if the user should be indexed anyway.
233 elseif ( $this->options_helper->get( 'noindex-author-noposts-wpseo', false ) === true && $this->author_archive->author_has_public_posts_wp( $user_id ) === false ) {
234 $exception = Author_Not_Built_Exception::author_archives_are_not_indexed_for_users_without_posts( $user_id );
235 }
236
237 /**
238 * Filter: Include or exclude a user from being build and saved as an indexable.
239 * Return an `Author_Not_Built_Exception` when the indexable should not be build, with an appropriate message telling why it should not be built.
240 * Return `null` if the indexable should be build.
241 *
242 * @param Author_Not_Built_Exception|null $exception An exception if the indexable is not being built, `null` if the indexable should be built.
243 * @param string $user_id The ID of the user that should or should not be excluded.
244 */
245 return \apply_filters( 'wpseo_should_build_and_save_user_indexable', $exception, $user_id );
246 }
247 }
248