Home / Admin / WPCode Snippet: Download All Snippets Button
Duplicate Snippet

Embed Snippet on Your Site

WPCode Snippet: Download All Snippets Button

* Description: Adds bulk download button to WPCode Snippets Sync admin page
* Location: Admin Only
* Priority: 20
*
* INSTALL ORDER: #5 - Install after sync system is fully set up
*
* REQUIRES:
* - WPCode Snippets Sync CPT Registration (Priority 5)
* - WPCode Snippets Sync Engine (Priority 10)
* - PHP ZipArchive extension (standard on most hosts)
*
* SECURITY (WPCS Compliant):
* - Capability check FIRST (current_user_can) - per Patchstack/WPCS best practice
* - Nonce verification via check_ajax_referer() - standard for AJAX handlers
* - Uses wp_nonce_url() for URL nonce generation (standard _wpnonce param)
* - Uses get_temp_dir() for WordPress-standard temp location
* - Directory traversal prevention via realpath() validation
* - PHP 8.2+ compatible (ZipArchive::OVERWRITE flag)
* - Auto-cleanup of temp files after download (per WP VIP guidelines)
*
* @version 1.1.0
* @since 2025-01-02
*/

ismail daugherty PRO
<10
Code Preview
php
<?php
/**
 * WPCode Snippet: Download All Snippets Button
 * Description: Adds bulk download button to WPCode Snippets Sync admin page
 * Location: Admin Only
 * Priority: 20
 * 
 * INSTALL ORDER: #5 - Install after sync system is fully set up
 * 
 * REQUIRES: 
 *   - WPCode Snippets Sync CPT Registration (Priority 5)
 *   - WPCode Snippets Sync Engine (Priority 10)
 *   - PHP ZipArchive extension (standard on most hosts)
 * 
 * SECURITY (WPCS Compliant):
 *   - Capability check FIRST (current_user_can) - per Patchstack/WPCS best practice
 *   - Nonce verification via check_ajax_referer() - standard for AJAX handlers
 *   - Uses wp_nonce_url() for URL nonce generation (standard _wpnonce param)
 *   - Uses get_temp_dir() for WordPress-standard temp location
 *   - Directory traversal prevention via realpath() validation
 *   - PHP 8.2+ compatible (ZipArchive::OVERWRITE flag)
 *   - Auto-cleanup of temp files after download (per WP VIP guidelines)
 * 
 * v1.8.0 UPDATES:
 *   - Added "AI Summary" button - brief 1-2 sentence summaries via OpenAI
 *   - Added "Full Analysis" button - detailed code breakdown via OpenAI
 *   - Direct API calls to GPT-4o-mini (cost-effective)
 *   - Settings page: Settings → Code Snippets AI for API key
 *   - Confirmation dialog for Full Analysis (warns about time/cost)
 *   - Fallback to parsed descriptions if AI fails
 *   - Rate limiting to prevent API throttling
 *   - Cost estimates: Summary ~$0.05/50 snippets, Full ~$0.50/50 snippets
 * 
 * v1.7.0 UPDATES:
 *   - Added "Download Directory" button for text catalog export
 *   - Parses code comments/docblocks to extract descriptions
 *   - Groups by WPCode, MU-Plugins (root), MU-Plugins (subfolders)
 *   - Includes: name, ID, type, status, priority, location, description
 *   - New function: kic_parse_code_description() for comment parsing
 * 
 * v1.6.0 UPDATES:
 *   - WPCode snippet filenames now include original WPCode ID
 *   - Format: {name}-{active|inactive}-ID{wpcode_id}.php
 *   - Example: My-Snippet-active-ID42.php
 *   - MU-plugins unchanged (no WPCode ID)
 * 
 * v1.5.0 UPDATES:
 *   - Now scans ALL mu-plugins subfolders (not just "shortcode" folders)
 *   - Renamed function: kic_scan_shortcode_folders() -> kic_scan_mu_subfolders()
 *   - UI label changed from "shortcode files" to "subfolder files"
 * 
 * v1.4.0 UPDATES:
 *   - Scans mu-plugins subfolders containing "shortcode" in name
 *   - Includes all .php files from shortcode folders (skips subdirs like OLD/)
 *   - Preserves folder hierarchy: mu-plugins-subfolders/folder-name/file.php
 *   - Shows shortcode folder counts in admin notice
 * 
 * v1.3.0 UPDATES:
 *   - Filenames now include -active or -inactive suffix
 *   - Shows active/inactive counts in button notice
 *   - MU-plugins always marked as active (they run automatically)
 * 
 * v1.2.0 UPDATES:
 *   - Now includes MU-plugins (source_type = 'mu-plugin')
 *   - Creates files from snippet_code field when no file attached
 *   - Organizes into wpcode/ and mu-plugins/ folders in ZIP
 *   - Shows separate counts for WPCode vs MU-plugins
 * 
 * @version 1.8.0
 * @since 2025-01-02
 * @updated 2025-01-25 - Added OpenAI API integration for AI Directory
 */
defined( 'ABSPATH' ) || exit;
/**
 * Scan mu-plugins directory for ALL subfolders
 * Returns array of folder info with their PHP files
 * Skips nested subdirectories (like OLD/) - only gets root-level .php files in each folder
 * 
 * @return array Array of subfolder data
 */
if ( ! function_exists( 'kic_scan_mu_subfolders' ) ) {
function kic_scan_mu_subfolders() {
    $mu_plugins_dir = WPMU_PLUGIN_DIR;
    $subfolders = array();
    
    if ( ! is_dir( $mu_plugins_dir ) ) {
        return $subfolders;
    }
    
    // Scan for subdirectories
    $directories = glob( trailingslashit( $mu_plugins_dir ) . '*', GLOB_ONLYDIR );
    
    if ( empty( $directories ) ) {
        return $subfolders;
    }
    
    foreach ( $directories as $dir_path ) {
        $folder_name = basename( $dir_path );
        
        // Get all PHP files in this folder (not recursive into subdirs like OLD/)
        $php_files = glob( trailingslashit( $dir_path ) . '*.php' );
        
        if ( empty( $php_files ) ) {
            continue;
        }
        
        $files_data = array();
        foreach ( $php_files as $file_path ) {
            $files_data[] = array(
                'path'     => $file_path,
                'filename' => basename( $file_path ),
            );
        }
        
        $subfolders[] = array(
            'folder_name' => $folder_name,
            'folder_path' => $dir_path,
            'files'       => $files_data,
            'file_count'  => count( $files_data ),
        );
    }
    
    return $subfolders;
}
}
/**
 * Add "Download All Snippets" button to admin list page
 */
