PluginProbe
AI / trunk
AI vtrunk
1.3.0 1.2.0 1.1.0 1.0.2 1.0.1 1.0.0 0.9.0 trunk 0.1.1 0.2.0 0.2.1 0.3.0 0.3.1 0.4.0 0.4.1 0.5.0 0.6.0 0.7.0 0.8.0
ai / includes / Experiments / Abilities_Explorer / Ability_Table.php

Ability_Table.php in AI trunk, at includes/Experiments/Abilities_Explorer/Ability_Table.php

352 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability Table Class
4 *
5 * Extends WP_List_Table to display abilities in a searchable, filterable table.
6 *
7 * @package WordPress\AI\Experiments\Abilities_Explorer
8 * @since 0.2.0
9 */
10
11 declare( strict_types=1 );
12
13 namespace WordPress\AI\Experiments\Abilities_Explorer;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 if ( ! class_exists( 'WP_List_Table' ) ) {
20 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
21 }
22
23 /**
24 * Ability Table Class
25 *
26 * Displays abilities in a table with search and filter functionality.
27 *
28 * @since 0.2.0
29 */
30 class Ability_Table extends \WP_List_Table {
31
32 /**
33 * Full list of abilities before pagination, used to derive filter options.
34 *
35 * @since 0.7.0
36 *
37 * @var array<array<string,mixed>>
38 */
39 private array $all_abilities = array();
40
41 /**
42 * Constructor.
43 *
44 * @since 0.2.0
45 */
46 public function __construct() {
47 parent::__construct(
48 array(
49 'singular' => 'ability',
50 'plural' => 'abilities',
51 'ajax' => false,
52 )
53 );
54 }
55
56 /**
57 * {@inheritDoc}
58 *
59 * @return array<string,mixed> Column definitions.
60 */
61 public function get_columns(): array {
62 return array(
63 'name' => __( 'Name', 'ai' ),
64 'slug' => __( 'Slug', 'ai' ),
65 'provider' => __( 'Provider', 'ai' ),
66 'actions' => __( 'Actions', 'ai' ),
67 );
68 }
69
70 /**
71 * {@inheritDoc}
72 *
73 * @return array<string,mixed> Sortable column definitions.
74 */
75 public function get_sortable_columns(): array {
76 return array(
77 'name' => array( 'name', false ),
78 'slug' => array( 'slug', false ),
79 'provider' => array( 'provider', false ),
80 );
81 }
82
83 /**
84 * {@inheritDoc}
85 */
86 public function prepare_items(): void {
87 $columns = $this->get_columns();
88 $hidden = array();
89 $sortable = $this->get_sortable_columns();
90
91 $this->_column_headers = array( $columns, $hidden, $sortable );
92
93 // Get abilities once and store the full list for filter option derivation.
94 $abilities = Ability_Handler::get_all_abilities();
95 $this->all_abilities = $abilities;
96
97 // Apply search filter.
98 $search = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
99 if ( ! empty( $search ) ) {
100 $abilities = array_filter(
101 $abilities,
102 static function ( $ability ) use ( $search ) {
103 return stripos( $ability['name'], $search ) !== false
104 || stripos( $ability['slug'], $search ) !== false
105 || stripos( $ability['description'], $search ) !== false;
106 }
107 );
108 }
109
110 // Apply provider filter.
111 $provider_filter = isset( $_REQUEST['provider'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['provider'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
112 if ( ! empty( $provider_filter ) && 'all' !== $provider_filter ) {
113 // Known origins filter by origin, so abilities carrying a custom
114 // provider label still appear under their Core/Plugin/Theme bucket
115 // (consistent with the statistics); custom providers filter by
116 // their exact label.
117 $known_origins = array( 'Core', 'Plugin', 'Theme' );
118 $abilities = array_filter(
119 $abilities,
120 static function ( $ability ) use ( $provider_filter, $known_origins ) {
121 if ( in_array( $provider_filter, $known_origins, true ) ) {
122 return ( $ability['origin'] ?? '' ) === $provider_filter;
123 }
124 return $ability['provider'] === $provider_filter;
125 }
126 );
127 }
128
129 // Apply category filter.
130 $category_filter = isset( $_REQUEST['category'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['category'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
131 if ( ! empty( $category_filter ) && 'all' !== $category_filter ) {
132 $abilities = array_filter(
133 $abilities,
134 static function ( $ability ) use ( $category_filter ) {
135 return ( $ability['category'] ?? '' ) === $category_filter;
136 }
137 );
138 }
139
140 // Apply sorting.
141 $orderby = isset( $_REQUEST['orderby'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ) : 'name'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
142 $order = isset( $_REQUEST['order'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) : 'asc'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
143
144 usort(
145 $abilities,
146 static function ( $a, $b ) use ( $orderby, $order ) {
147 $result = 0;
148
149 if ( isset( $a[ $orderby ] ) && isset( $b[ $orderby ] ) ) {
150 $result = strcasecmp( $a[ $orderby ], $b[ $orderby ] );
151 }
152
153 return 'asc' === $order ? $result : -$result;
154 }
155 );
156
157 // Pagination.
158 $per_page = 20;
159 $current_page = $this->get_pagenum();
160 $total_items = count( $abilities );
161
162 $this->set_pagination_args(
163 array(
164 'total_items' => $total_items,
165 'per_page' => $per_page,
166 'total_pages' => (int) ceil( $total_items / $per_page ),
167 )
168 );
169
170 $this->items = array_slice( $abilities, ( $current_page - 1 ) * $per_page, $per_page );
171 }
172
173 /**
174 * Get sorted unique categories derived from the already-fetched ability list.
175 *
176 * @since 0.7.0
177 *
178 * @return array<string>
179 */
180 public function get_unique_categories(): array {
181 $categories = array();
182
183 foreach ( $this->all_abilities as $ability ) {
184 if ( empty( $ability['category'] ) ) {
185 continue;
186 }
187
188 $categories[] = $ability['category'];
189 }
190
191 $categories = array_unique( $categories );
192 sort( $categories );
193
194 return $categories;
195 }
196
197 /**
198 * Gets sorted unique providers derived from the already-fetched ability list.
199 *
200 * Known origins (Core, Plugin, Theme) come first in their canonical order;
201 * any custom providers (set via an ability's `meta['provider']`) follow
202 * alphabetically.
203 *
204 * @since 1.3.0
205 *
206 * @return list<string> Known provider origins followed by custom providers sorted alphabetically.
207 */
208 public function get_unique_providers(): array {
209 $known = array( 'Core', 'Plugin', 'Theme' );
210 $custom = array();
211
212 foreach ( $this->all_abilities as $ability ) {
213 if ( empty( $ability['provider'] ) || in_array( $ability['provider'], $known, true ) ) {
214 continue;
215 }
216
217 $custom[] = $ability['provider'];
218 }
219
220 $custom = array_unique( $custom );
221 sort( $custom );
222
223 return array_merge( $known, $custom );
224 }
225
226 /**
227 * {@inheritDoc}
228 *
229 * @param array<string,mixed> $item Item data.
230 */
231 public function column_default( $item, $column_name ): string {
232 return isset( $item[ $column_name ] ) ? esc_html( $item[ $column_name ] ) : '—';
233 }
234
235 /**
236 * {@inheritDoc}
237 *
238 * @param array<string,mixed> $item Item data.
239 */
240 public function column_name( $item ): string {
241 $detail_url = add_query_arg(
242 array(
243 'page' => 'ai-abilities-explorer',
244 'action' => 'view',
245 'ability' => $item['slug'],
246 ),
247 admin_url( 'tools.php' )
248 );
249
250 return sprintf(
251 '<strong><a href="%s">%s</a></strong>',
252 esc_url( $detail_url ),
253 esc_html( $item['name'] )
254 );
255 }
256
257 /**
258 * {@inheritDoc}
259 *
260 * @param array<string,mixed> $item Item data.
261 */
262 public function column_slug( $item ): string {
263 return sprintf(
264 '<code>%s</code>',
265 esc_html( $item['slug'] )
266 );
267 }
268
269 /**
270 * {@inheritDoc}
271 *
272 * @param array<string,mixed> $item Item data.
273 */
274 public function column_provider( $item ): string {
275 $provider = $item['provider'];
276 $class = 'ability-provider ability-provider-' . sanitize_title( $provider );
277
278 return sprintf(
279 '<span class="%s">%s</span>',
280 esc_attr( $class ),
281 esc_html( Ability_Handler::get_provider_label( $provider ) )
282 );
283 }
284
285 /**
286 * {@inheritDoc}
287 *
288 * @param array<string,mixed> $item Item data.
289 */
290 public function column_actions( $item ): string {
291 $detail_url = add_query_arg(
292 array(
293 'page' => 'ai-abilities-explorer',
294 'action' => 'view',
295 'ability' => $item['slug'],
296 ),
297 admin_url( 'tools.php' )
298 );
299
300 $test_url = add_query_arg(
301 array(
302 'page' => 'ai-abilities-explorer',
303 'action' => 'test',
304 'ability' => $item['slug'],
305 ),
306 admin_url( 'tools.php' )
307 );
308
309 return sprintf(
310 '<a href="%s" class="button button-small">%s</a> <a href="%s" class="button button-small button-primary">%s</a>',
311 esc_url( $detail_url ),
312 esc_html__( 'View', 'ai' ),
313 esc_url( $test_url ),
314 esc_html__( 'Test', 'ai' )
315 );
316 }
317
318 /**
319 * {@inheritDoc}
320 */
321 public function extra_tablenav( $which ): void {
322 if ( 'top' !== $which ) {
323 return;
324 }
325
326 $provider_filter = isset( $_REQUEST['provider'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['provider'] ) ) : 'all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
327 $category_filter = isset( $_REQUEST['category'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['category'] ) ) : 'all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
328
329 ?>
330 <div class="alignleft actions">
331 <label for="filter-by-provider" class="screen-reader-text"><?php esc_html_e( 'Filter by provider', 'ai' ); ?></label>
332 <select name="provider" id="filter-by-provider">
333 <option value="all" <?php selected( $provider_filter, 'all' ); ?>><?php esc_html_e( 'All Providers', 'ai' ); ?></option>
334 <?php foreach ( $this->get_unique_providers() as $provider ) : ?>
335 <option value="<?php echo esc_attr( $provider ); ?>" <?php selected( $provider_filter, $provider ); ?>><?php echo esc_html( Ability_Handler::get_provider_label( $provider ) ); ?></option>
336 <?php endforeach; ?>
337 </select>
338
339 <label for="filter-by-category" class="screen-reader-text"><?php esc_html_e( 'Filter by category', 'ai' ); ?></label>
340 <select name="category" id="filter-by-category">
341 <option value="all" <?php selected( $category_filter, 'all' ); ?>><?php esc_html_e( 'All Categories', 'ai' ); ?></option>
342 <?php foreach ( $this->get_unique_categories() as $category ) : ?>
343 <option value="<?php echo esc_attr( $category ); ?>" <?php selected( $category_filter, $category ); ?>><?php echo esc_html( $category ); ?></option>
344 <?php endforeach; ?>
345 </select>
346
347 <?php submit_button( __( 'Filter', 'ai' ), '', 'filter_action', false ); ?>
348 </div>
349 <?php
350 }
351 }
352