$tpl) { $tid = $tpl['id'] ?? ''; if ($tid === '') continue; $edit_links_by_id[$tid] = add_query_arg( [ 'action' => 'edit', 'tpl_index' => $idx, 'aigude_tpl_nonce'=> $tpl_nonce, ], $prompts_page_url ); } // Sort templates for display: provider → language → title. $templates_sorted = $templates; usort($templates_sorted, static function ($a, $b) { $pa = strtolower($a['target_provider'] ?? ''); $pb = strtolower($b['target_provider'] ?? ''); if ($pa !== $pb) { return strcmp($pa, $pb); } // Use language label for user-friendly ordering (fallback to code). $la_label = strtolower(AIGUDE_Tools_Plugin::describe_target_language_choice($a['target_provider'] ?? '', $a['target_lang'] ?? '')['label'] ?? ($a['target_lang'] ?? '')); $lb_label = strtolower(AIGUDE_Tools_Plugin::describe_target_language_choice($b['target_provider'] ?? '', $b['target_lang'] ?? '')['label'] ?? ($b['target_lang'] ?? '')); if ($la_label !== $lb_label) { return strcmp($la_label, $lb_label); } $ta = strtolower($a['title'] ?? ''); $tb = strtolower($b['title'] ?? ''); return strcmp($ta, $tb); }); $templates = $templates_sorted; // Group templates by provider for clearer separation in selects. $grouped_templates = []; foreach ($templates as $tpl) { $prov = strtolower($tpl['target_provider'] ?? ''); $label = $prov !== '' ? AIGUDE_Tools_Plugin::get_translation_provider_label($prov) : __('(No provider)', 'aigude-tools'); if (!isset($grouped_templates[$prov])) { $grouped_templates[$prov] = ['label' => $label, 'items' => []]; } $tid = $tpl['id'] ?? ''; if ($tid !== '' && isset($edit_links_by_id[$tid])) { $tpl['edit_url'] = $edit_links_by_id[$tid]; } $grouped_templates[$prov]['items'][] = $tpl; } // Nonce for harmless list filters (search/pagination/per-page); scoped to this admin view. $filters_nonce_action = 'aigude_tools_list_filters'; $filters_nonce_value = wp_create_nonce( $filters_nonce_action ); // Default filter values $paged = 1; $per_page = 20; $search = ''; // Only honor incoming filters if a filter was submitted and nonce validates. $has_filters = isset( $_GET['paged'] ) || isset( $_GET['per_page'] ) || isset( $_GET['s'] ); if ( $has_filters ) { // Dies on failure; prevents bypass via crafted URLs. check_admin_referer( $filters_nonce_action ); $paged = isset( $_GET['paged'] ) ? max( 1, absint( wp_unslash( $_GET['paged'] ) ) ) : 1; $per_page = isset( $_GET['per_page'] ) ? absint( wp_unslash( $_GET['per_page'] ) ) : 20; $allowed_pp = [ 20, 50, 100 ]; if ( ! in_array( $per_page, $allowed_pp, true ) ) { $per_page = 20; } $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; } $q = new WP_Query([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => AIGUDE_Tools_Plugin::get_image_mime_types(), 'orderby' => [ 'date' => 'DESC', 'ID' => 'DESC' ], 'order' => 'DESC', 'posts_per_page' => $per_page, 'paged' => $paged, 's' => $search, 'ai_tools_list' => 1, ]); $page_total = (int) $q->post_count; $base = add_query_arg( '_wpnonce', $filters_nonce_value, remove_query_arg('paged') ); $pagination = paginate_links([ 'base' => $base . '%_%', 'format' => (strpos($base, '?') !== false ? '&' : '?') . 'paged=%#%', 'total' => max(1, (int) $q->max_num_pages), 'current' => $paged, 'prev_text' => '«', 'next_text' => '»', 'type' => 'plain', ]); // Resolve initial prompt text from default template, fallback to built-in prompt $initial_prompt_text = $default_tpl['prompt'] ?? ($builtin_tpl['prompt'] ?? ''); ?>

posts as $img): ?> ID; $thumb_src = wp_get_attachment_image_src($id, 'full'); $thumb = is_array($thumb_src) ? $thumb_src[0] : ''; $saved_alt = (string) get_post_meta($id, '_wp_attachment_image_alt', true); ?>

post_title ) ); ?>