add_action( 'admin_notices', function() {
    $screen = get_current_screen();
    
    if ( ! $screen || 'edit-wpcode_snippets_sync' !== $screen->id ) {
        return;
    }
    
    // Get count of all snippets
    $count = wp_count_posts( 'wpcode_snippets_sync' );
    $total = isset( $count->publish ) ? $count->publish : 0;
    
    if ( $total < 1 ) {
        return;
    }
    
    // Count by source type
    $wpcode_count = count( get_posts( array(
        'post_type'      => 'wpcode_snippets_sync',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'fields'         => 'ids',
        'no_found_rows'  => true,
        'meta_query'     => array(
            'relation' => 'OR',
            array(
                'key'   => 'source_type',
                'value' => 'wpcode',
            ),
            array(
                'key'     => 'source_type',
                'compare' => 'NOT EXISTS',
            ),
        ),
    ) ) );
    
    $mu_plugin_count = count( get_posts( array(
        'post_type'      => 'wpcode_snippets_sync',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'fields'         => 'ids',
        'no_found_rows'  => true,
        'meta_query'     => array(
            array(
                'key'   => 'source_type',
                'value' => 'mu-plugin',
            ),
        ),
    ) ) );
    
    // Count active vs inactive (WPCode only - MU-plugins are always active)
    $active_count = count( get_posts( array(
        'post_type'      => 'wpcode_snippets_sync',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'fields'         => 'ids',
        'no_found_rows'  => true,
        'meta_query'     => array(
            'relation' => 'AND',
            array(
                'key'   => 'snippet_active',
                'value' => '1',
            ),
            array(
                'relation' => 'OR',
                array(
                    'key'   => 'source_type',
                    'value' => 'wpcode',
                ),
                array(
                    'key'     => 'source_type',
                    'compare' => 'NOT EXISTS',
                ),
            ),
        ),
    ) ) );
    
    $inactive_count = $wpcode_count - $active_count;
    
    // Count subfolders and files
    $mu_subfolders = kic_scan_mu_subfolders();
    $subfolder_count = count( $mu_subfolders );
    $subfolder_file_count = 0;
    foreach ( $mu_subfolders as $folder ) {
        $subfolder_file_count += $folder['file_count'];
    }
    
    // Use wp_nonce_url for proper URL nonce generation
    $download_url = wp_nonce_url( 
        admin_url( 'admin-ajax.php?action=download_all_snippets' ), 
        'download_all_snippets_action', 
        '_wpnonce' 
    );
    
    $directory_url = wp_nonce_url( 
        admin_url( 'admin-ajax.php?action=download_snippets_directory' ), 
        'download_snippets_directory_action', 
        '_wpnonce' 
    );
    
    $ai_directory_url = wp_nonce_url( 
        admin_url( 'admin-ajax.php?action=download_ai_directory' ), 
        'download_ai_directory_action', 
        '_wpnonce' 
    );
    
    $full_analysis_url = wp_nonce_url( 
        admin_url( 'admin-ajax.php?action=download_full_ai_analysis' ), 
        'download_full_ai_analysis_action', 
        '_wpnonce' 
    );
    
    // Check if OpenAI API key is configured
    $has_openai_key = ! empty( get_option( 'kic_openai_api_key' ) );
    
    ?>
    <div class="notice notice-info" style="display: flex; align-items: center; justify-content: space-between; padding: 12px 15px; flex-wrap: wrap; gap: 10px;">
        <p style="margin: 0;">
            <strong>📦 Bulk Export:</strong> 
            Download all synced snippets as a ZIP archive.
            <span style="color: #666; font-size: 12px;">
                (<?php echo esc_html( $wpcode_count ); ?> WPCode: <span style="color: #00a32a;"><?php echo esc_html( $active_count ); ?> active</span>, <span style="color: #d63638;"><?php echo esc_html( $inactive_count ); ?> inactive</span> | <?php echo esc_html( $mu_plugin_count ); ?> MU-plugins<?php if ( $subfolder_file_count > 0 ) : ?> | <span style="color: #2271b1;"><?php echo esc_html( $subfolder_file_count ); ?> subfolder files</span><?php endif; ?>)
            </span>
        </p>
        <div style="display: flex; gap: 8px; flex-wrap: wrap;">
            <a href="<?php echo esc_url( $directory_url ); ?>" 
               class="button button-secondary" 
               id="download-directory-btn"
               title="Download text catalog with descriptions parsed from code comments (FREE - no API)">
                <span class="dashicons dashicons-list-view" style="margin-top: 3px; margin-right: 3px;"></span>
                Directory
            </a>
            <?php if ( $has_openai_key ) : ?>
            <a href="<?php echo esc_url( $ai_directory_url ); ?>" 
               class="button button-secondary" 
               id="download-ai-directory-btn"
               style="background: linear-gradient(135deg, #10a37f 0%, #1a7f64 100%); border-color: #10a37f; color: #fff;"
               title="AI-generated 1-2 sentence summaries (~$0.05 for 50 snippets)">
                <span class="dashicons dashicons-superhero" style="margin-top: 3px; margin-right: 3px;"></span>
                AI Summary
            </a>
            <a href="<?php echo esc_url( $full_analysis_url ); ?>" 
               class="button button-secondary" 
               id="download-full-analysis-btn"
               style="background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%); border-color: #7c3aed; color: #fff;"
               title="Full AI analysis explaining every part of each snippet (~$0.50 for 50 snippets) - TAKES LONGER"
               onclick="return confirm('Full AI Analysis sends all code to OpenAI and takes several minutes.\n\nEstimated cost: ~$0.01 per snippet\n\nContinue?');">
                <span class="dashicons dashicons-welcome-learn-more" style="margin-top: 3px; margin-right: 3px;"></span>
                Full Analysis
            </a>
            <?php else : ?>
            <span class="button button-disabled" 
                  style="opacity: 0.6; cursor: not-allowed;"
                  title="Configure OpenAI API key in SettingsCode Snippets AI to enable">
                <span class="dashicons dashicons-superhero" style="margin-top: 3px; margin-right: 3px;"></span>
                AI Summary
            </span>
            <span class="button button-disabled" 
                  style="opacity: 0.6; cursor: not-allowed;"
                  title="Configure OpenAI API key in SettingsCode Snippets AI to enable">
                <span class="dashicons dashicons-welcome-learn-more" style="margin-top: 3px; margin-right: 3px;"></span>
                Full Analysis
            </span>
            <?php endif; ?>
            <a href="<?php echo esc_url( $download_url ); ?>" 
               class="button button-primary" 
               id="download-all-snippets-btn">
                <span class="dashicons dashicons-download" style="margin-top: 3px; margin-right: 3px;"></span>
                Download All
            </a>
        </div>
    </div>
    <?php
}, 10 ); // Priority 10 to show after MU-plugins control panel
/**
 * AJAX handler for downloading all snippets as ZIP
 * 
 * Security: Uses check_ajax_referer() which automatically dies on failure
 * and properly handles nonce verification per WordPress Coding Standards.
 * 
 * v1.4.0: Adds shortcode subfolders from mu-plugins
 * v1.3.0: Adds -active/-inactive suffix to filenames
 * v1.2.0: Now includes MU-plugins by generating files from snippet_code field
 */
