| |
| <?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| defined( 'ABSPATH' ) || exit;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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;
|
| }
|
|
|
|
|
| $directories = glob( trailingslashit( $mu_plugins_dir ) . '*', GLOB_ONLYDIR );
|
|
|
| if ( empty( $directories ) ) {
|
| return $subfolders;
|
| }
|
|
|
| foreach ( $directories as $dir_path ) {
|
| $folder_name = basename( $dir_path );
|
|
|
|
|
| $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 ),
|
| 'folder_name' => $folder_name,
|
| );
|
| }
|
|
|
| $subfolders[] = array(
|
| 'folder_name' => $folder_name,
|
| 'folder_path' => $dir_path,
|
| 'files' => $files_data,
|
| 'file_count' => count( $files_data ),
|
| );
|
| }
|
|
|
| return $subfolders;
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| add_action( 'admin_notices', function() {
|
| $screen = get_current_screen();
|
|
|
| if ( ! $screen || 'edit-wpcode_snippets_sync' !== $screen->id ) {
|
| return;
|
| }
|
|
|
|
|
| $count = wp_count_posts( 'wpcode_snippets_sync' );
|
| $total = isset( $count->publish ) ? $count->publish : 0;
|
|
|
| if ( $total < 1 ) {
|
| return;
|
| }
|
|
|
|
|
| $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',
|
| ),
|
| ),
|
| ) ) );
|
|
|
|
|
| $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;
|
|
|
|
|
| $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'];
|
| }
|
|
|
|
|
| $download_url = wp_nonce_url(
|
| admin_url( 'admin-ajax.php?action=download_all_snippets' ),
|
| 'download_all_snippets_action',
|
| '_wpnonce'
|
| );
|
|
|
| ?>
|
| <div class="notice notice-info" style="display: flex; align-items: center; justify-content: space-between; padding: 12px 15px;">
|
| <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>
|
| <a href="<?php echo esc_url( $download_url ); ?>"
|
| class="button button-primary"
|
| style="margin-left: 15px;"
|
| id="download-all-snippets-btn">
|
| <span class="dashicons dashicons-download" style="margin-top: 3px; margin-right: 3px;"></span>
|
| Download All Snippets
|
| </a>
|
| </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() {
|
|
|
|
|
| if ( ! current_user_can( 'manage_options' ) ) {
|
| wp_send_json_error( array( 'message' => 'Unauthorized access.' ), 403 );
|
| }
|
|
|
|
|
| check_ajax_referer( 'download_all_snippets_action', '_wpnonce' );
|
|
|
|
|
| if ( ! class_exists( 'ZipArchive' ) ) {
|
| wp_send_json_error( array( 'message' => 'ZipArchive PHP extension is required but not available.' ), 500 );
|
| }
|
|
|
|
|
| $snippets = get_posts( array(
|
| 'post_type' => 'wpcode_snippets_sync',
|
| 'posts_per_page' => -1,
|
| 'post_status' => 'publish',
|
| 'no_found_rows' => true,
|
| ) );
|
|
|
|
|
| $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 );
|
| }
|
|
|
|
|
| $temp_dir = get_temp_dir();
|
| $zip_filename = 'wpcode-snippets-export-' . gmdate( 'Y-m-d-His' ) . '.zip';
|
| $zip_path = trailingslashit( $temp_dir ) . $zip_filename;
|
|
|
|
|
| $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 );
|
| }
|
|
|
|
|
| $upload_dir = wp_upload_dir();
|
|
|
|
|
| $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();
|
|
|
|
|
|
|
|
|
| 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_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 );
|
|
|
|
|
| $is_mu_plugin = ( 'mu-plugin' === $source_type );
|
|
|
|
|
| if ( $is_mu_plugin ) {
|
| $is_active = true;
|
| $status_suffix = '-active';
|
| } else {
|
| $is_active = ! empty( $snippet_active );
|
| $status_suffix = $is_active ? '-active' : '-inactive';
|
| }
|
|
|
|
|
| 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 {
|
| $safe_name = sanitize_file_name( $snippet_title );
|
| if ( empty( $safe_name ) ) {
|
| $safe_name = 'snippet-' . $post_id;
|
| }
|
| $safe_name .= $status_suffix;
|
|
|
| $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 = '';
|
|
|
|
|
| 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 ( 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";
|
| }
|
| }
|
|
|
|
|
|
|
|
|
| $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'];
|
|
|
|
|
| $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_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;
|
| }
|
| }
|
|
|
|
|
| $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;
|
| }
|
|
|
|
|
| $zip->addFromString( 'manifest.json', wp_json_encode( $manifest, JSON_PRETTY_PRINT ) );
|
|
|
|
|
| $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 and root MU-plugin filenames include status:\n";
|
| $readme .= "- `snippet-name-active.php` - Currently running\n";
|
| $readme .= "- `snippet-name-inactive.php` - Disabled\n\n";
|
| $readme .= "**Note:** Subfolder files 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 );
|
|
|
|
|
| $zip->close();
|
|
|
| if ( ! file_exists( $zip_path ) ) {
|
| wp_die( 'ZIP file was not created.', 'Error', array( 'response' => 500 ) );
|
| }
|
|
|
|
|
| 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 );
|
|
|
|
|
| if ( file_exists( $zip_path ) ) {
|
| wp_delete_file( $zip_path );
|
| }
|
|
|
| exit;
|
| } );
|
|
|
|
|
|
|
|
|
| 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 );
|
|
|
|
|
|
|
|
|
| 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