PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.3
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.3
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 / admin / admin-columns-cache-integration.php

admin-columns-cache-integration.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.3, at src/integrations/admin/admin-columns-cache-integration.php

280 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\Integrations\Admin;
4
5 use WP_Post;
6 use Yoast\WP\SEO\Conditionals\Admin_Conditional;
7 use Yoast\WP\SEO\Integrations\Integration_Interface;
8 use Yoast\WP\SEO\Models\Indexable;
9 use Yoast\WP\SEO\Repositories\Indexable_Repository;
10
11 /**
12 * Admin_Columns_Cache_Integration class.
13 */
14 class Admin_Columns_Cache_Integration implements Integration_Interface {
15
16 /**
17 * Cache of indexables.
18 *
19 * @var Indexable[]
20 */
21 protected $indexable_cache = [];
22
23 /**
24 * The indexable repository.
25 *
26 * @var Indexable_Repository
27 */
28 protected $indexable_repository;
29
30 /**
31 * Returns the conditionals based on which this loadable should be active.
32 *
33 * In this case: only when on an admin page.
34 *
35 * @return array The conditionals.
36 */
37 public static function get_conditionals() {
38 return [ Admin_Conditional::class ];
39 }
40
41 /**
42 * Admin_Columns_Cache_Integration constructor.
43 *
44 * @param Indexable_Repository $indexable_repository The indexable repository.
45 */
46 public function __construct( Indexable_Repository $indexable_repository ) {
47 $this->indexable_repository = $indexable_repository;
48 }
49
50 /**
51 * Registers the appropriate actions and filters to fill the cache with
52 * indexables on admin pages.
53 *
54 * This cache is used in showing the Yoast SEO columns on the posts overview
55 * page (e.g. keyword score, incoming link count, etc.)
56 *
57 * @return void
58 */
59 public function register_hooks() {
60 // Hook into tablenav to calculate links and linked.
61 \add_action( 'manage_posts_extra_tablenav', [ $this, 'maybe_fill_cache' ] );
62 }
63
64 /**
65 * Makes sure we calculate all values in one query by filling our cache beforehand.
66 *
67 * @param string $target Extra table navigation location which is triggered.
68 *
69 * @return void
70 */
71 public function maybe_fill_cache( $target ) {
72 if ( $target === 'top' ) {
73 $this->fill_cache();
74 }
75 }
76
77 /**
78 * Fills the cache of indexables for all known post IDs.
79 *
80 * @return void
81 */
82 public function fill_cache() {
83 global $wp_query;
84
85 // No need to continue building a cache if the main query did not return anything to cache.
86 if ( empty( $wp_query->posts ) ) {
87 return;
88 }
89
90 $posts = $wp_query->posts;
91 $post_ids = [];
92
93 // Post lists return a list of objects.
94 if ( isset( $posts[0] ) && \is_a( $posts[0], 'WP_Post' ) ) {
95 $post_ids = \wp_list_pluck( $posts, 'ID' );
96 }
97 elseif ( isset( $posts[0] ) && \is_object( $posts[0] ) ) {
98 $post_ids = $this->get_current_page_page_ids( $posts );
99 }
100 elseif ( ! empty( $posts ) ) {
101 // Page list returns an array of post IDs.
102 $post_ids = \array_keys( $posts );
103 }
104
105 if ( empty( $post_ids ) ) {
106 return;
107 }
108
109 if ( isset( $posts[0] ) && ! \is_a( $posts[0], WP_Post::class ) ) {
110 // Prime the post caches as core would to avoid duplicate queries.
111 // This needs to be done as this executes before core does.
112 \_prime_post_caches( $post_ids );
113 }
114
115 $indexables = $this->indexable_repository->find_by_multiple_ids_and_type( $post_ids, 'post', false );
116
117 foreach ( $indexables as $indexable ) {
118 if ( $indexable instanceof Indexable ) {
119 $this->indexable_cache[ $indexable->object_id ] = $indexable;
120 }
121 }
122 }
123
124 /**
125 * Returns the indexable for a given post ID.
126 *
127 * @param int $post_id The post ID.
128 *
129 * @return Indexable|false The indexable. False if none could be found.
130 */
131 public function get_indexable( $post_id ) {
132 if ( ! \array_key_exists( $post_id, $this->indexable_cache ) ) {
133 $this->indexable_cache[ $post_id ] = $this->indexable_repository->find_by_id_and_type( $post_id, 'post' );
134 }
135 return $this->indexable_cache[ $post_id ];
136 }
137
138 /**
139 * Gets all the page IDs set to be shown on the current page.
140 * This is copied over with some changes from WP_Posts_List_Table::_display_rows_hierarchical.
141 *
142 * @param array $pages The pages, each containing an ID and post_parent.
143 *
144 * @return array The IDs of all pages shown on the current page.
145 */
146 private function get_current_page_page_ids( $pages ) {
147 global $per_page;
148 $pagenum = isset( $_REQUEST['paged'] ) ? \absint( $_REQUEST['paged'] ) : 0;
149 $pagenum = \max( 1, $pagenum );
150
151 /*
152 * Arrange pages into two parts: top level pages and children_pages
153 * children_pages is two dimensional array, eg.
154 * children_pages[10][] contains all sub-pages whose parent is 10.
155 * It only takes O( N ) to arrange this and it takes O( 1 ) for subsequent lookup operations
156 * If searching, ignore hierarchy and treat everything as top level
157 */
158 if ( empty( $_REQUEST['s'] ) ) {
159 $top_level_pages = [];
160 $children_pages = [];
161 $pages_map = [];
162
163 foreach ( $pages as $page ) {
164
165 // Catch and repair bad pages.
166 if ( $page->post_parent === $page->ID ) {
167 $page->post_parent = 0;
168 }
169
170 if ( $page->post_parent === 0 ) {
171 $top_level_pages[] = $page;
172 }
173 else {
174 $children_pages[ $page->post_parent ][] = $page;
175 }
176 $pages_map[ $page->ID ] = $page;
177 }
178
179 $pages = $top_level_pages;
180 }
181
182 $count = 0;
183 $start = ( ( $pagenum - 1 ) * $per_page );
184 $end = ( $start + $per_page );
185 $to_display = [];
186
187 foreach ( $pages as $page ) {
188 if ( $count >= $end ) {
189 break;
190 }
191
192 if ( $count >= $start ) {
193 $to_display[] = $page->ID;
194 }
195
196 ++$count;
197
198 $this->get_child_page_ids( $children_pages, $count, $page->ID, $start, $end, $to_display, $pages_map );
199 }
200
201 // If it is the last pagenum and there are orphaned pages, display them with paging as well.
202 if ( isset( $children_pages ) && $count < $end ) {
203 foreach ( $children_pages as $orphans ) {
204 foreach ( $orphans as $op ) {
205 if ( $count >= $end ) {
206 break;
207 }
208
209 if ( $count >= $start ) {
210 $to_display[] = $op->ID;
211 }
212
213 ++$count;
214 }
215 }
216 }
217
218 return $to_display;
219 }
220
221 /**
222 * Adds all child pages due to be shown on the current page to the $to_display array.
223 * Copied over with some changes from WP_Posts_List_Table::_page_rows.
224 *
225 * @param array $children_pages The full map of child pages.
226 * @param int $count The number of pages already processed.
227 * @param int $parent_id The id of the parent that's currently being processed.
228 * @param int $start The number at which the current overview starts.
229 * @param int $end The number at which the current overview ends.
230 * @param int $to_display The page IDs to be shown.
231 * @param int $pages_map A map of page ID to an object with ID and post_parent.
232 *
233 * @return void
234 */
235 private function get_child_page_ids( &$children_pages, &$count, $parent_id, $start, $end, &$to_display, &$pages_map ) {
236 if ( ! isset( $children_pages[ $parent_id ] ) ) {
237 return;
238 }
239
240 foreach ( $children_pages[ $parent_id ] as $page ) {
241 if ( $count >= $end ) {
242 break;
243 }
244
245 // If the page starts in a subtree, print the parents.
246 if ( $count === $start && $page->post_parent > 0 ) {
247 $my_parents = [];
248 $my_parent = $page->post_parent;
249 while ( $my_parent ) {
250 // Get the ID from the list or the attribute if my_parent is an object.
251 $parent_id = $my_parent;
252 if ( \is_object( $my_parent ) ) {
253 $parent_id = $my_parent->ID;
254 }
255
256 $my_parent = $pages_map[ $parent_id ];
257 $my_parents[] = $my_parent;
258 if ( ! $my_parent->post_parent ) {
259 break;
260 }
261 $my_parent = $my_parent->post_parent;
262 }
263 while ( $my_parent = \array_pop( $my_parents ) ) {
264 $to_display[] = $my_parent->ID;
265 }
266 }
267
268 if ( $count >= $start ) {
269 $to_display[] = $page->ID;
270 }
271
272 ++$count;
273
274 $this->get_child_page_ids( $children_pages, $count, $page->ID, $start, $end, $to_display, $pages_map );
275 }
276
277 unset( $children_pages[ $parent_id ] ); // Required in order to keep track of orphans.
278 }
279 }
280