add_action( 'wp_ajax_download_all_snippets', function() {
    
    // Capability check FIRST (before nonce to prevent timing attacks)
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( array( 'message' => 'Unauthorized access.' ), 403 );
    }
    
    // Verify nonce - check_ajax_referer auto-dies on failure
    check_ajax_referer( 'download_all_snippets_action', '_wpnonce' );
    
    // Check ZipArchive exists
    if ( ! class_exists( 'ZipArchive' ) ) {
        wp_send_json_error( array( 'message' => 'ZipArchive PHP extension is required but not available.' ), 500 );
    }
    
    // Get all sync records
    $snippets = get_posts( array(
        'post_type'      => 'wpcode_snippets_sync',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'no_found_rows'  => true,
    ) );
    
    // Get ALL subfolders
    $mu_subfolders = kic_scan_mu_subfolders();
    
    if ( empty( $snippets ) && empty( $mu_subfolders ) ) {
        wp_send_json_error( array( 'message' => 'No snippets or subfolders found to download.' ), 404 );
    }
    
    // Use WordPress standard temp directory
    $temp_dir = get_temp_dir();
    $zip_filename = 'wpcode-snippets-export-' . gmdate( 'Y-m-d-His' ) . '.zip';
    $zip_path = trailingslashit( $temp_dir ) . $zip_filename;
    
    // Initialize ZIP with OVERWRITE for PHP 8.2+ compatibility
    $zip = new ZipArchive();
    $zip_result = $zip->open( $zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE );
    
    if ( true !== $zip_result ) {
        wp_send_json_error( array( 
            'message' => 'Could not create ZIP file.', 
            'error_code' => $zip_result 
        ), 500 );
    }
    
    // Get upload directory for file path resolution
    $upload_dir = wp_upload_dir();
    
    // Track what we're adding
    $manifest = array(
        'exported_at'         => gmdate( 'Y-m-d H:i:s' ) . ' UTC',
        'exported_by'         => wp_get_current_user()->user_login,
        'site_url'            => home_url(),
        'wordpress_ver'       => get_bloginfo( 'version' ),
        'total_files'         => 0,
        'wpcode_count'        => 0,
        'wpcode_active'       => 0,
        'wpcode_inactive'     => 0,
        'mu_plugin_count'     => 0,
        'subfolder_count'     => 0,
        'subfolder_file_count'=> 0,
        'snippets'            => array(),
        'subfolders'          => array(),
    );
    
    $files_added = 0;
    $wpcode_added = 0;
    $wpcode_active = 0;
    $wpcode_inactive = 0;
    $mu_plugin_added = 0;
    $subfolder_files_added = 0;
    $errors = array();
    
    // ========================================
    // PART 1: Process synced snippets (WPCode + MU-plugins)
    // ========================================
    foreach ( $snippets as $snippet ) {
        $post_id = $snippet->ID;
        
        // Get ACF fields
        $kic_id = get_field( 'kic_snippet_id', $post_id );
        $snippet_type = get_field( 'snippet_type', $post_id );
        $snippet_title = get_field( 'snippet_title', $post_id );
        $snippet_file = get_field( 'snippet_code_file', $post_id );
        $snippet_code = get_field( 'snippet_code', $post_id );
        $original_wpcode_id = get_field( 'original_wpcode_id', $post_id );
        $snippet_active = get_field( 'snippet_active', $post_id );
        $snippet_priority = get_field( 'snippet_priority', $post_id );
        $snippet_location = get_field( 'snippet_location', $post_id );
        $source_type = get_field( 'source_type', $post_id );
        $file_path_meta = get_field( 'file_path', $post_id );
        
        // Determine if this is MU-plugin or WPCode
        $is_mu_plugin = ( 'mu-plugin' === $source_type );
        
        // Determine active status suffix
        if ( $is_mu_plugin ) {
            $is_active = true;
            $status_suffix = '-active';
        } else {
            $is_active = ! empty( $snippet_active );
            $status_suffix = $is_active ? '-active' : '-inactive';
        }
        
        // Determine ZIP entry path based on source type
        if ( $is_mu_plugin ) {
            if ( ! empty( $file_path_meta ) ) {
                $original_filename = basename( $file_path_meta );
                $original_filename = preg_replace( '/\.php$/i', $status_suffix . '.php', $original_filename );
            } else {
                $original_filename = sanitize_file_name( $snippet_title );
                if ( empty( $original_filename ) ) {
                    $original_filename = 'mu-plugin-' . $post_id;
                }
                $original_filename .= $status_suffix . '.php';
            }
            $zip_entry_name = 'mu-plugins/' . $original_filename;
        } else {
            // WPCode snippets - include original WPCode ID in filename
            $safe_name = sanitize_file_name( $snippet_title );
            if ( empty( $safe_name ) ) {
                $safe_name = 'snippet-' . $post_id;
            }
            $safe_name .= $status_suffix;
            
            // Add WPCode ID if available
            if ( ! empty( $original_wpcode_id ) ) {
                $safe_name .= '-ID' . intval( $original_wpcode_id );
            }
            
            $extension = '.txt';
            if ( ! empty( $snippet_type ) ) {
                switch ( strtolower( $snippet_type ) ) {
                    case 'php':
                        $extension = '.php';
                        break;
                    case 'css':
                        $extension = '.css';
                        break;
                    case 'js':
                    case 'javascript':
                        $extension = '.js';
                        break;
                    case 'html':
                        $extension = '.html';
                        break;
                }
            }
            
            $type_folder = ! empty( $snippet_type ) ? strtolower( $snippet_type ) : 'misc';
            $zip_entry_name = 'wpcode/' . $type_folder . '/' . $safe_name . $extension;
        }
        
        $file_content = null;
        $content_source = '';
        
        // Try to get content from file first
        if ( ! empty( $snippet_file ) ) {
            $file_url = is_array( $snippet_file ) ? $snippet_file['url'] : $snippet_file;
            
            if ( ! empty( $file_url ) ) {
                $local_file_path = str_replace( 
                    $upload_dir['baseurl'], 
                    $upload_dir['basedir'], 
                    esc_url_raw( $file_url )
                );
                $local_file_path = preg_replace( '/\?.*$/', '', $local_file_path );
                $local_file_path = realpath( $local_file_path );
                
                if ( $local_file_path && strpos( $local_file_path, realpath( $upload_dir['basedir'] ) ) === 0 ) {
                    if ( file_exists( $local_file_path ) ) {
                        $file_content = file_get_contents( $local_file_path );
                        $content_source = 'file';
                    }
                }
            }
        }
        
        // If no file content, try snippet_code field
        if ( null === $file_content && ! empty( $snippet_code ) ) {
            $file_content = $snippet_code;
            $content_source = 'code_field';
        }
        
        if ( empty( $file_content ) ) {
            $errors[] = "Skipped '{$snippet_title}' - no file or code content";
            continue;
        }
        
        if ( $zip->addFromString( $zip_entry_name, $file_content ) ) {
            $files_added++;
            
            if ( $is_mu_plugin ) {
                $mu_plugin_added++;
            } else {
                $wpcode_added++;
                if ( $is_active ) {
                    $wpcode_active++;
                } else {
                    $wpcode_inactive++;
                }
            }
            
            $manifest['snippets'][] = array(
                'file'               => $zip_entry_name,
                'kic_id'             => $kic_id,
                'title'              => $snippet_title,
                'type'               => $snippet_type,
                'source_type'        => $is_mu_plugin ? 'mu-plugin' : 'wpcode',
                'original_wpcode_id' => $original_wpcode_id,
                'original_file_path' => $file_path_meta,
                'active'             => $is_active,
                'priority'           => $snippet_priority,
                'location'           => $snippet_location,
                'content_source'     => $content_source,
            );
        } else {
            $errors[] = "Failed to add '{$snippet_title}' to ZIP";
        }
    }
    
    // ========================================
    // PART 2: Process ALL mu-plugins subfolders
    // ========================================
    $mu_plugins_dir = WPMU_PLUGIN_DIR;
    
    foreach ( $mu_subfolders as $folder ) {
        $folder_name = $folder['folder_name'];
        $folder_manifest = array(
            'folder_name' => $folder_name,
            'files'       => array(),
        );
        
        foreach ( $folder['files'] as $file_info ) {
            $file_path = $file_info['path'];
            $filename = $file_info['filename'];
            
            // Security: Verify file is within mu-plugins directory
            $real_path = realpath( $file_path );
            $real_mu_dir = realpath( $mu_plugins_dir );
            
            if ( ! $real_path || strpos( $real_path, $real_mu_dir ) !== 0 ) {
                $errors[] = "Skipped '{$filename}' - outside mu-plugins directory";
                continue;
            }
            
            if ( ! file_exists( $file_path ) || ! is_readable( $file_path ) ) {
                $errors[] = "Skipped '{$filename}' - file not readable";
                continue;
            }
            
            $file_content = file_get_contents( $file_path );
            
            if ( false === $file_content ) {
                $errors[] = "Skipped '{$filename}' - could not read file";
                continue;
            }
            
            // ZIP path: mu-plugins-subfolders/folder-name/file.php
            $zip_entry_name = 'mu-plugins-subfolders/' . $folder_name . '/' . $filename;
            
            if ( $zip->addFromString( $zip_entry_name, $file_content ) ) {
                $subfolder_files_added++;
                $files_added++;
                
                $folder_manifest['files'][] = array(
                    'file'          => $zip_entry_name,
                    'original_path' => $file_path,
                    'filename'      => $filename,
                );
            } else {
                $errors[] = "Failed to add '{$filename}' from {$folder_name} to ZIP";
            }
        }
        
        if ( ! empty( $folder_manifest['files'] ) ) {
            $manifest['subfolders'][] = $folder_manifest;
        }
    }
    
    // Update manifest counts
    $manifest['total_files'] = $files_added;
    $manifest['wpcode_count'] = $wpcode_added;
    $manifest['wpcode_active'] = $wpcode_active;
    $manifest['wpcode_inactive'] = $wpcode_inactive;
    $manifest['mu_plugin_count'] = $mu_plugin_added;
    $manifest['subfolder_count'] = count( $mu_subfolders );
    $manifest['subfolder_file_count'] = $subfolder_files_added;
    
    if ( ! empty( $errors ) ) {
        $manifest['warnings'] = $errors;
    }
    
    // Add manifest to ZIP
    $zip->addFromString( 'manifest.json', wp_json_encode( $manifest, JSON_PRETTY_PRINT ) );
    
    // Add README
    $readme = "# WPCode Snippets Export\n\n";
    $readme .= "Exported: " . $manifest['exported_at'] . "\n";
    $readme .= "From: " . $manifest['site_url'] . "\n";
    $readme .= "Total Files: " . $files_added . "\n\n";
    $readme .= "## Status Summary\n\n";
    $readme .= "| Type | Active | Inactive | Total |\n";
    $readme .= "|------|--------|----------|-------|\n";
    $readme .= "| WPCode Snippets | " . $wpcode_active . " | " . $wpcode_inactive . " | " . $wpcode_added . " |\n";
    $readme .= "| MU-Plugins (root) | " . $mu_plugin_added . " | 0 | " . $mu_plugin_added . " |\n";
    $readme .= "| MU-Plugins (subfolders) | " . $subfolder_files_added . " | 0 | " . $subfolder_files_added . " |\n";
    $readme .= "| **Total** | **" . ( $wpcode_active + $mu_plugin_added + $subfolder_files_added ) . "** | **" . $wpcode_inactive . "** | **" . $files_added . "** |\n\n";
    $readme .= "## Filename Convention\n\n";
    $readme .= "**WPCode snippets** include status AND WPCode ID:\n";
    $readme .= "- `snippet-name-active-ID42.php` - Active snippet (WPCode ID 42)\n";
    $readme .= "- `snippet-name-inactive-ID99.php` - Disabled snippet (WPCode ID 99)\n\n";
    $readme .= "**MU-Plugins (root)** include status only:\n";
    $readme .= "- `plugin-name-active.php` - Always active\n\n";
    $readme .= "**MU-Plugins (subfolders)** keep original names (always active).\n\n";
    $readme .= "## Folder Structure\n\n";
    $readme .= "```\n";
    $readme .= "export.zip\n";
    $readme .= "├── wpcode/                    # WPCode snippets by type\n";
    $readme .= "│   ├── php/\n";
    $readme .= "│   ├── css/\n";
    $readme .= "│   ├── js/\n";
    $readme .= "│   └── html/\n";
    $readme .= "├── mu-plugins/                # Root-level MU-Plugins\n";
    $readme .= "├── mu-plugins-subfolders/     # All MU-Plugin subfolders\n";
    foreach ( $mu_subfolders as $folder ) {
        $readme .= "│   └── " . $folder['folder_name'] . "/\n";
    }
    $readme .= "├── manifest.json\n";
    $readme .= "└── README.md\n";
    $readme .= "```\n\n";
    $readme .= "## Deployment\n\n";
    $readme .= "Copy `mu-plugins-subfolders/*` folders to `/wp-content/mu-plugins/` on target site.\n";
    
    $zip->addFromString( 'README.md', $readme );
    
    // Close ZIP
    $zip->close();
    
    if ( ! file_exists( $zip_path ) ) {
        wp_die( 'ZIP file was not created.', 'Error', array( 'response' => 500 ) );
    }
    
    // Send headers for download
    header( 'Content-Type: application/zip' );
    header( 'Content-Disposition: attachment; filename="' . $zip_filename . '"' );
    header( 'Content-Length: ' . filesize( $zip_path ) );
    header( 'Pragma: no-cache' );
    header( 'Expires: 0' );
    
    readfile( $zip_path );
    
    // Cleanup temp file (wp_delete_file is preferred over unlink per WP VIP)
    if ( file_exists( $zip_path ) ) {
        wp_delete_file( $zip_path );
    }
    
    exit;
} );
/**
 * AJAX handler for downloading snippets directory (text catalog)
 * 
 * Parses code comments/docblocks to extract descriptions
 * Creates a formatted text file with all snippet metadata
 * 
 * @since 1.7.0
 */
