PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.6
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.6
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 18.6, at src/integrations/admin/admin-columns-cache-integration.php

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