PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.0
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.0
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 / integrations / watchers / indexable-post-watcher.php

indexable-post-watcher.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.0, at src/integrations/watchers/indexable-post-watcher.php

287 lines 8.1 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\Integrations\Watchers;
4
5 use Exception;
6 use WP_Post;
7 use Yoast\WP\SEO\Builders\Indexable_Builder;
8 use Yoast\WP\SEO\Builders\Indexable_Link_Builder;
9 use Yoast\WP\SEO\Conditionals\Migrations_Conditional;
10 use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
11 use Yoast\WP\SEO\Helpers\Post_Helper;
12 use Yoast\WP\SEO\Integrations\Integration_Interface;
13 use Yoast\WP\SEO\Loggers\Logger;
14 use Yoast\WP\SEO\Models\Indexable;
15 use Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository;
16 use Yoast\WP\SEO\Repositories\Indexable_Repository;
17 use YoastSEO_Vendor\Psr\Log\LogLevel;
18
19 /**
20 * WordPress Post watcher.
21 *
22 * Fills the Indexable according to Post data.
23 */
24 class Indexable_Post_Watcher implements Integration_Interface {
25
26 /**
27 * The indexable repository.
28 *
29 * @var Indexable_Repository
30 */
31 protected $repository;
32
33 /**
34 * The indexable builder.
35 *
36 * @var Indexable_Builder
37 */
38 protected $builder;
39
40 /**
41 * The indexable hierarchy repository.
42 *
43 * @var Indexable_Hierarchy_Repository
44 */
45 private $hierarchy_repository;
46
47 /**
48 * The link builder.
49 *
50 * @var Indexable_Link_Builder
51 */
52 protected $link_builder;
53
54 /**
55 * The author archive helper.
56 *
57 * @var Author_Archive_Helper
58 */
59 private $author_archive;
60
61 /**
62 * Holds the Post_Helper instance.
63 *
64 * @var Post_Helper
65 */
66 private $post;
67
68 /**
69 * Holds the logger.
70 *
71 * @var Logger
72 */
73 protected $logger;
74
75 /**
76 * Returns the conditionals based on which this loadable should be active.
77 *
78 * @return array
79 */
80 public static function get_conditionals() {
81 return [ Migrations_Conditional::class ];
82 }
83
84 /**
85 * Indexable_Post_Watcher constructor.
86 *
87 * @param Indexable_Repository $repository The repository to use.
88 * @param Indexable_Builder $builder The post builder to use.
89 * @param Indexable_Hierarchy_Repository $hierarchy_repository The hierarchy repository to use.
90 * @param Indexable_Link_Builder $link_builder The link builder.
91 * @param Author_Archive_Helper $author_archive The author archive helper.
92 * @param Post_Helper $post The post helper.
93 * @param Logger $logger The logger.
94 */
95 public function __construct(
96 Indexable_Repository $repository,
97 Indexable_Builder $builder,
98 Indexable_Hierarchy_Repository $hierarchy_repository,
99 Indexable_Link_Builder $link_builder,
100 Author_Archive_Helper $author_archive,
101 Post_Helper $post,
102 Logger $logger
103 ) {
104 $this->repository = $repository;
105 $this->builder = $builder;
106 $this->hierarchy_repository = $hierarchy_repository;
107 $this->link_builder = $link_builder;
108 $this->author_archive = $author_archive;
109 $this->post = $post;
110 $this->logger = $logger;
111 }
112
113 /**
114 * Initializes the integration.
115 *
116 * This is the place to register hooks and filters.
117 *
118 * @return void
119 */
120 public function register_hooks() {
121 \add_action( 'wp_insert_post', [ $this, 'build_indexable' ], \PHP_INT_MAX );
122 \add_action( 'delete_post', [ $this, 'delete_indexable' ] );
123
124 \add_action( 'edit_attachment', [ $this, 'build_indexable' ], \PHP_INT_MAX );
125 \add_action( 'add_attachment', [ $this, 'build_indexable' ], \PHP_INT_MAX );
126 \add_action( 'delete_attachment', [ $this, 'delete_indexable' ] );
127 }
128
129 /**
130 * Deletes the meta when a post is deleted.
131 *
132 * @param int $post_id Post ID.
133 *
134 * @return void
135 */
136 public function delete_indexable( $post_id ) {
137 $indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false );
138
139 // Only interested in post indexables.
140 if ( ! $indexable || $indexable->object_type !== 'post' ) {
141 return;
142 }
143
144 $this->update_relations( $this->post->get_post( $post_id ) );
145
146 $this->update_has_public_posts( $indexable );
147
148 $this->hierarchy_repository->clear_ancestors( $indexable->id );
149 $this->link_builder->delete( $indexable );
150 $indexable->delete();
151 }
152
153 /**
154 * Updates the relations when the post indexable is built.
155 *
156 * @param Indexable $indexable The indexable.
157 * @param WP_Post $post The post.
158 */
159 public function updated_indexable( $indexable, $post ) {
160 // Only interested in post indexables.
161 if ( $indexable->object_type !== 'post' ) {
162 return;
163 }
164
165 if ( is_a( $post, Indexable::class ) ) {
166 _deprecated_argument( __FUNCTION__, '17.7', 'The $old_indexable argument has been deprecated.' );
167 $post = $this->post->get_post( $indexable->object_id );
168 }
169
170 $this->update_relations( $post );
171 $this->update_has_public_posts( $indexable );
172
173 $indexable->save();
174 }
175
176 /**
177 * Saves post meta.
178 *
179 * @param int $post_id Post ID.
180 *
181 * @return void
182 */
183 public function build_indexable( $post_id ) {
184 // Bail if this is a multisite installation and the site has been switched.
185 if ( $this->is_multisite_and_switched() ) {
186 return;
187 }
188
189 try {
190 $indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false );
191 $indexable = $this->builder->build_for_id_and_type( $post_id, 'post', $indexable );
192
193 $post = $this->post->get_post( $post_id );
194
195 // Build links for this post.
196 if ( $post && $indexable && \in_array( $post->post_status, $this->post->get_public_post_statuses(), true ) ) {
197 $this->link_builder->build( $indexable, $post->post_content );
198 // Save indexable to persist the updated link count.
199 $indexable->save();
200 $this->updated_indexable( $indexable, $post );
201 }
202 } catch ( Exception $exception ) {
203 $this->logger->log( LogLevel::ERROR, $exception->getMessage() );
204 }
205 }
206
207 /**
208 * Updates the has_public_posts when the post indexable is built.
209 *
210 * @param Indexable $indexable The indexable to check.
211 */
212 protected function update_has_public_posts( $indexable ) {
213 // Update the author indexable's has public posts value.
214 try {
215 $author_indexable = $this->repository->find_by_id_and_type( $indexable->author_id, 'user' );
216 $author_indexable->has_public_posts = $this->author_archive->author_has_public_posts( $author_indexable->object_id );
217 $author_indexable->save();
218 } catch ( Exception $exception ) {
219 $this->logger->log( LogLevel::ERROR, $exception->getMessage() );
220 }
221
222 // Update possible attachment's has public posts value.
223 $this->post->update_has_public_posts_on_attachments( $indexable->object_id, $indexable->is_public );
224 }
225
226 /**
227 * Updates the relations on post save or post status change.
228 *
229 * @param WP_Post $post The post that has been updated.
230 */
231 protected function update_relations( $post ) {
232 $related_indexables = $this->get_related_indexables( $post );
233
234 foreach ( $related_indexables as $indexable ) {
235 $indexable->object_last_modified = max( $indexable->object_last_modified, $post->post_modified_gmt );
236 $indexable->save();
237 }
238 }
239
240 /**
241 * Retrieves the related indexables for given post.
242 *
243 * @param WP_Post $post The post to get the indexables for.
244 *
245 * @return Indexable[] The indexables.
246 */
247 protected function get_related_indexables( $post ) {
248 /**
249 * The related indexables.
250 *
251 * @var Indexable[] $related_indexables .
252 */
253 $related_indexables = [];
254 $related_indexables[] = $this->repository->find_by_id_and_type( $post->post_author, 'user', false );
255 $related_indexables[] = $this->repository->find_for_post_type_archive( $post->post_type, false );
256 $related_indexables[] = $this->repository->find_for_home_page( false );
257
258 $taxonomies = \get_post_taxonomies( $post->ID );
259 $taxonomies = \array_filter( $taxonomies, 'is_taxonomy_viewable' );
260 $term_ids = [];
261 foreach ( $taxonomies as $taxonomy ) {
262 $terms = \get_the_terms( $post->ID, $taxonomy );
263
264 if ( empty( $terms ) || \is_wp_error( $terms ) ) {
265 continue;
266 }
267
268 $term_ids = \array_merge( $term_ids, \wp_list_pluck( $terms, 'term_id' ) );
269 }
270 $related_indexables = \array_merge(
271 $related_indexables,
272 $this->repository->find_by_multiple_ids_and_type( $term_ids, 'term', false )
273 );
274
275 return \array_filter( $related_indexables );
276 }
277
278 /**
279 * Tests if the site is multisite and switched.
280 *
281 * @return bool True when the site is multisite and switched
282 */
283 protected function is_multisite_and_switched() {
284 return \is_multisite() && \ms_is_switched();
285 }
286 }
287