PluginProbe
wpForo Forum / 3.2.0
wpForo Forum v3.2.0
3.2.0 3.1.7 3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 All 140 releases
wpforo / admin / pages / tabs / ai-features-tab-ai-logs.php

ai-features-tab-ai-logs.php in wpForo Forum 3.2.0, at admin/pages/tabs/ai-features-tab-ai-logs.php

473 lines 19.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * AI Logs Tab
4 *
5 * Displays AI action logs with filtering, search, and management capabilities.
6 *
7 * @since 3.0.0
8 */
9
10 use wpforo\classes\AILogs;
11
12 /**
13 * Render the AI Logs tab
14 *
15 * @param bool $is_connected Whether the AI service is connected
16 * @param array $status Tenant status data
17 */
18 function wpforo_ai_render_ai_logs_tab( $is_connected, $status ) {
19 if ( ! $is_connected ) {
20 echo '<div class="notice notice-warning"><p>' . esc_html__( 'Please connect to AI service to view logs.', 'wpforo' ) . '</p></div>';
21 return;
22 }
23
24 // Get all boards and determine current board
25 $boards = WPF()->board->get_boards();
26 $current_boardid = isset( $_GET['boardid'] ) ? intval( $_GET['boardid'] ) : 0;
27
28 // Validate boardid exists
29 $valid_boardids = array_column( $boards, 'boardid' );
30 if ( ! in_array( $current_boardid, $valid_boardids ) ) {
31 $current_boardid = 0; // Default to main board
32 }
33
34 // Switch to the selected board context
35 if ( $current_boardid > 0 ) {
36 WPF()->change_board( $current_boardid );
37 }
38
39 // Render board sub-tabs if multiple boards exist
40 if ( count( $boards ) > 1 ) :
41 // Find current board title
42 $current_board_title = __( 'Board', 'wpforo' );
43 foreach ( $boards as $board ) {
44 if ( isset( $board['boardid'] ) && (int) $board['boardid'] === $current_boardid ) {
45 $current_board_title = isset( $board['settings']['title'] ) ? esc_html( $board['settings']['title'] ) : ( isset( $board['title'] ) ? esc_html( $board['title'] ) : $current_board_title );
46 break;
47 }
48 }
49 ?>
50 <div class="wpforo-ai-board-tabs">
51 <div class="wpforo-ai-board-current">
52 <?php echo esc_html__( 'Board:', 'wpforo' ); ?> <strong><?php echo $current_board_title; ?></strong>
53 </div>
54 <div class="wpforo-ai-board-tabs-list">
55 <?php foreach ( $boards as $board ) :
56 $board_id = isset( $board['boardid'] ) ? (int) $board['boardid'] : 0;
57 $board_label = isset( $board['title'] ) ? esc_html( $board['title'] ) : __( 'Board', 'wpforo' ) . ' ' . $board_id;
58 $tab_url = add_query_arg( [
59 'page' => 'wpforo-ai',
60 'tab' => 'ai_logs',
61 'boardid' => $board_id,
62 ], admin_url( 'admin.php' ) );
63 $active_class = ( $current_boardid === $board_id ) ? 'active' : '';
64 ?>
65 <a href="<?php echo esc_url( $tab_url ); ?>" class="wpforo-ai-board-tab <?php echo esc_attr( $active_class ); ?>">
66 <span class="dashicons dashicons-category"></span>
67 <?php echo $board_label; ?>
68 </a>
69 <?php endforeach; ?>
70 </div>
71 </div>
72 <?php
73 endif;
74
75 // Get action types for filter dropdown
76 $action_types = AILogs::get_action_types();
77
78 // Get per page setting
79 $per_page = WPF()->ai_logs->get_per_page();
80
81 // Get initial logs
82 $initial_logs = WPF()->ai_logs->get_logs( [
83 'limit' => $per_page,
84 'offset' => 0,
85 'orderby' => 'created',
86 'order' => 'DESC',
87 ] );
88 $total_logs = WPF()->ai_logs->get_logs_count( [] );
89
90 // Create nonce for AJAX
91 $nonce = wp_create_nonce( 'wpforo_ai_logs_nonce' );
92 ?>
93
94 <div class="wpforo-ai-logs-tab" id="wpforo-ai-logs-tab" data-nonce="<?php echo esc_attr( $nonce ); ?>" data-boardid="<?php echo esc_attr( $current_boardid ); ?>">
95
96 <!-- Header Section -->
97 <div class="wpforo-ai-box wpforo-ai-logs-header-box">
98 <div class="wpforo-ai-box-body">
99 <div class="wpforo-ai-logs-header">
100 <div class="wpforo-ai-logs-info">
101 <h2>
102 <span class="dashicons dashicons-list-view"></span>
103 <?php esc_html_e( 'AI Activity Logs', 'wpforo' ); ?>
104 <span class="wpforo-ai-logs-count" id="wpforo-ai-logs-total-count">(<?php echo number_format( $total_logs ); ?>)</span>
105 </h2>
106 <p class="wpforo-ai-description">
107 <?php esc_html_e( 'View all AI actions performed on your forum including searches, translations, content generation, and more.', 'wpforo' ); ?>
108 </p>
109 </div>
110 <div class="wpforo-ai-logs-actions">
111 <button type="button" class="button button-secondary wpforo-ai-empty-logs-btn" id="wpforo-ai-empty-logs-btn">
112 <span class="dashicons dashicons-trash"></span>
113 <?php esc_html_e( 'Empty All Logs', 'wpforo' ); ?>
114 </button>
115 </div>
116 </div>
117 </div>
118 </div>
119
120 <!-- Filters Section -->
121 <div class="wpforo-ai-box wpforo-ai-logs-filters-box">
122 <div class="wpforo-ai-box-body">
123 <div class="wpforo-ai-logs-filters">
124 <!-- Action Type Filter -->
125 <div class="wpforo-ai-filter-group">
126 <label for="wpforo-ai-logs-filter-action"><?php esc_html_e( 'Action Type', 'wpforo' ); ?></label>
127 <select id="wpforo-ai-logs-filter-action" class="wpforo-ai-logs-filter">
128 <option value=""><?php esc_html_e( 'All Actions', 'wpforo' ); ?></option>
129 <?php foreach ( $action_types as $key => $label ) : ?>
130 <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $label ); ?></option>
131 <?php endforeach; ?>
132 </select>
133 </div>
134
135 <!-- Date Filter -->
136 <div class="wpforo-ai-filter-group">
137 <label for="wpforo-ai-logs-filter-date"><?php esc_html_e( 'Date Range', 'wpforo' ); ?></label>
138 <select id="wpforo-ai-logs-filter-date" class="wpforo-ai-logs-filter">
139 <option value="all"><?php esc_html_e( 'All Time', 'wpforo' ); ?></option>
140 <option value="last_hour"><?php esc_html_e( 'Last Hour', 'wpforo' ); ?></option>
141 <option value="last_day"><?php esc_html_e( 'Last 24 Hours', 'wpforo' ); ?></option>
142 <option value="last_week"><?php esc_html_e( 'Last 7 Days', 'wpforo' ); ?></option>
143 <option value="last_month"><?php esc_html_e( 'Last 30 Days', 'wpforo' ); ?></option>
144 </select>
145 </div>
146
147 <!-- Status Filter -->
148 <div class="wpforo-ai-filter-group">
149 <label for="wpforo-ai-logs-filter-status"><?php esc_html_e( 'Status', 'wpforo' ); ?></label>
150 <select id="wpforo-ai-logs-filter-status" class="wpforo-ai-logs-filter">
151 <option value=""><?php esc_html_e( 'All Status', 'wpforo' ); ?></option>
152 <option value="success"><?php esc_html_e( 'Success', 'wpforo' ); ?></option>
153 <option value="error"><?php esc_html_e( 'Error', 'wpforo' ); ?></option>
154 <option value="cached"><?php esc_html_e( 'Cached', 'wpforo' ); ?></option>
155 </select>
156 </div>
157
158 <!-- User Type Filter -->
159 <div class="wpforo-ai-filter-group">
160 <label for="wpforo-ai-logs-filter-user-type"><?php esc_html_e( 'Triggered By', 'wpforo' ); ?></label>
161 <select id="wpforo-ai-logs-filter-user-type" class="wpforo-ai-logs-filter">
162 <option value=""><?php esc_html_e( 'All', 'wpforo' ); ?></option>
163 <option value="user"><?php esc_html_e( 'User', 'wpforo' ); ?></option>
164 <option value="guest"><?php esc_html_e( 'Guest', 'wpforo' ); ?></option>
165 <option value="cron"><?php esc_html_e( 'Cron Job', 'wpforo' ); ?></option>
166 <option value="system"><?php esc_html_e( 'System', 'wpforo' ); ?></option>
167 </select>
168 </div>
169
170 <!-- Search -->
171 <div class="wpforo-ai-filter-group wpforo-ai-filter-search">
172 <label for="wpforo-ai-logs-search"><?php esc_html_e( 'Search', 'wpforo' ); ?></label>
173 <input type="text" id="wpforo-ai-logs-search" class="wpforo-ai-logs-filter"
174 placeholder="<?php esc_attr_e( 'Search logs...', 'wpforo' ); ?>">
175 </div>
176
177 <!-- Filter Button -->
178 <div class="wpforo-ai-filter-group wpforo-ai-filter-button">
179 <button type="button" class="button button-primary" id="wpforo-ai-logs-apply-filter">
180 <span class="dashicons dashicons-search"></span>
181 <?php esc_html_e( 'Apply', 'wpforo' ); ?>
182 </button>
183 <?php if ( isset( WPF()->ai_client ) && WPF()->ai_client->is_feature_available( 'ai_assistant_chatbot' ) ) : ?>
184 <button type="button" class="button button-primary" id="wpforo-ai-logs-chat-messages-btn">
185 <span class="dashicons dashicons-format-chat"></span>
186 <?php esc_html_e( 'AI ChatBot Messages', 'wpforo' ); ?>
187 </button>
188 <?php endif; ?>
189 <button type="button" class="button button-secondary" id="wpforo-ai-logs-reset-filter">
190 <?php esc_html_e( 'Reset', 'wpforo' ); ?>
191 </button>
192 </div>
193 </div>
194 </div>
195 </div>
196
197 <!-- Logs Table Section -->
198 <div class="wpforo-ai-box wpforo-ai-logs-table-box">
199 <div class="wpforo-ai-box-header">
200 <h3>
201 <?php esc_html_e( 'Activity Logs', 'wpforo' ); ?>
202 <span class="wpforo-ai-logs-showing" id="wpforo-ai-logs-showing"></span>
203 </h3>
204 <div class="wpforo-ai-logs-bulk-actions">
205 <select id="wpforo-ai-logs-bulk-action" class="wpforo-ai-bulk-action-select">
206 <option value=""><?php esc_html_e( 'Bulk Actions', 'wpforo' ); ?></option>
207 <option value="delete"><?php esc_html_e( 'Delete Selected', 'wpforo' ); ?></option>
208 </select>
209 <button type="button" class="button wpforo-ai-bulk-apply" id="wpforo-ai-logs-bulk-apply">
210 <?php esc_html_e( 'Apply', 'wpforo' ); ?>
211 </button>
212 </div>
213 </div>
214 <div class="wpforo-ai-box-body">
215 <!-- Loading indicator -->
216 <div class="wpforo-ai-logs-loading" id="wpforo-ai-logs-loading" style="display: none;">
217 <span class="spinner is-active"></span>
218 <?php esc_html_e( 'Loading logs...', 'wpforo' ); ?>
219 </div>
220
221 <!-- Logs Table -->
222 <table class="wp-list-table widefat fixed striped wpforo-ai-logs-table" id="wpforo-ai-logs-table">
223 <thead>
224 <tr>
225 <th class="check-column">
226 <input type="checkbox" id="wpforo-ai-logs-select-all" class="wpforo-ai-select-all-logs">
227 </th>
228 <th class="column-datetime" style="width: 140px;"><?php esc_html_e( 'Date/Time', 'wpforo' ); ?></th>
229 <th class="column-action-type" style="width: 150px;"><?php esc_html_e( 'Action', 'wpforo' ); ?></th>
230 <th class="column-user" style="width: 120px;"><?php esc_html_e( 'User', 'wpforo' ); ?></th>
231 <th class="column-credits" style="width: 80px;"><?php esc_html_e( 'Credits', 'wpforo' ); ?></th>
232 <th class="column-status" style="width: 90px;"><?php esc_html_e( 'Status', 'wpforo' ); ?></th>
233 <th class="column-summary"><?php esc_html_e( 'Summary', 'wpforo' ); ?></th>
234 <th class="column-actions" style="width: 100px;"><?php esc_html_e( 'Actions', 'wpforo' ); ?></th>
235 </tr>
236 </thead>
237 <tbody id="wpforo-ai-logs-tbody">
238 <?php if ( empty( $initial_logs ) ) : ?>
239 <tr class="wpforo-ai-logs-empty-row">
240 <td colspan="8">
241 <div class="wpforo-ai-logs-empty">
242 <span class="dashicons dashicons-info-outline"></span>
243 <?php esc_html_e( 'No logs found. AI action logs will appear here as users interact with AI features.', 'wpforo' ); ?>
244 </div>
245 </td>
246 </tr>
247 <?php else : ?>
248 <?php foreach ( $initial_logs as $log ) : ?>
249 <?php wpforo_ai_render_log_row( $log ); ?>
250 <?php endforeach; ?>
251 <?php endif; ?>
252 </tbody>
253 </table>
254
255 <!-- Pagination -->
256 <div class="wpforo-ai-logs-pagination" id="wpforo-ai-logs-pagination" data-per-page="<?php echo esc_attr( $per_page ); ?>">
257 <?php if ( $total_logs > $per_page ) : ?>
258 <div class="tablenav-pages">
259 <span class="displaying-num">
260 <?php printf( esc_html__( '%s items', 'wpforo' ), number_format( $total_logs ) ); ?>
261 </span>
262 <span class="pagination-links">
263 <button type="button" class="button first-page" data-page="1" disabled>
264 <span class="screen-reader-text"><?php esc_html_e( 'First page', 'wpforo' ); ?></span>
265 <span aria-hidden="true">&laquo;</span>
266 </button>
267 <button type="button" class="button prev-page" data-page="1" disabled>
268 <span class="screen-reader-text"><?php esc_html_e( 'Previous page', 'wpforo' ); ?></span>
269 <span aria-hidden="true">&lsaquo;</span>
270 </button>
271 <span class="paging-input">
272 <span class="current-page">1</span>
273 <?php esc_html_e( 'of', 'wpforo' ); ?>
274 <span class="total-pages"><?php echo ceil( $total_logs / $per_page ); ?></span>
275 </span>
276 <button type="button" class="button next-page" data-page="2" <?php echo $total_logs <= $per_page ? 'disabled' : ''; ?>>
277 <span class="screen-reader-text"><?php esc_html_e( 'Next page', 'wpforo' ); ?></span>
278 <span aria-hidden="true">&rsaquo;</span>
279 </button>
280 <button type="button" class="button last-page" data-page="<?php echo ceil( $total_logs / $per_page ); ?>" <?php echo $total_logs <= $per_page ? 'disabled' : ''; ?>>
281 <span class="screen-reader-text"><?php esc_html_e( 'Last page', 'wpforo' ); ?></span>
282 <span aria-hidden="true">&raquo;</span>
283 </button>
284 </span>
285 </div>
286 <?php endif; ?>
287 </div>
288 </div>
289 </div>
290
291 <!-- Settings Section -->
292 <div class="wpforo-ai-box wpforo-ai-logs-settings-box">
293 <div class="wpforo-ai-box-header">
294 <h3 style="margin: 2px 0;">
295 <span class="dashicons dashicons-admin-generic"></span>
296 <?php esc_html_e( 'Log Settings', 'wpforo' ); ?>
297 </h3>
298 </div>
299 <div class="wpforo-ai-box-body">
300 <div class="wpforo-ai-logs-settings">
301 <div class="wpforo-ai-logs-setting-row">
302 <label for="wpforo-ai-logs-per-page">
303 <?php esc_html_e( 'Logs per page', 'wpforo' ); ?>
304 </label>
305 <select id="wpforo-ai-logs-per-page" class="wpforo-ai-logs-setting">
306 <?php
307 $per_page_options = [ 25, 50, 100, 200 ];
308 $current_per_page = WPF()->ai_logs->get_per_page();
309 foreach ( $per_page_options as $option ) :
310 ?>
311 <option value="<?php echo esc_attr( $option ); ?>" <?php selected( $current_per_page, $option ); ?>>
312 <?php echo esc_html( $option ); ?>
313 </option>
314 <?php endforeach; ?>
315 </select>
316 <span class="wpforo-ai-logs-setting-status">
317 <span class="wpforo-ai-logs-setting-spinner spinner" id="wpforo-ai-logs-per-page-spinner"></span>
318 <span class="wpforo-ai-logs-setting-saved" id="wpforo-ai-logs-per-page-saved">
319 <span class="dashicons dashicons-yes-alt"></span>
320 </span>
321 </span>
322 </div>
323 <div class="wpforo-ai-logs-setting-row">
324 <label for="wpforo-ai-logs-cleanup-days">
325 <?php esc_html_e( 'Auto-delete logs older than', 'wpforo' ); ?>
326 </label>
327 <input type="number" id="wpforo-ai-logs-cleanup-days"
328 class="small-text wpforo-ai-logs-setting"
329 min="0" max="365"
330 value="<?php echo esc_attr( WPF()->ai_logs->get_cleanup_days() ); ?>"
331 placeholder="90">
332 <span class="wpforo-ai-logs-setting-days"><?php esc_html_e( 'days', 'wpforo' ); ?></span>
333 <span class="wpforo-ai-logs-setting-status">
334 <span class="wpforo-ai-logs-setting-spinner spinner" id="wpforo-ai-logs-cleanup-spinner"></span>
335 <span class="wpforo-ai-logs-setting-saved" id="wpforo-ai-logs-cleanup-saved">
336 <span class="dashicons dashicons-yes-alt"></span>
337 </span>
338 </span>
339 <span class="wpforo-ai-logs-setting-hint">
340 <?php esc_html_e( '(0 = keep forever)', 'wpforo' ); ?>
341 </span>
342 </div>
343 </div>
344 </div>
345 </div>
346
347 <!-- Log Detail Modal -->
348 <div class="wpforo-ai-log-detail-overlay" id="wpforo-ai-log-detail-overlay" style="display: none;">
349 <div class="wpforo-ai-log-detail-modal">
350 <div class="wpforo-ai-log-detail-header">
351 <h3>
352 <span class="dashicons dashicons-visibility"></span>
353 <?php esc_html_e( 'Log Details', 'wpforo' ); ?>
354 </h3>
355 <button type="button" class="wpforo-ai-log-detail-close" id="wpforo-ai-log-detail-close">
356 <span class="dashicons dashicons-no-alt"></span>
357 </button>
358 </div>
359 <div class="wpforo-ai-log-detail-body" id="wpforo-ai-log-detail-body">
360 <!-- Content loaded via AJAX -->
361 </div>
362 </div>
363 </div>
364
365 <!-- Empty All Confirmation Modal -->
366 <div class="wpforo-ai-confirm-overlay" id="wpforo-ai-empty-confirm-overlay" style="display: none;">
367 <div class="wpforo-ai-confirm-modal">
368 <div class="wpforo-ai-confirm-header">
369 <span class="dashicons dashicons-warning"></span>
370 <?php esc_html_e( 'Confirm Delete All Logs', 'wpforo' ); ?>
371 </div>
372 <div class="wpforo-ai-confirm-body">
373 <p><?php esc_html_e( 'Are you sure you want to delete ALL AI logs? This action cannot be undone.', 'wpforo' ); ?></p>
374 </div>
375 <div class="wpforo-ai-confirm-footer">
376 <button type="button" class="button button-secondary" id="wpforo-ai-empty-cancel">
377 <?php esc_html_e( 'Cancel', 'wpforo' ); ?>
378 </button>
379 <button type="button" class="button button-primary wpforo-ai-btn-danger" id="wpforo-ai-empty-confirm">
380 <?php esc_html_e( 'Delete All Logs', 'wpforo' ); ?>
381 </button>
382 </div>
383 </div>
384 </div>
385 </div>
386
387 <?php
388 }
389
390 /**
391 * Render a single log row
392 *
393 * @param array $log Log data
394 */
395 function wpforo_ai_render_log_row( $log ) {
396 $log_id = intval( $log['id'] );
397 $action_type = sanitize_text_field( $log['action_type'] );
398 $status = sanitize_text_field( $log['status'] );
399 $user_type = sanitize_text_field( $log['user_type'] );
400 $credits = intval( $log['credits_used'] );
401 $duration = intval( $log['duration_ms'] );
402 $created = $log['created'];
403
404 // Format date in user's timezone (database stores UTC)
405 $date_display = wpforo_ai_format_date_custom( $created, 'M j, Y' );
406 $time_display = wpforo_ai_format_date_custom( $created, 'g:i a' );
407
408 // Get display values
409 $action_label = AILogs::get_action_label( $action_type );
410 $status_label = AILogs::get_status_label( $status );
411 $status_class = AILogs::get_status_class( $status );
412 $user_display = isset( $log['user_display'] ) ? $log['user_display'] : __( 'Unknown', 'wpforo' );
413
414 // Build summary
415 $summary = '';
416 if ( ! empty( $log['request_summary'] ) ) {
417 $summary = esc_html( wp_trim_words( $log['request_summary'], 15 ) );
418 } elseif ( ! empty( $log['response_summary'] ) ) {
419 $summary = esc_html( wp_trim_words( $log['response_summary'], 15 ) );
420 } elseif ( ! empty( $log['error_message'] ) ) {
421 $summary = '<span class="wpforo-ai-log-error-summary">' . esc_html( wp_trim_words( $log['error_message'], 15 ) ) . '</span>';
422 }
423 ?>
424 <tr data-log-id="<?php echo esc_attr( $log_id ); ?>" data-action-type="<?php echo esc_attr( $action_type ); ?>" data-status="<?php echo esc_attr( $status ); ?>">
425 <td class="check-column">
426 <input type="checkbox" name="log_ids[]" value="<?php echo esc_attr( $log_id ); ?>" class="wpforo-ai-log-checkbox">
427 </td>
428 <td class="column-datetime">
429 <span class="wpforo-ai-log-date"><?php echo esc_html( $date_display ); ?></span>
430 <span class="wpforo-ai-log-time"><?php echo esc_html( $time_display ); ?></span>
431 </td>
432 <td class="column-action-type">
433 <span class="wpforo-ai-log-action-badge wpforo-ai-action-<?php echo esc_attr( $action_type ); ?>">
434 <?php echo esc_html( $action_label ); ?>
435 </span>
436 </td>
437 <td class="column-user">
438 <span class="wpforo-ai-log-user wpforo-ai-user-type-<?php echo esc_attr( $user_type ); ?>">
439 <?php echo esc_html( $user_display ); ?>
440 </span>
441 </td>
442 <td class="column-credits">
443 <?php if ( $credits > 0 ) : ?>
444 <span class="wpforo-ai-log-credits"><?php echo number_format( $credits ); ?></span>
445 <?php else : ?>
446 <span class="wpforo-ai-log-credits-zero">-</span>
447 <?php endif; ?>
448 </td>
449 <td class="column-status">
450 <span class="wpforo-ai-log-status <?php echo esc_attr( $status_class ); ?>">
451 <?php echo esc_html( $status_label ); ?>
452 </span>
453 </td>
454 <td class="column-summary">
455 <span class="wpforo-ai-log-summary"><?php echo $summary; ?></span>
456 <?php if ( $duration > 0 ) : ?>
457 <span class="wpforo-ai-log-duration" title="<?php esc_attr_e( 'Duration', 'wpforo' ); ?>">
458 (<?php echo number_format( $duration ); ?>ms)
459 </span>
460 <?php endif; ?>
461 </td>
462 <td class="column-actions">
463 <button type="button" class="button button-small wpforo-ai-log-view" data-log-id="<?php echo esc_attr( $log_id ); ?>" title="<?php esc_attr_e( 'View Details', 'wpforo' ); ?>">
464 <span class="dashicons dashicons-visibility"></span>
465 </button>
466 <button type="button" class="button button-small wpforo-ai-log-delete" data-log-id="<?php echo esc_attr( $log_id ); ?>" title="<?php esc_attr_e( 'Delete', 'wpforo' ); ?>">
467 <span class="dashicons dashicons-trash"></span>
468 </button>
469 </td>
470 </tr>
471 <?php
472 }
473