add_action( 'wp_ajax_download_snippets_directory', function() {
    
    // Capability check FIRST (before nonce to prevent timing attacks)
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( array( 'message' => 'Unauthorized access.' ), 403 );
    }
    
    // Verify nonce - check_ajax_referer auto-dies on failure
    check_ajax_referer( 'download_snippets_directory_action', '_wpnonce' );
    
    // Get all sync records
    $snippets = get_posts( array(
        'post_type'      => 'wpcode_snippets_sync',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'no_found_rows'  => true,
        'orderby'        => 'title',
        'order'          => 'ASC',
    ) );
    
    // Get ALL subfolders
    $mu_subfolders = kic_scan_mu_subfolders();
    
    if ( empty( $snippets ) && empty( $mu_subfolders ) ) {
        wp_send_json_error( array( 'message' => 'No snippets found.' ), 404 );
    }
    
    // Build directory content
    $directory = "================================================================================\n";
    $directory .= "                    CODE SNIPPETS DIRECTORY\n";
    $directory .= "================================================================================\n";
    $directory .= "Generated: " . gmdate( 'Y-m-d H:i:s' ) . " UTC\n";
    $directory .= "Site: " . home_url() . "\n";
    $directory .= "================================================================================\n\n";
    
    // Counters
    $wpcode_entries = array();
    $mu_plugin_entries = array();
    $mu_subfolder_entries = array();
    
    // Process synced snippets
    foreach ( $snippets as $snippet ) {
        $post_id = $snippet->ID;
        
        // Get ACF fields
        $kic_id = get_field( 'kic_snippet_id', $post_id );
        $snippet_type = get_field( 'snippet_type', $post_id );
        $snippet_title = get_field( 'snippet_title', $post_id );
        $snippet_code = get_field( 'snippet_code', $post_id );
        $original_wpcode_id = get_field( 'original_wpcode_id', $post_id );
        $snippet_active = get_field( 'snippet_active', $post_id );
        $snippet_priority = get_field( 'snippet_priority', $post_id );
        $snippet_location = get_field( 'snippet_location', $post_id );
        $source_type = get_field( 'source_type', $post_id );
        $file_path_meta = get_field( 'file_path', $post_id );
        $snippet_description = get_field( 'snippet_description', $post_id );
        
        // Parse description from code if not in ACF field
        $parsed_description = kic_parse_code_description( $snippet_code );
        $final_description = ! empty( $parsed_description ) ? $parsed_description : $snippet_description;
        
        // Build entry
        $entry = array(
            'title'       => $snippet_title ? $snippet_title : $snippet->post_title,
            'id'          => $original_wpcode_id ? $original_wpcode_id : $post_id,
            'kic_id'      => $kic_id,
            'type'        => $snippet_type ? strtoupper( $snippet_type ) : 'UNKNOWN',
            'status'      => $snippet_active ? 'ACTIVE' : 'INACTIVE',
            'priority'    => $snippet_priority ? $snippet_priority : 10,
            'location'    => $snippet_location ? $snippet_location : 'N/A',
            'description' => $final_description,
            'file_path'   => $file_path_meta,
        );
        
        // Sort into categories
        if ( 'mu-plugin' === $source_type ) {
            $mu_plugin_entries[] = $entry;
        } elseif ( 'mu-plugin-subfolder' === $source_type ) {
            $mu_subfolder_entries[] = $entry;
        } else {
            $wpcode_entries[] = $entry;
        }
    }
    
    // Process unsynced subfolder files (read directly from filesystem)
    $mu_plugins_dir = WPMU_PLUGIN_DIR;
    foreach ( $mu_subfolders as $folder ) {
        foreach ( $folder['files'] as $file_info ) {
            $file_path = $file_info['path'];
            $filename = $file_info['filename'];
            
            // Check if already synced
            $already_synced = false;
            foreach ( $mu_subfolder_entries as $entry ) {
                if ( isset( $entry['file_path'] ) && basename( $entry['file_path'] ) === $filename ) {
                    $already_synced = true;
                    break;
                }
            }
            
            if ( ! $already_synced ) {
                // Security check
                $real_path = realpath( $file_path );
                $real_mu_dir = realpath( $mu_plugins_dir );
                
                if ( $real_path && strpos( $real_path, $real_mu_dir ) === 0 && file_exists( $file_path ) ) {
                    $code_content = file_get_contents( $file_path );
                    $parsed_description = kic_parse_code_description( $code_content );
                    
                    $mu_subfolder_entries[] = array(
                        'title'       => $filename,
                        'id'          => 'N/A (not synced)',
                        'kic_id'      => 'mu_subfolder_' . $folder['folder_name'] . '_' . pathinfo( $filename, PATHINFO_FILENAME ),
                        'type'        => 'PHP',
                        'status'      => 'ACTIVE',
                        'priority'    => 'N/A',
                        'location'    => 'mu-plugins/' . $folder['folder_name'] . '/',
                        'description' => $parsed_description,
                        'file_path'   => $file_path,
                    );
                }
            }
        }
    }
    
    // ========================================
    // SECTION 1: WPCODE SNIPPETS
    // ========================================
    $directory .= "################################################################################\n";
    $directory .= "# SECTION 1: WPCODE SNIPPETS (" . count( $wpcode_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $wpcode_entries ) ) {
        $directory .= "(No WPCode snippets found)\n\n";
    } else {
        foreach ( $wpcode_entries as $index => $entry ) {
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "  WPCode ID:    %s\n", $entry['id'] );
            $directory .= sprintf( "  Type:         %s\n", $entry['type'] );
            $directory .= sprintf( "  Status:       %s\n", $entry['status'] );
            $directory .= sprintf( "  Priority:     %s\n", $entry['priority'] );
            $directory .= sprintf( "  Location:     %s\n", $entry['location'] );
            $directory .= "\n";
            $directory .= "  DESCRIPTION:\n";
            if ( ! empty( $entry['description'] ) ) {
                // Indent description lines
                $desc_lines = explode( "\n", wordwrap( $entry['description'], 74 ) );
                foreach ( $desc_lines as $line ) {
                    $directory .= "    " . $line . "\n";
                }
            } else {
                $directory .= "    (No description found in code)\n";
            }
            $directory .= "\n";
        }
    }
    
    // ========================================
    // SECTION 2: MU-PLUGINS (ROOT)
    // ========================================
    $directory .= "\n################################################################################\n";
    $directory .= "# SECTION 2: MU-PLUGINS - ROOT (" . count( $mu_plugin_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $mu_plugin_entries ) ) {
        $directory .= "(No root MU-plugins found)\n\n";
    } else {
        foreach ( $mu_plugin_entries as $index => $entry ) {
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "  Type:         %s\n", $entry['type'] );
            $directory .= sprintf( "  Status:       %s (always active)\n", $entry['status'] );
            $directory .= sprintf( "  File:         %s\n", $entry['file_path'] ? basename( $entry['file_path'] ) : 'N/A' );
            $directory .= "\n";
            $directory .= "  DESCRIPTION:\n";
            if ( ! empty( $entry['description'] ) ) {
                $desc_lines = explode( "\n", wordwrap( $entry['description'], 74 ) );
                foreach ( $desc_lines as $line ) {
                    $directory .= "    " . $line . "\n";
                }
            } else {
                $directory .= "    (No description found in code)\n";
            }
            $directory .= "\n";
        }
    }
    
    // ========================================
    // SECTION 3: MU-PLUGINS (SUBFOLDERS)
    // ========================================
    $directory .= "\n################################################################################\n";
    $directory .= "# SECTION 3: MU-PLUGINS - SUBFOLDERS (" . count( $mu_subfolder_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $mu_subfolder_entries ) ) {
        $directory .= "(No subfolder MU-plugins found)\n\n";
    } else {
        // Group by folder
        $by_folder = array();
        foreach ( $mu_subfolder_entries as $entry ) {
            $folder = 'unknown';
            if ( ! empty( $entry['location'] ) ) {
                preg_match( '/mu-plugins\/([^\/]+)\//', $entry['location'], $matches );
                if ( ! empty( $matches[1] ) ) {
                    $folder = $matches[1];
                }
            } elseif ( ! empty( $entry['file_path'] ) ) {
                $parts = explode( '/', str_replace( '\\', '/', $entry['file_path'] ) );
                $mu_index = array_search( 'mu-plugins', $parts );
                if ( $mu_index !== false && isset( $parts[ $mu_index + 1 ] ) ) {
                    $folder = $parts[ $mu_index + 1 ];
                }
            }
            $by_folder[ $folder ][] = $entry;
        }
        
        foreach ( $by_folder as $folder_name => $entries ) {
            $directory .= "=== FOLDER: " . $folder_name . "/ (" . count( $entries ) . " files) ===\n\n";
            
            foreach ( $entries as $index => $entry ) {
                $directory .= "--------------------------------------------------------------------------------\n";
                $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
                $directory .= "--------------------------------------------------------------------------------\n";
                $directory .= sprintf( "  Type:         %s\n", $entry['type'] );
                $directory .= sprintf( "  Status:       ACTIVE (always active)\n" );
                $directory .= sprintf( "  Folder:       %s/\n", $folder_name );
                $directory .= "\n";
                $directory .= "  DESCRIPTION:\n";
                if ( ! empty( $entry['description'] ) ) {
                    $desc_lines = explode( "\n", wordwrap( $entry['description'], 74 ) );
                    foreach ( $desc_lines as $line ) {
                        $directory .= "    " . $line . "\n";
                    }
                } else {
                    $directory .= "    (No description found in code)\n";
                }
                $directory .= "\n";
            }
        }
    }
    
    // ========================================
    // SUMMARY
    // ========================================
    $directory .= "\n################################################################################\n";
    $directory .= "# SUMMARY\n";
    $directory .= "################################################################################\n\n";
    $directory .= sprintf( "  WPCode Snippets:        %d\n", count( $wpcode_entries ) );
    $directory .= sprintf( "  MU-Plugins (root):      %d\n", count( $mu_plugin_entries ) );
    $directory .= sprintf( "  MU-Plugins (subfolders):%d\n", count( $mu_subfolder_entries ) );
    $directory .= sprintf( "  --------------------------------\n" );
    $directory .= sprintf( "  TOTAL:                  %d\n", count( $wpcode_entries ) + count( $mu_plugin_entries ) + count( $mu_subfolder_entries ) );
    $directory .= "\n================================================================================\n";
    $directory .= "                         END OF DIRECTORY\n";
    $directory .= "================================================================================\n";
    
    // Generate filename
    $filename = 'code-snippets-directory-' . gmdate( 'Y-m-d-His' ) . '.txt';
    
    // Send headers for download
    header( 'Content-Type: text/plain; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
    header( 'Content-Length: ' . strlen( $directory ) );
    header( 'Pragma: no-cache' );
    header( 'Expires: 0' );
    
    echo $directory;
    exit;
} );
/**
 * Parse description from code comments/docblocks
 * 
 * Extracts description from:
 * 1. WPCode header: "Description: ..."
 * 2. Plugin header: "Description: ..."
 * 3. PHPDoc @description tag
 * 4. First docblock after opening comment
 * 5. First multi-line comment block
 * 
 * @param string $code The code to parse
 * @return string Extracted description or empty string
 */
function kic_parse_code_description( $code ) {
    if ( empty( $code ) ) {
        return '';
    }
    
    $description = '';
    
    // Pattern 1: WPCode/Plugin header "Description: ..."
    if ( preg_match( '/^\s*\*?\s*Description:\s*(.+?)$/mi', $code, $matches ) ) {
        $description = trim( $matches[1] );
    }
    
    // Pattern 2: PHPDoc @description tag
    if ( empty( $description ) && preg_match( '/@description\s+(.+?)(?:\n\s*\*\s*@|\n\s*\*\/)/si', $code, $matches ) ) {
        $description = trim( preg_replace( '/\s*\*\s*/', ' ', $matches[1] ) );
    }
    
    // Pattern 3: First docblock content (after /** and before first @tag)
    if ( empty( $description ) && preg_match( '/\/\*\*\s*\n\s*\*\s*([^@\n][^\n]*(?:\n\s*\*\s*[^@\n][^\n]*)*)/s', $code, $matches ) ) {
        $raw = $matches[1];
        // Clean up asterisks and whitespace
        $lines = preg_split( '/\n\s*\*\s*/', $raw );
        $cleaned = array();
        foreach ( $lines as $line ) {
            $line = trim( $line );
            if ( ! empty( $line ) && strpos( $line, '@' ) !== 0 ) {
                $cleaned[] = $line;
            }
        }
        if ( ! empty( $cleaned ) ) {
            $description = implode( ' ', $cleaned );
        }
    }
    
    // Pattern 4: Multi-line comment block /* ... */
    if ( empty( $description ) && preg_match( '/\/\*[^*](.+?)\*\//s', $code, $matches ) ) {
        $raw = trim( $matches[1] );
        // Clean up and limit
        $raw = preg_replace( '/\s+/', ' ', $raw );
        if ( strlen( $raw ) > 20 && strlen( $raw ) < 500 ) {
            $description = $raw;
        }
    }
    
    // Pattern 5: Single line comment at top // Description or // What this does
    if ( empty( $description ) && preg_match( '/^(?:<\?php\s*)?\s*\/\/\s*(.{20,200})$/m', $code, $matches ) ) {
        $description = trim( $matches[1] );
    }
    
    // Clean up description
    $description = trim( $description );
    $description = preg_replace( '/\s+/', ' ', $description );
    
    // Truncate if too long
    if ( strlen( $description ) > 500 ) {
        $description = substr( $description, 0, 497 ) . '...';
    }
    
    return $description;
}
/**
 * Call OpenAI API to analyze code
 * 
 * @param string $code The code to analyze
 * @param string $title The snippet title for context
 * @param string $mode 'summary' for brief, 'full' for detailed analysis
 * @return string AI-generated analysis or empty string on failure
 */
function kic_openai_analyze_code( $code, $title = '', $mode = 'summary' ) {
    $api_key = get_option( 'kic_openai_api_key' );
    
    if ( empty( $api_key ) || empty( $code ) ) {
        return '';
    }
    
    // Adjust truncation based on mode
    $code_sample = $code;
    if ( 'full' === $mode ) {
        // For full analysis, allow more code (up to 6000 chars)
        if ( strlen( $code ) > 6000 ) {
            $code_sample = substr( $code, 0, 4000 ) . "\n\n... [code truncated for length] ...\n\n" . substr( $code, -1500 );
        }
    } else {
        // For summary, keep it shorter for cost efficiency
        if ( strlen( $code ) > 3000 ) {
            $code_sample = substr( $code, 0, 1500 ) . "\n\n... [code truncated] ...\n\n" . substr( $code, -500 );
        }
    }
    
    // Build prompt based on mode
    if ( 'full' === $mode ) {
        $system_message = 'You are a senior WordPress developer assistant. Provide detailed code analysis that explains what each major section does. Use clear headings and bullet points. Be thorough but organized.';
        
        $prompt = "Analyze this WordPress/PHP code snippet in detail. Provide:\n\n";
        $prompt .= "1. **PURPOSE**: What is the overall goal of this code?\n";
        $prompt .= "2. **KEY FUNCTIONS**: List and explain each function/hook\n";
        $prompt .= "3. **HOW IT WORKS**: Step-by-step explanation of the flow\n";
        $prompt .= "4. **DEPENDENCIES**: What plugins/features does it require?\n";
        $prompt .= "5. **SECURITY**: Any security measures implemented\n\n";
        
        $max_tokens = 800; // More tokens for detailed analysis
    } else {
        $system_message = 'You are a WordPress developer assistant. Summarize code snippets concisely in 1-2 sentences. Focus on what the code does, not how it does it. Do not include phrases like "This code" - just state what it does directly.';
        
        $prompt = "Analyze this WordPress/PHP code snippet and provide a concise 1-2 sentence summary of what it does. Focus on the main functionality and purpose. Be specific but brief.\n\n";
        
        $max_tokens = 150;
    }
    
    if ( ! empty( $title ) ) {
        $prompt .= "Snippet Title: {$title}\n\n";
    }
    $prompt .= "Code:\n```php\n{$code_sample}\n```";
    
    // Prepare API request
    $request_body = array(
        'model'       => 'gpt-4o-mini', // Cost-effective, good quality
        'messages'    => array(
            array(
                'role'    => 'system',
                'content' => $system_message,
            ),
            array(
                'role'    => 'user',
                'content' => $prompt,
            ),
        ),
        'max_tokens'  => $max_tokens,
        'temperature' => 0.3,
    );
    
    $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
        'timeout' => 60, // Longer timeout for full analysis
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ),
        'body'    => wp_json_encode( $request_body ),
    ) );
    
    if ( is_wp_error( $response ) ) {
        return '';
    }
    
    $response_code = wp_remote_retrieve_response_code( $response );
    if ( 200 !== $response_code ) {
        return '';
    }
    
    $body = wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );
    
    if ( ! empty( $data['choices'][0]['message']['content'] ) ) {
        return trim( $data['choices'][0]['message']['content'] );
    }
    
    return '';
}
/**
 * Wrapper for backward compatibility - calls summary mode
 */
function kic_openai_summarize_code( $code, $title = '' ) {
    return kic_openai_analyze_code( $code, $title, 'summary' );
}
/**
 * AJAX handler for downloading AI-enhanced directory
 * Uses OpenAI API to generate summaries for each snippet
 * 
 * @since 1.8.0
 */
add_action( 'wp_ajax_download_ai_directory', function() {
    
    // Capability check FIRST
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( array( 'message' => 'Unauthorized access.' ), 403 );
    }
    
    // Verify nonce
    check_ajax_referer( 'download_ai_directory_action', '_wpnonce' );
    
    // Check API key
    $api_key = get_option( 'kic_openai_api_key' );
    if ( empty( $api_key ) ) {
        wp_die( 'OpenAI API key not configured. Go to Settings → Code Snippets AI to add your key.', 'Configuration Error', array( 'response' => 400 ) );
    }
    
    // Get all sync records
    $snippets = get_posts( array(
        'post_type'      => 'wpcode_snippets_sync',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'no_found_rows'  => true,
        'orderby'        => 'title',
        'order'          => 'ASC',
    ) );
    
    // Get ALL subfolders
    $mu_subfolders = kic_scan_mu_subfolders();
    
    if ( empty( $snippets ) && empty( $mu_subfolders ) ) {
        wp_send_json_error( array( 'message' => 'No snippets found.' ), 404 );
    }
    
    // Build directory content
    $directory = "================================================================================\n";
    $directory .= "              CODE SNIPPETS DIRECTORY (AI-ENHANCED)\n";
    $directory .= "================================================================================\n";
    $directory .= "Generated: " . gmdate( 'Y-m-d H:i:s' ) . " UTC\n";
    $directory .= "Site: " . home_url() . "\n";
    $directory .= "AI Model: GPT-4o-mini (OpenAI)\n";
    $directory .= "================================================================================\n\n";
    
    // Counters
    $wpcode_entries = array();
    $mu_plugin_entries = array();
    $mu_subfolder_entries = array();
    $ai_summaries_generated = 0;
    $ai_failures = 0;
    
    // Process synced snippets
    foreach ( $snippets as $snippet ) {
        $post_id = $snippet->ID;
        
        // Get ACF fields
        $kic_id = get_field( 'kic_snippet_id', $post_id );
        $snippet_type = get_field( 'snippet_type', $post_id );
        $snippet_title = get_field( 'snippet_title', $post_id );
        $snippet_code = get_field( 'snippet_code', $post_id );
        $original_wpcode_id = get_field( 'original_wpcode_id', $post_id );
        $snippet_active = get_field( 'snippet_active', $post_id );
        $snippet_priority = get_field( 'snippet_priority', $post_id );
        $snippet_location = get_field( 'snippet_location', $post_id );
        $source_type = get_field( 'source_type', $post_id );
        $file_path_meta = get_field( 'file_path', $post_id );
        
        // Get AI summary
        $ai_summary = '';
        if ( ! empty( $snippet_code ) ) {
            $ai_summary = kic_openai_summarize_code( $snippet_code, $snippet_title );
            if ( ! empty( $ai_summary ) ) {
                $ai_summaries_generated++;
            } else {
                $ai_failures++;
                // Fallback to parsed description
                $ai_summary = kic_parse_code_description( $snippet_code );
                if ( ! empty( $ai_summary ) ) {
                    $ai_summary = '[Parsed] ' . $ai_summary;
                }
            }
        }
        
        // Build entry
        $entry = array(
            'title'       => $snippet_title ? $snippet_title : $snippet->post_title,
            'id'          => $original_wpcode_id ? $original_wpcode_id : $post_id,
            'kic_id'      => $kic_id,
            'type'        => $snippet_type ? strtoupper( $snippet_type ) : 'UNKNOWN',
            'status'      => $snippet_active ? 'ACTIVE' : 'INACTIVE',
            'priority'    => $snippet_priority ? $snippet_priority : 10,
            'location'    => $snippet_location ? $snippet_location : 'N/A',
            'description' => $ai_summary,
            'file_path'   => $file_path_meta,
        );
        
        // Sort into categories
        if ( 'mu-plugin' === $source_type ) {
            $mu_plugin_entries[] = $entry;
        } elseif ( 'mu-plugin-subfolder' === $source_type ) {
            $mu_subfolder_entries[] = $entry;
        } else {
            $wpcode_entries[] = $entry;
        }
        
        // Small delay to avoid rate limiting (be nice to the API)
        usleep( 100000 ); // 100ms
    }
    
    // Process unsynced subfolder files
    $mu_plugins_dir = WPMU_PLUGIN_DIR;
    foreach ( $mu_subfolders as $folder ) {
        foreach ( $folder['files'] as $file_info ) {
            $file_path = $file_info['path'];
            $filename = $file_info['filename'];
            
            // Check if already synced
            $already_synced = false;
            foreach ( $mu_subfolder_entries as $entry ) {
                if ( isset( $entry['file_path'] ) && basename( $entry['file_path'] ) === $filename ) {
                    $already_synced = true;
                    break;
                }
            }
            
            if ( ! $already_synced ) {
                $real_path = realpath( $file_path );
                $real_mu_dir = realpath( $mu_plugins_dir );
                
                if ( $real_path && strpos( $real_path, $real_mu_dir ) === 0 && file_exists( $file_path ) ) {
                    $code_content = file_get_contents( $file_path );
                    
                    // Get AI summary
                    $ai_summary = kic_openai_summarize_code( $code_content, $filename );
                    if ( ! empty( $ai_summary ) ) {
                        $ai_summaries_generated++;
                    } else {
                        $ai_failures++;
                        $ai_summary = kic_parse_code_description( $code_content );
                        if ( ! empty( $ai_summary ) ) {
                            $ai_summary = '[Parsed] ' . $ai_summary;
                        }
                    }
                    
                    $mu_subfolder_entries[] = array(
                        'title'       => $filename,
                        'id'          => 'N/A',
                        'kic_id'      => 'mu_subfolder_' . $folder['folder_name'] . '_' . pathinfo( $filename, PATHINFO_FILENAME ),
                        'type'        => 'PHP',
                        'status'      => 'ACTIVE',
                        'priority'    => 'N/A',
                        'location'    => 'mu-plugins/' . $folder['folder_name'] . '/',
                        'description' => $ai_summary,
                        'file_path'   => $file_path,
                    );
                    
                    usleep( 100000 ); // 100ms delay
                }
            }
        }
    }
    
    // ========================================
    // SECTION 1: WPCODE SNIPPETS
    // ========================================
    $directory .= "################################################################################\n";
    $directory .= "# SECTION 1: WPCODE SNIPPETS (" . count( $wpcode_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $wpcode_entries ) ) {
        $directory .= "(No WPCode snippets found)\n\n";
    } else {
        foreach ( $wpcode_entries as $index => $entry ) {
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "  WPCode ID:    %s\n", $entry['id'] );
            $directory .= sprintf( "  Type:         %s\n", $entry['type'] );
            $directory .= sprintf( "  Status:       %s\n", $entry['status'] );
            $directory .= sprintf( "  Priority:     %s\n", $entry['priority'] );
            $directory .= sprintf( "  Location:     %s\n", $entry['location'] );
            $directory .= "\n";
            $directory .= "  AI SUMMARY:\n";
            if ( ! empty( $entry['description'] ) ) {
                $desc_lines = explode( "\n", wordwrap( $entry['description'], 74 ) );
                foreach ( $desc_lines as $line ) {
                    $directory .= "    " . $line . "\n";
                }
            } else {
                $directory .= "    (Could not generate summary)\n";
            }
            $directory .= "\n";
        }
    }
    
    // ========================================
    // SECTION 2: MU-PLUGINS (ROOT)
    // ========================================
    $directory .= "\n################################################################################\n";
    $directory .= "# SECTION 2: MU-PLUGINS - ROOT (" . count( $mu_plugin_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $mu_plugin_entries ) ) {
        $directory .= "(No root MU-plugins found)\n\n";
    } else {
        foreach ( $mu_plugin_entries as $index => $entry ) {
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
            $directory .= "--------------------------------------------------------------------------------\n";
            $directory .= sprintf( "  Type:         %s\n", $entry['type'] );
            $directory .= sprintf( "  Status:       %s (always active)\n", $entry['status'] );
            $directory .= sprintf( "  File:         %s\n", $entry['file_path'] ? basename( $entry['file_path'] ) : 'N/A' );
            $directory .= "\n";
            $directory .= "  AI SUMMARY:\n";
            if ( ! empty( $entry['description'] ) ) {
                $desc_lines = explode( "\n", wordwrap( $entry['description'], 74 ) );
                foreach ( $desc_lines as $line ) {
                    $directory .= "    " . $line . "\n";
                }
            } else {
                $directory .= "    (Could not generate summary)\n";
            }
            $directory .= "\n";
        }
    }
    
    // ========================================
    // SECTION 3: MU-PLUGINS (SUBFOLDERS)
    // ========================================
    $directory .= "\n################################################################################\n";
    $directory .= "# SECTION 3: MU-PLUGINS - SUBFOLDERS (" . count( $mu_subfolder_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $mu_subfolder_entries ) ) {
        $directory .= "(No subfolder MU-plugins found)\n\n";
    } else {
        $by_folder = array();
        foreach ( $mu_subfolder_entries as $entry ) {
            $folder = 'unknown';
            if ( ! empty( $entry['location'] ) ) {
                preg_match( '/mu-plugins\/([^\/]+)\//', $entry['location'], $matches );
                if ( ! empty( $matches[1] ) ) {
                    $folder = $matches[1];
                }
            } elseif ( ! empty( $entry['file_path'] ) ) {
                $parts = explode( '/', str_replace( '\\', '/', $entry['file_path'] ) );
                $mu_index = array_search( 'mu-plugins', $parts );
                if ( $mu_index !== false && isset( $parts[ $mu_index + 1 ] ) ) {
                    $folder = $parts[ $mu_index + 1 ];
                }
            }
            $by_folder[ $folder ][] = $entry;
        }
        
        foreach ( $by_folder as $folder_name => $entries ) {
            $directory .= "=== FOLDER: " . $folder_name . "/ (" . count( $entries ) . " files) ===\n\n";
            
            foreach ( $entries as $index => $entry ) {
                $directory .= "--------------------------------------------------------------------------------\n";
                $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
                $directory .= "--------------------------------------------------------------------------------\n";
                $directory .= sprintf( "  Type:         %s\n", $entry['type'] );
                $directory .= sprintf( "  Status:       ACTIVE (always active)\n" );
                $directory .= sprintf( "  Folder:       %s/\n", $folder_name );
                $directory .= "\n";
                $directory .= "  AI SUMMARY:\n";
                if ( ! empty( $entry['description'] ) ) {
                    $desc_lines = explode( "\n", wordwrap( $entry['description'], 74 ) );
                    foreach ( $desc_lines as $line ) {
                        $directory .= "    " . $line . "\n";
                    }
                } else {
                    $directory .= "    (Could not generate summary)\n";
                }
                $directory .= "\n";
            }
        }
    }
    
    // ========================================
    // SUMMARY
    // ========================================
    $total_snippets = count( $wpcode_entries ) + count( $mu_plugin_entries ) + count( $mu_subfolder_entries );
    
    $directory .= "\n################################################################################\n";
    $directory .= "# SUMMARY\n";
    $directory .= "################################################################################\n\n";
    $directory .= sprintf( "  WPCode Snippets:          %d\n", count( $wpcode_entries ) );
    $directory .= sprintf( "  MU-Plugins (root):        %d\n", count( $mu_plugin_entries ) );
    $directory .= sprintf( "  MU-Plugins (subfolders):  %d\n", count( $mu_subfolder_entries ) );
    $directory .= sprintf( "  --------------------------------\n" );
    $directory .= sprintf( "  TOTAL SNIPPETS:           %d\n", $total_snippets );
    $directory .= "\n";
    $directory .= sprintf( "  AI Summaries Generated:   %d\n", $ai_summaries_generated );
    $directory .= sprintf( "  Fallback (Parsed):        %d\n", $ai_failures );
    $directory .= "\n================================================================================\n";
    $directory .= "                    END OF AI-ENHANCED DIRECTORY\n";
    $directory .= "================================================================================\n";
    
    // Generate filename
    $filename = 'code-snippets-ai-directory-' . gmdate( 'Y-m-d-His' ) . '.txt';
    
    // Send headers for download
    header( 'Content-Type: text/plain; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
    header( 'Content-Length: ' . strlen( $directory ) );
    header( 'Pragma: no-cache' );
    header( 'Expires: 0' );
    
    echo $directory;
    exit;
} );
/**
 * AJAX handler for downloading FULL AI analysis
 * Provides detailed explanation of every part of each snippet
 * 
 * @since 1.8.0
 */
add_action( 'wp_ajax_download_full_ai_analysis', function() {
    
    // Capability check FIRST
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( array( 'message' => 'Unauthorized access.' ), 403 );
    }
    
    // Verify nonce
    check_ajax_referer( 'download_full_ai_analysis_action', '_wpnonce' );
    
    // Check API key
    $api_key = get_option( 'kic_openai_api_key' );
    if ( empty( $api_key ) ) {
        wp_die( 'OpenAI API key not configured. Go to Settings → Code Snippets AI to add your key.', 'Configuration Error', array( 'response' => 400 ) );
    }
    
    // Get all sync records
    $snippets = get_posts( array(
        'post_type'      => 'wpcode_snippets_sync',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
        'no_found_rows'  => true,
        'orderby'        => 'title',
        'order'          => 'ASC',
    ) );
    
    $mu_subfolders = kic_scan_mu_subfolders();
    
    if ( empty( $snippets ) && empty( $mu_subfolders ) ) {
        wp_send_json_error( array( 'message' => 'No snippets found.' ), 404 );
    }
    
    // Build directory content
    $directory = "================================================================================\n";
    $directory .= "            FULL CODE ANALYSIS REPORT (AI-POWERED)\n";
    $directory .= "================================================================================\n";
    $directory .= "Generated: " . gmdate( 'Y-m-d H:i:s' ) . " UTC\n";
    $directory .= "Site: " . home_url() . "\n";
    $directory .= "AI Model: GPT-4o-mini (OpenAI)\n";
    $directory .= "Analysis Type: FULL (detailed breakdown of each snippet)\n";
    $directory .= "================================================================================\n\n";
    $directory .= "NOTE: This report provides comprehensive analysis of each code snippet,\n";
    $directory .= "including purpose, functions, flow, dependencies, and security measures.\n";
    $directory .= "\n================================================================================\n\n";
    
    // Counters
    $wpcode_entries = array();
    $mu_plugin_entries = array();
    $mu_subfolder_entries = array();
    $ai_analyses_generated = 0;
    $ai_failures = 0;
    
    // Process synced snippets with FULL analysis
    foreach ( $snippets as $snippet ) {
        $post_id = $snippet->ID;
        
        $kic_id = get_field( 'kic_snippet_id', $post_id );
        $snippet_type = get_field( 'snippet_type', $post_id );
        $snippet_title = get_field( 'snippet_title', $post_id );
        $snippet_code = get_field( 'snippet_code', $post_id );
        $original_wpcode_id = get_field( 'original_wpcode_id', $post_id );
        $snippet_active = get_field( 'snippet_active', $post_id );
        $snippet_priority = get_field( 'snippet_priority', $post_id );
        $snippet_location = get_field( 'snippet_location', $post_id );
        $source_type = get_field( 'source_type', $post_id );
        $file_path_meta = get_field( 'file_path', $post_id );
        
        // Get FULL AI analysis
        $ai_analysis = '';
        if ( ! empty( $snippet_code ) ) {
            $ai_analysis = kic_openai_analyze_code( $snippet_code, $snippet_title, 'full' );
            if ( ! empty( $ai_analysis ) ) {
                $ai_analyses_generated++;
            } else {
                $ai_failures++;
                $ai_analysis = '[Analysis failed - API error or timeout]';
            }
        }
        
        $entry = array(
            'title'       => $snippet_title ? $snippet_title : $snippet->post_title,
            'id'          => $original_wpcode_id ? $original_wpcode_id : $post_id,
            'kic_id'      => $kic_id,
            'type'        => $snippet_type ? strtoupper( $snippet_type ) : 'UNKNOWN',
            'status'      => $snippet_active ? 'ACTIVE' : 'INACTIVE',
            'priority'    => $snippet_priority ? $snippet_priority : 10,
            'location'    => $snippet_location ? $snippet_location : 'N/A',
            'analysis'    => $ai_analysis,
            'file_path'   => $file_path_meta,
            'code_length' => strlen( $snippet_code ),
        );
        
        if ( 'mu-plugin' === $source_type ) {
            $mu_plugin_entries[] = $entry;
        } elseif ( 'mu-plugin-subfolder' === $source_type ) {
            $mu_subfolder_entries[] = $entry;
        } else {
            $wpcode_entries[] = $entry;
        }
        
        // Longer delay for full analysis (be nice to API)
        usleep( 200000 ); // 200ms
    }
    
    // Process unsynced subfolder files with FULL analysis
    $mu_plugins_dir = WPMU_PLUGIN_DIR;
    foreach ( $mu_subfolders as $folder ) {
        foreach ( $folder['files'] as $file_info ) {
            $file_path = $file_info['path'];
            $filename = $file_info['filename'];
            
            $already_synced = false;
            foreach ( $mu_subfolder_entries as $entry ) {
                if ( isset( $entry['file_path'] ) && basename( $entry['file_path'] ) === $filename ) {
                    $already_synced = true;
                    break;
                }
            }
            
            if ( ! $already_synced ) {
                $real_path = realpath( $file_path );
                $real_mu_dir = realpath( $mu_plugins_dir );
                
                if ( $real_path && strpos( $real_path, $real_mu_dir ) === 0 && file_exists( $file_path ) ) {
                    $code_content = file_get_contents( $file_path );
                    
                    $ai_analysis = kic_openai_analyze_code( $code_content, $filename, 'full' );
                    if ( ! empty( $ai_analysis ) ) {
                        $ai_analyses_generated++;
                    } else {
                        $ai_failures++;
                        $ai_analysis = '[Analysis failed - API error or timeout]';
                    }
                    
                    $mu_subfolder_entries[] = array(
                        'title'       => $filename,
                        'id'          => 'N/A',
                        'kic_id'      => 'mu_subfolder_' . $folder['folder_name'] . '_' . pathinfo( $filename, PATHINFO_FILENAME ),
                        'type'        => 'PHP',
                        'status'      => 'ACTIVE',
                        'priority'    => 'N/A',
                        'location'    => 'mu-plugins/' . $folder['folder_name'] . '/',
                        'analysis'    => $ai_analysis,
                        'file_path'   => $file_path,
                        'code_length' => strlen( $code_content ),
                    );
                    
                    usleep( 200000 ); // 200ms delay
                }
            }
        }
    }
    
    // ========================================
    // SECTION 1: WPCODE SNIPPETS
    // ========================================
    $directory .= "################################################################################\n";
    $directory .= "# SECTION 1: WPCODE SNIPPETS (" . count( $wpcode_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $wpcode_entries ) ) {
        $directory .= "(No WPCode snippets found)\n\n";
    } else {
        foreach ( $wpcode_entries as $index => $entry ) {
            $directory .= "================================================================================\n";
            $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
            $directory .= "================================================================================\n";
            $directory .= sprintf( "WPCode ID:    %s\n", $entry['id'] );
            $directory .= sprintf( "Type:         %s\n", $entry['type'] );
            $directory .= sprintf( "Status:       %s\n", $entry['status'] );
            $directory .= sprintf( "Priority:     %s\n", $entry['priority'] );
            $directory .= sprintf( "Location:     %s\n", $entry['location'] );
            $directory .= sprintf( "Code Length:  %s chars\n", number_format( $entry['code_length'] ) );
            $directory .= "\n--- FULL AI ANALYSIS ---\n\n";
            if ( ! empty( $entry['analysis'] ) ) {
                $directory .= $entry['analysis'] . "\n";
            } else {
                $directory .= "(Could not generate analysis)\n";
            }
            $directory .= "\n";
        }
    }
    
    // ========================================
    // SECTION 2: MU-PLUGINS (ROOT)
    // ========================================
    $directory .= "\n################################################################################\n";
    $directory .= "# SECTION 2: MU-PLUGINS - ROOT (" . count( $mu_plugin_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $mu_plugin_entries ) ) {
        $directory .= "(No root MU-plugins found)\n\n";
    } else {
        foreach ( $mu_plugin_entries as $index => $entry ) {
            $directory .= "================================================================================\n";
            $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
            $directory .= "================================================================================\n";
            $directory .= sprintf( "Type:         %s\n", $entry['type'] );
            $directory .= sprintf( "Status:       %s (always active)\n", $entry['status'] );
            $directory .= sprintf( "File:         %s\n", $entry['file_path'] ? basename( $entry['file_path'] ) : 'N/A' );
            $directory .= sprintf( "Code Length:  %s chars\n", number_format( $entry['code_length'] ) );
            $directory .= "\n--- FULL AI ANALYSIS ---\n\n";
            if ( ! empty( $entry['analysis'] ) ) {
                $directory .= $entry['analysis'] . "\n";
            } else {
                $directory .= "(Could not generate analysis)\n";
            }
            $directory .= "\n";
        }
    }
    
    // ========================================
    // SECTION 3: MU-PLUGINS (SUBFOLDERS)
    // ========================================
    $directory .= "\n################################################################################\n";
    $directory .= "# SECTION 3: MU-PLUGINS - SUBFOLDERS (" . count( $mu_subfolder_entries ) . " total)\n";
    $directory .= "################################################################################\n\n";
    
    if ( empty( $mu_subfolder_entries ) ) {
        $directory .= "(No subfolder MU-plugins found)\n\n";
    } else {
        $by_folder = array();
        foreach ( $mu_subfolder_entries as $entry ) {
            $folder = 'unknown';
            if ( ! empty( $entry['location'] ) ) {
                preg_match( '/mu-plugins\/([^\/]+)\//', $entry['location'], $matches );
                if ( ! empty( $matches[1] ) ) {
                    $folder = $matches[1];
                }
            } elseif ( ! empty( $entry['file_path'] ) ) {
                $parts = explode( '/', str_replace( '\\', '/', $entry['file_path'] ) );
                $mu_index = array_search( 'mu-plugins', $parts );
                if ( $mu_index !== false && isset( $parts[ $mu_index + 1 ] ) ) {
                    $folder = $parts[ $mu_index + 1 ];
                }
            }
            $by_folder[ $folder ][] = $entry;
        }
        
        foreach ( $by_folder as $folder_name => $entries ) {
            $directory .= "=== FOLDER: " . $folder_name . "/ (" . count( $entries ) . " files) ===\n\n";
            
            foreach ( $entries as $index => $entry ) {
                $directory .= "================================================================================\n";
                $directory .= sprintf( "[%d] %s\n", $index + 1, $entry['title'] );
                $directory .= "================================================================================\n";
                $directory .= sprintf( "Type:         %s\n", $entry['type'] );
                $directory .= sprintf( "Status:       ACTIVE (always active)\n" );
                $directory .= sprintf( "Folder:       %s/\n", $folder_name );
                $directory .= sprintf( "Code Length:  %s chars\n", isset( $entry['code_length'] ) ? number_format( $entry['code_length'] ) : 'N/A' );
                $directory .= "\n--- FULL AI ANALYSIS ---\n\n";
                if ( ! empty( $entry['analysis'] ) ) {
                    $directory .= $entry['analysis'] . "\n";
                } else {
                    $directory .= "(Could not generate analysis)\n";
                }
                $directory .= "\n";
            }
        }
    }
    
    // ========================================
    // SUMMARY
    // ========================================
    $total_snippets = count( $wpcode_entries ) + count( $mu_plugin_entries ) + count( $mu_subfolder_entries );
    
    $directory .= "\n################################################################################\n";
    $directory .= "# ANALYSIS SUMMARY\n";
    $directory .= "################################################################################\n\n";
    $directory .= sprintf( "  WPCode Snippets:          %d\n", count( $wpcode_entries ) );
    $directory .= sprintf( "  MU-Plugins (root):        %d\n", count( $mu_plugin_entries ) );
    $directory .= sprintf( "  MU-Plugins (subfolders):  %d\n", count( $mu_subfolder_entries ) );
    $directory .= sprintf( "  --------------------------------\n" );
    $directory .= sprintf( "  TOTAL ANALYZED:           %d\n", $total_snippets );
    $directory .= "\n";
    $directory .= sprintf( "  Successful Analyses:      %d\n", $ai_analyses_generated );
    $directory .= sprintf( "  Failed Analyses:          %d\n", $ai_failures );
    $directory .= "\n";
    $directory .= "================================================================================\n";
    $directory .= "                    END OF FULL ANALYSIS REPORT\n";
    $directory .= "================================================================================\n";
    
    // Generate filename
    $filename = 'code-full-ai-analysis-' . gmdate( 'Y-m-d-His' ) . '.txt';
    
    // Send headers for download
    header( 'Content-Type: text/plain; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
    header( 'Content-Length: ' . strlen( $directory ) );
    header( 'Pragma: no-cache' );
    header( 'Expires: 0' );
    
    echo $directory;
    exit;
} );
/**
 * Add settings page for OpenAI API key
 */
add_action( 'admin_menu', function() {
    add_options_page(
        'Code Snippets AI Settings',
        'Code Snippets AI',
        'manage_options',
        'kic-snippets-ai-settings',
        'kic_snippets_ai_settings_page'
    );
} );
/**
 * Settings page callback
 */
function kic_snippets_ai_settings_page() {
    // Handle form submission
    if ( isset( $_POST['kic_save_openai_key'] ) ) {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( 'Unauthorized' );
        }
        
        check_admin_referer( 'kic_openai_settings_nonce' );
        
        $api_key = isset( $_POST['kic_openai_api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['kic_openai_api_key'] ) ) : '';
        update_option( 'kic_openai_api_key', $api_key );
        
        echo '<div class="notice notice-success"><p>OpenAI API key saved!</p></div>';
    }
    
    $current_key = get_option( 'kic_openai_api_key', '' );
    $masked_key = ! empty( $current_key ) ? substr( $current_key, 0, 7 ) . '...' . substr( $current_key, -4 ) : '';
    
    ?>
    <div class="wrap">
        <h1>🤖 Code Snippets AI Settings</h1>
        <p>Configure OpenAI API integration for AI-powered code summaries.</p>
        
        <div class="card" style="max-width: 600px; padding: 20px; margin: 20px 0;">
            <h2>OpenAI API Key</h2>
            <form method="post" action="">
                <?php wp_nonce_field( 'kic_openai_settings_nonce' ); ?>
                
                <table class="form-table">
                    <tr>
                        <th scope="row"><label for="kic_openai_api_key">API Key</label></th>
                        <td>
                            <input type="password" 
                                   name="kic_openai_api_key" 
                                   id="kic_openai_api_key" 
                                   class="regular-text"
                                   value="<?php echo esc_attr( $current_key ); ?>"
                                   placeholder="sk-...">
                            <?php if ( ! empty( $masked_key ) ) : ?>
                                <p class="description">Current: <code><?php echo esc_html( $masked_key ); ?></code></p>
                            <?php endif; ?>
                            <p class="description">
                                Get your API key from <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI Platform</a>
                            </p>
                        </td>
                    </tr>
                </table>
                
                <p class="submit">
                    <button type="submit" name="kic_save_openai_key" class="button button-primary">
                        Save API Key
                    </button>
                </p>
            </form>
        </div>
        
        <div class="card" style="max-width: 600px; padding: 20px; margin: 20px 0;">
            <h2>💡 How It Works</h2>
            <ol>
                <li>Enter your OpenAI API key above</li>
                <li>Go to <strong>Code Snippets (Sync)</strong> in the admin menu</li>
                <li>Click the <strong style="color: #10a37f;">AI Directory</strong> button</li>
                <li>Each snippet's code is sent to GPT-4o-mini for summarization</li>
                <li>Download includes AI-generated descriptions for all code</li>
            </ol>
            
            <h3>Cost Estimate</h3>
            <p>
                Using <strong>GPT-4o-mini</strong> (most cost-effective):<br>
                ~$0.001 - $0.005 per snippet depending on code length<br>
                <strong>50 snippets ≈ $0.05 - $0.25</strong>
            </p>
        </div>
    </div>
    <?php
}
/**
 * Add download link to individual row actions
 */
add_filter( 'post_row_actions', function( $actions, $post ) {
    
    if ( 'wpcode_snippets_sync' !== $post->post_type ) {
        return $actions;
    }
    
    $snippet_file = get_field( 'snippet_code_file', $post->ID );
    
    if ( ! empty( $snippet_file ) ) {
        $file_url = is_array( $snippet_file ) ? $snippet_file['url'] : $snippet_file;
        
        if ( ! empty( $file_url ) ) {
            $download_url = add_query_arg( 'download', '1', esc_url_raw( $file_url ) );
            $actions['download'] = sprintf(
                '<a href="%s" title="%s">%s</a>',
                esc_url( $download_url ),
                esc_attr__( 'Download snippet file', 'wpcode-snippets-sync' ),
                esc_html__( 'Download', 'wpcode-snippets-sync' )
            );
        }
    }
    
    return $actions;
}, 10, 2 );
/**
 * Register admin scripts for button feedback
 */
add_action( 'admin_footer', function() {
    $screen = get_current_screen();
    
    if ( ! $screen || 'edit-wpcode_snippets_sync' !== $screen->id ) {
        return;
    }
    ?>
    <script>
    jQuery(document).ready(function($) {
        $('#download-all-snippets-btn').on('click', function() {
            var $btn = $(this);
            var originalText = $btn.html();
            
            $btn.html('<span class="dashicons dashicons-update" style="margin-top: 3px; margin-right: 3px; animation: rotation 1s infinite linear;"></span> Preparing ZIP...');
            
            setTimeout(function() {
                $btn.html(originalText);
            }, 3000);
        });
    });
    </script>
    <style>
    @keyframes rotation {
        from { transform: rotate(0deg); }
        to { transform: rotate(360deg); }
    }
    </style>
    <?php
} );

Comments

Add a Comment