All skills
jeffallan avatar

/wordpress-pro

@efebc44
by jeffallanjeffallan/claude-skills12k stars
1,124

Develops custom WordPress themes and plugins, creates and registers Gutenberg blocks and block patterns, configures WooCommerce stores, implements WordPress REST API endpoints, applies security hardening (nonces, sanitization, escaping, capability checks), and optimizes performance through caching and query tuning. Use when building WordPress themes, writing plugins, customizing Gutenberg blocks, extending WooCommerce, working with ACF, using the WordPress REST API, applying hooks and filters, or improving WordPress performance and security.

Use this Skill: https://skilld.dev/gh/jeffallan/claude-skills/wordpress-pro

This session only. Nothing lands on disk.

referencesperformance-security.md

≈6.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Performance & Security


Performance Optimization

Database Query Optimization

<?php
declare(strict_types=1);

/**
 * Efficient database queries
 */

// BAD: Query inside loop
foreach ($post_ids as $post_id) {
    $meta = get_post_meta($post_id, 'my_key', true); // N+1 queries!
}

// GOOD: Batch query with caching
function get_posts_with_meta(array $post_ids): array {
    global $wpdb;

    $ids = implode(',', array_map('intval', $post_ids));

    // Single query for all meta
    $results = $wpdb->get_results($wpdb->prepare("
        SELECT post_id, meta_value
        FROM {$wpdb->postmeta}
        WHERE post_id IN ({$ids})
        AND meta_key = %s
    ", 'my_key'));

    $meta_map = [];
    foreach ($results as $row) {
        $meta_map[$row->post_id] = $row->meta_value;
    }

    return $meta_map;
}

/**
 * Use proper $wpdb methods with prepared statements
 */
function get_custom_data(int $user_id, string $status): array {
    global $wpdb;

    $table = $wpdb->prefix . 'custom_table';

    // GOOD: Prepared statement prevents SQL injection
    return $wpdb->get_results($wpdb->prepare("
        SELECT id, title, created_at
        FROM {$table}
        WHERE user_id = %d
        AND status = %s
        ORDER BY created_at DESC
        LIMIT 100
    ", $user_id, $status));
}

/**
 * Optimize WP_Query
 */
$optimized_query = new WP_Query([
    'post_type'              => 'product',
    'posts_per_page'         => 10,
    'no_found_rows'          => true,  // Skip SQL_CALC_FOUND_ROWS for pagination
    'update_post_meta_cache' => false, // Skip meta cache if not needed
    'update_post_term_cache' => false, // Skip term cache if not needed
    'fields'                 => 'ids', // Only get IDs if that's all you need
]);

/**
 * Use transients for expensive queries
 */
function get_popular_posts(): array {
    $cache_key = 'popular_posts_week';
    $posts = get_transient($cache_key);

    if ($posts === false) {
        $posts = get_posts([
            'post_type'      => 'post',
            'posts_per_page' => 10,
            'meta_key'       => 'views_count',
            'orderby'        => 'meta_value_num',
            'order'          => 'DESC',
            'date_query'     => [
                ['after' => '1 week ago'],
            ],
        ]);

        set_transient($cache_key, $posts, HOUR_IN_SECONDS);
    }

    return $posts;
}

/**
 * Invalidate cache when data changes
 */
add_action('save_post', function(int $post_id): void {
    delete_transient('popular_posts_week');
    delete_transient('featured_posts');
});

Object Caching

<?php
/**
 * WordPress object cache (works with Redis, Memcached)
 */

// Set cache
wp_cache_set('my_data', $data, 'my_plugin', 3600);

// Get cache
$data = wp_cache_get('my_data', 'my_plugin');
if ($data === false) {
    // Cache miss - fetch and set
    $data = expensive_operation();
    wp_cache_set('my_data', $data, 'my_plugin', 3600);
}

// Delete cache
wp_cache_delete('my_data', 'my_plugin');

// Cache with automatic handling
function get_expensive_data(int $id): mixed {
    $cache_key = 'expensive_data_' . $id;
    $cache_group = 'my_plugin';

    $data = wp_cache_get($cache_key, $cache_group);

    if ($data === false) {
        $data = perform_expensive_operation($id);
        wp_cache_set($cache_key, $data, $cache_group, HOUR_IN_SECONDS);
    }

    return $data;
}

/**
 * Fragment caching for expensive HTML
 */
function render_sidebar_widget(): void {
    $cache_key = 'sidebar_widget_html';
    $html = get_transient($cache_key);

    if ($html === false) {
        ob_start();
        // Expensive rendering
        include plugin_dir_path(__FILE__) . 'templates/widget.php';
        $html = ob_get_clean();

        set_transient($cache_key, $html, 15 * MINUTE_IN_SECONDS);
    }

    echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

Asset Optimization

<?php
/**
 * Efficient script and style loading
 */

// Conditional loading
add_action('wp_enqueue_scripts', function(): void {
    // Only load on specific pages
    if (!is_page('contact')) {
        return;
    }

    wp_enqueue_script('contact-form', ...);
});

// Defer non-critical scripts
add_filter('script_loader_tag', function(string $tag, string $handle): string {
    $defer_scripts = ['analytics', 'social-share', 'comments'];

    if (in_array($handle, $defer_scripts, true)) {
        return str_replace(' src', ' defer src', $tag);
    }

    return $tag;
}, 10, 2);

// Async scripts
add_filter('script_loader_tag', function(string $tag, string $handle): string {
    if ($handle === 'my-async-script') {
        return str_replace(' src', ' async src', $tag);
    }
    return $tag;
}, 10, 2);

// Preload critical assets
add_action('wp_head', function(): void {
    $font_url = get_template_directory_uri() . '/assets/fonts/inter.woff2';
    echo '<link rel="preload" href="' . esc_url($font_url) . '" as="font" type="font/woff2" crossorigin>';
}, 1);

// Remove unused scripts/styles
add_action('wp_enqueue_scripts', function(): void {
    // Remove block library CSS if not using blocks
    if (!is_singular()) {
        wp_dequeue_style('wp-block-library');
        wp_dequeue_style('wp-block-library-theme');
        wp_dequeue_style('global-styles');
    }

    // Remove emoji scripts
    remove_action('wp_head', 'print_emoji_detection_script', 7);
    remove_action('wp_print_styles', 'print_emoji_styles');
}, 100);

/**
 * Combine/minify inline styles
 */
add_action('wp_footer', function(): void {
    // Add critical CSS inline
    $critical_css = file_get_contents(get_template_directory() . '/assets/css/critical.css');
    if ($critical_css) {
        echo '<style id="critical-css">' . $critical_css . '</style>'; // phpcs:ignore
    }
}, 1);

Image Optimization

<?php
/**
 * Image optimization techniques
 */

// Add custom image sizes
add_action('after_setup_theme', function(): void {
    add_image_size('card-thumbnail', 400, 300, true);
    add_image_size('hero-image', 1600, 900, true);
});

// Lazy load images
add_filter('wp_get_attachment_image_attributes', function(array $attr): array {
    $attr['loading'] = 'lazy';
    $attr['decoding'] = 'async';
    return $attr;
});

// Add srcset for responsive images
add_filter('wp_calculate_image_srcset_meta', function(array $image_meta): array {
    // Ensure srcset is calculated
    return $image_meta;
});

// WebP support (requires server-side conversion)
add_filter('wp_generate_attachment_metadata', function(array $metadata, int $attachment_id): array {
    $file = get_attached_file($attachment_id);
    $mime = mime_content_type($file);

    if (in_array($mime, ['image/jpeg', 'image/png'], true)) {
        // Convert to WebP (requires Imagick or GD)
        my_plugin_create_webp_version($file);
    }

    return $metadata;
}, 10, 2);

// Serve WebP with fallback
function get_webp_image_url(string $url): string {
    $webp_url = preg_replace('/\.(jpe?g|png)$/i', '.webp', $url);
    $webp_path = str_replace(
        wp_upload_dir()['baseurl'],
        wp_upload_dir()['basedir'],
        $webp_url
    );

    if (file_exists($webp_path)) {
        return $webp_url;
    }

    return $url;
}

Database Cleanup

<?php
/**
 * Database maintenance
 */

// Clean up revisions (run via WP-CLI or cron)
function cleanup_post_revisions(int $keep = 5): int {
    global $wpdb;

    $deleted = 0;

    $posts = $wpdb->get_col("
        SELECT ID FROM {$wpdb->posts}
        WHERE post_type = 'revision'
        AND post_parent IN (
            SELECT ID FROM {$wpdb->posts} WHERE post_type IN ('post', 'page')
        )
    ");

    // Group by parent
    $by_parent = [];
    foreach ($posts as $revision_id) {
        $parent = wp_get_post_parent_id($revision_id);
        $by_parent[$parent][] = $revision_id;
    }

    foreach ($by_parent as $parent_id => $revisions) {
        // Keep most recent $keep revisions
        $to_delete = array_slice($revisions, $keep);
        foreach ($to_delete as $revision_id) {
            wp_delete_post_revision($revision_id);
            $deleted++;
        }
    }

    return $deleted;
}

// Clean up orphaned meta
function cleanup_orphaned_postmeta(): int {
    global $wpdb;

    return $wpdb->query("
        DELETE pm FROM {$wpdb->postmeta} pm
        LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID
        WHERE p.ID IS NULL
    ");
}

// Clean up transients
function cleanup_expired_transients(): int {
    global $wpdb;

    $time = time();

    return $wpdb->query($wpdb->prepare("
        DELETE a, b FROM {$wpdb->options} a
        INNER JOIN {$wpdb->options} b ON b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 12))
        WHERE a.option_name LIKE %s
        AND b.option_value < %d
    ", '_transient_%', $time));
}

// Schedule cleanup
add_action('init', function(): void {
    if (!wp_next_scheduled('my_plugin_db_cleanup')) {
        wp_schedule_event(time(), 'weekly', 'my_plugin_db_cleanup');
    }
});

add_action('my_plugin_db_cleanup', function(): void {
    cleanup_post_revisions(3);
    cleanup_orphaned_postmeta();
    cleanup_expired_transients();
});

Security Hardening

Input Sanitization

<?php
declare(strict_types=1);

/**
 * Always sanitize user input
 */

// Text fields
$title = sanitize_text_field($_POST['title'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');
$url = esc_url_raw($_POST['url'] ?? '');

// Textarea (allows line breaks)
$content = sanitize_textarea_field($_POST['content'] ?? '');

// HTML content (with allowed tags)
$html = wp_kses_post($_POST['html_content'] ?? '');

// Custom allowed HTML
$allowed_html = [
    'a'      => ['href' => [], 'title' => [], 'target' => []],
    'strong' => [],
    'em'     => [],
    'p'      => ['class' => []],
];
$safe_html = wp_kses($_POST['custom_html'] ?? '', $allowed_html);

// File names
$filename = sanitize_file_name($_POST['filename'] ?? '');

// Keys (alphanumeric, dashes, underscores)
$key = sanitize_key($_POST['key'] ?? '');

// Arrays
$ids = array_map('absint', (array) ($_POST['ids'] ?? []));
$tags = array_map('sanitize_text_field', (array) ($_POST['tags'] ?? []));

// Numbers
$id = absint($_POST['id'] ?? 0);
$price = (float) filter_var($_POST['price'] ?? 0, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);

/**
 * Database-safe queries
 */
global $wpdb;

// ALWAYS use prepared statements
$results = $wpdb->get_results($wpdb->prepare("
    SELECT * FROM {$wpdb->prefix}custom_table
    WHERE user_id = %d
    AND status = %s
    AND created_at > %s
", $user_id, $status, $date));

// Insert with proper escaping
$wpdb->insert(
    $wpdb->prefix . 'custom_table',
    [
        'user_id' => $user_id,
        'title'   => $title,
        'content' => $content,
    ],
    ['%d', '%s', '%s']
);

// Update with proper escaping
$wpdb->update(
    $wpdb->prefix . 'custom_table',
    ['title' => $new_title],
    ['id' => $id],
    ['%s'],
    ['%d']
);

Output Escaping

<?php
/**
 * Always escape output
 */

// HTML context
echo '<h1>' . esc_html($title) . '</h1>';
echo '<p>' . esc_html__('Welcome', 'my-plugin') . '</p>';

// Attributes
echo '<input type="text" value="' . esc_attr($value) . '" />';
echo '<div class="' . esc_attr($class) . '">';
echo '<div data-config="' . esc_attr(wp_json_encode($config)) . '">';

// URLs
echo '<a href="' . esc_url($url) . '">' . esc_html($text) . '</a>';
echo '<img src="' . esc_url($image_url) . '" alt="' . esc_attr($alt) . '" />';

// JavaScript
echo '<script>var config = ' . wp_json_encode($config) . ';</script>';

// Textarea content
echo '<textarea>' . esc_textarea($content) . '</textarea>';

// Allow specific HTML
echo wp_kses_post($html_content);

// Translation with escaping
printf(
    /* translators: %s: user name */
    esc_html__('Hello, %s!', 'my-plugin'),
    esc_html($user_name)
);

/**
 * When outputting large blocks of HTML
 */
?>
<div class="card">
    <h2><?php echo esc_html($card_title); ?></h2>
    <p><?php echo wp_kses_post($card_content); ?></p>
    <a href="<?php echo esc_url($card_link); ?>" class="<?php echo esc_attr($card_class); ?>">
        <?php echo esc_html($card_cta); ?>
    </a>
</div>
<?php

Nonce Verification

<?php
/**
 * Nonces prevent CSRF attacks
 */

// In form
function render_settings_form(): void {
    ?>
    <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
        <?php wp_nonce_field('my_plugin_save_settings', 'my_plugin_nonce'); ?>
        <input type="hidden" name="action" value="my_plugin_save_settings" />

        <!-- Form fields -->

        <?php submit_button(__('Save Settings', 'my-plugin')); ?>
    </form>
    <?php
}

// Verify nonce on submission
add_action('admin_post_my_plugin_save_settings', function(): void {
    // Verify nonce
    if (!wp_verify_nonce($_POST['my_plugin_nonce'] ?? '', 'my_plugin_save_settings')) {
        wp_die(
            esc_html__('Security check failed.', 'my-plugin'),
            esc_html__('Error', 'my-plugin'),
            ['response' => 403]
        );
    }

    // Verify capability
    if (!current_user_can('manage_options')) {
        wp_die(
            esc_html__('You do not have permission to perform this action.', 'my-plugin'),
            esc_html__('Error', 'my-plugin'),
            ['response' => 403]
        );
    }

    // Process form
    $settings = [
        'option_1' => sanitize_text_field($_POST['option_1'] ?? ''),
        'option_2' => absint($_POST['option_2'] ?? 0),
    ];

    update_option('my_plugin_settings', $settings);

    wp_safe_redirect(add_query_arg('updated', 'true', wp_get_referer()));
    exit;
});

/**
 * AJAX with nonce
 */

// Localize script with nonce
wp_localize_script('my-script', 'myPluginData', [
    'ajaxUrl' => admin_url('admin-ajax.php'),
    'nonce'   => wp_create_nonce('my_plugin_ajax'),
]);

// JavaScript
// fetch(myPluginData.ajaxUrl, {
//     method: 'POST',
//     headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
//     body: new URLSearchParams({
//         action: 'my_plugin_action',
//         nonce: myPluginData.nonce,
//         data: 'value'
//     })
// });

// Handle AJAX
add_action('wp_ajax_my_plugin_action', function(): void {
    check_ajax_referer('my_plugin_ajax', 'nonce');

    if (!current_user_can('edit_posts')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $data = sanitize_text_field($_POST['data'] ?? '');

    // Process request
    $result = process_data($data);

    wp_send_json_success(['result' => $result]);
});

Capability Checks

<?php
/**
 * Always verify user capabilities
 */

// Check before displaying admin page
function render_admin_page(): void {
    if (!current_user_can('manage_options')) {
        wp_die(__('You do not have permission to access this page.', 'my-plugin'));
    }

    // Render page
}

// Check before performing action
function delete_item(int $item_id): bool {
    if (!current_user_can('delete_posts')) {
        return false;
    }

    // Delete item
    return true;
}

// Meta capability check for specific post
function edit_custom_post(int $post_id): bool {
    if (!current_user_can('edit_post', $post_id)) {
        return false;
    }

    // Edit post
    return true;
}

/**
 * Custom capabilities
 */

// Add custom capabilities on activation
function add_custom_capabilities(): void {
    $admin = get_role('administrator');
    $editor = get_role('editor');

    if ($admin) {
        $admin->add_cap('manage_my_plugin');
        $admin->add_cap('edit_my_plugin_items');
        $admin->add_cap('delete_my_plugin_items');
    }

    if ($editor) {
        $editor->add_cap('edit_my_plugin_items');
    }
}

// Use custom capability
if (current_user_can('manage_my_plugin')) {
    // Show management interface
}

// Map meta capabilities
add_filter('map_meta_cap', function(array $caps, string $cap, int $user_id, array $args): array {
    if ($cap === 'edit_my_plugin_item') {
        $item_id = $args[0] ?? 0;
        $item = get_my_plugin_item($item_id);

        if ($item && $item->author_id === $user_id) {
            $caps = ['edit_my_plugin_items'];
        } else {
            $caps = ['manage_my_plugin'];
        }
    }

    return $caps;
}, 10, 4);

File Upload Security

<?php
/**
 * Secure file upload handling
 */

function handle_file_upload(): array|WP_Error {
    // Verify nonce and capability
    if (!wp_verify_nonce($_POST['nonce'] ?? '', 'my_plugin_upload')) {
        return new WP_Error('security', __('Security check failed.', 'my-plugin'));
    }

    if (!current_user_can('upload_files')) {
        return new WP_Error('permission', __('You cannot upload files.', 'my-plugin'));
    }

    // Check file exists
    if (empty($_FILES['my_file']['tmp_name'])) {
        return new WP_Error('no_file', __('No file uploaded.', 'my-plugin'));
    }

    $file = $_FILES['my_file'];

    // Validate file type
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
    $file_type = wp_check_filetype_and_ext($file['tmp_name'], $file['name']);

    if (!in_array($file_type['type'], $allowed_types, true)) {
        return new WP_Error('invalid_type', __('File type not allowed.', 'my-plugin'));
    }

    // Validate file size (5MB max)
    $max_size = 5 * 1024 * 1024;
    if ($file['size'] > $max_size) {
        return new WP_Error('too_large', __('File is too large.', 'my-plugin'));
    }

    // Sanitize filename
    $filename = sanitize_file_name($file['name']);

    // Use WordPress upload handling
    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/media.php';
    require_once ABSPATH . 'wp-admin/includes/image.php';

    // Handle upload
    $upload = wp_handle_upload($file, [
        'test_form' => false,
        'mimes'     => [
            'jpg|jpeg' => 'image/jpeg',
            'png'      => 'image/png',
            'gif'      => 'image/gif',
            'pdf'      => 'application/pdf',
        ],
    ]);

    if (isset($upload['error'])) {
        return new WP_Error('upload_error', $upload['error']);
    }

    // Create attachment
    $attachment_id = wp_insert_attachment([
        'post_mime_type' => $upload['type'],
        'post_title'     => preg_replace('/\.[^.]+$/', '', $filename),
        'post_content'   => '',
        'post_status'    => 'inherit',
    ], $upload['file']);

    // Generate metadata
    $metadata = wp_generate_attachment_metadata($attachment_id, $upload['file']);
    wp_update_attachment_metadata($attachment_id, $metadata);

    return [
        'id'  => $attachment_id,
        'url' => $upload['url'],
    ];
}

Security Headers

<?php
/**
 * Add security headers
 */

add_action('send_headers', function(): void {
    // Only on frontend, not admin or REST
    if (is_admin() || defined('REST_REQUEST')) {
        return;
    }

    // Prevent clickjacking
    header('X-Frame-Options: SAMEORIGIN');

    // Prevent MIME sniffing
    header('X-Content-Type-Options: nosniff');

    // XSS Protection
    header('X-XSS-Protection: 1; mode=block');

    // Referrer Policy
    header('Referrer-Policy: strict-origin-when-cross-origin');

    // Content Security Policy (customize as needed)
    $csp = "default-src 'self'; " .
           "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.google-analytics.com; " .
           "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " .
           "font-src 'self' https://fonts.gstatic.com; " .
           "img-src 'self' data: https:; " .
           "connect-src 'self' https://www.google-analytics.com;";

    header("Content-Security-Policy: {$csp}");

    // Permissions Policy
    header("Permissions-Policy: geolocation=(), microphone=(), camera=()");
});

Login Security

<?php
/**
 * Login security enhancements
 */

// Limit login attempts
add_filter('authenticate', function(?WP_User $user, string $username, string $password): WP_User|WP_Error|null {
    if (empty($username) || empty($password)) {
        return $user;
    }

    $ip = $_SERVER['REMOTE_ADDR'];
    $lockout_key = 'login_attempts_' . md5($ip);
    $attempts = (int) get_transient($lockout_key);

    if ($attempts >= 5) {
        return new WP_Error(
            'too_many_attempts',
            sprintf(
                __('Too many failed login attempts. Please try again in %d minutes.', 'my-plugin'),
                15
            )
        );
    }

    return $user;
}, 20, 3);

// Track failed attempts
add_action('wp_login_failed', function(string $username): void {
    $ip = $_SERVER['REMOTE_ADDR'];
    $lockout_key = 'login_attempts_' . md5($ip);
    $attempts = (int) get_transient($lockout_key);

    set_transient($lockout_key, $attempts + 1, 15 * MINUTE_IN_SECONDS);
});

// Clear on successful login
add_action('wp_login', function(string $username): void {
    $ip = $_SERVER['REMOTE_ADDR'];
    $lockout_key = 'login_attempts_' . md5($ip);
    delete_transient($lockout_key);
});

// Disable XML-RPC if not needed
add_filter('xmlrpc_enabled', '__return_false');

// Hide login errors (don't reveal if username exists)
add_filter('login_errors', function(): string {
    return __('Invalid login credentials.', 'my-plugin');
});

// Force strong passwords
add_action('user_profile_update_errors', function(WP_Error $errors, bool $update, object $user): void {
    $password = $_POST['pass1'] ?? '';

    if (!empty($password)) {
        // Require minimum 12 characters
        if (strlen($password) < 12) {
            $errors->add('weak_password', __('Password must be at least 12 characters.', 'my-plugin'));
        }

        // Require mixed case, numbers, special chars
        if (!preg_match('/[A-Z]/', $password) ||
            !preg_match('/[a-z]/', $password) ||
            !preg_match('/[0-9]/', $password) ||
            !preg_match('/[^A-Za-z0-9]/', $password)) {
            $errors->add('weak_password', __('Password must contain uppercase, lowercase, numbers, and special characters.', 'my-plugin'));
        }
    }
}, 10, 3);

Backup Strategy

<?php
/**
 * Backup implementation
 */

class Database_Backup {

    private string $backup_dir;

    public function __construct() {
        $upload_dir = wp_upload_dir();
        $this->backup_dir = $upload_dir['basedir'] . '/backups/';

        if (!file_exists($this->backup_dir)) {
            wp_mkdir_p($this->backup_dir);

            // Protect directory
            file_put_contents($this->backup_dir . '.htaccess', 'deny from all');
            file_put_contents($this->backup_dir . 'index.php', '<?php // Silence is golden');
        }
    }

    /**
     * Create database backup
     */
    public function create_backup(): string|WP_Error {
        global $wpdb;

        $filename = 'db-backup-' . date('Y-m-d-His') . '.sql';
        $filepath = $this->backup_dir . $filename;

        $tables = $wpdb->get_col('SHOW TABLES');
        $output = "-- WordPress Database Backup\n";
        $output .= "-- Generated: " . date('Y-m-d H:i:s') . "\n\n";

        foreach ($tables as $table) {
            // Skip non-WordPress tables
            if (strpos($table, $wpdb->prefix) !== 0) {
                continue;
            }

            $output .= "DROP TABLE IF EXISTS `{$table}`;\n";

            $create = $wpdb->get_row("SHOW CREATE TABLE `{$table}`", ARRAY_N);
            $output .= $create[1] . ";\n\n";

            $rows = $wpdb->get_results("SELECT * FROM `{$table}`", ARRAY_A);

            foreach ($rows as $row) {
                $values = array_map(function($value) use ($wpdb) {
                    return $value === null ? 'NULL' : "'" . $wpdb->_real_escape($value) . "'";
                }, $row);

                $output .= "INSERT INTO `{$table}` VALUES (" . implode(',', $values) . ");\n";
            }

            $output .= "\n";
        }

        if (file_put_contents($filepath, $output) === false) {
            return new WP_Error('backup_failed', __('Failed to write backup file.', 'my-plugin'));
        }

        // Compress
        if (function_exists('gzencode')) {
            $compressed = gzencode($output, 9);
            file_put_contents($filepath . '.gz', $compressed);
            unlink($filepath);
            $filepath .= '.gz';
        }

        return $filepath;
    }

    /**
     * Clean old backups
     */
    public function cleanup_old_backups(int $keep_days = 30): int {
        $deleted = 0;
        $files = glob($this->backup_dir . '*.sql*');
        $cutoff = time() - ($keep_days * DAY_IN_SECONDS);

        foreach ($files as $file) {
            if (filemtime($file) < $cutoff) {
                unlink($file);
                $deleted++;
            }
        }

        return $deleted;
    }
}

// Schedule automatic backups
add_action('init', function(): void {
    if (!wp_next_scheduled('my_plugin_daily_backup')) {
        wp_schedule_event(time(), 'daily', 'my_plugin_daily_backup');
    }
});

add_action('my_plugin_daily_backup', function(): void {
    $backup = new Database_Backup();
    $result = $backup->create_backup();

    if (!is_wp_error($result)) {
        $backup->cleanup_old_backups(7);
    }
});

Best Practices Summary

Performance

  • Use object caching (Redis/Memcached)
  • Implement transients for expensive operations
  • Optimize database queries with proper indexes
  • Lazy load images and defer non-critical scripts
  • Use no_found_rows when pagination not needed
  • Clean up revisions and transients regularly

Security

  • Sanitize ALL user input
  • Escape ALL output
  • Use nonces for all form submissions and AJAX
  • Verify capabilities before any action
  • Use prepared statements for database queries
  • Validate file uploads thoroughly
  • Implement rate limiting for login
  • Add security headers
  • Keep WordPress and plugins updated

Source: SKILL.md on GitHub

2 alerts16d5 checks · Risk CRITICAL
  • Gen Agent Trust Hub16d

    This skill is flagged as malicious due to confirmed detections by security scanners. The primary configuration file (SKILL.md) is identified as malware, and the associated documentation link is currently blacklisted as a malicious URL.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    3/6 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at efebc44. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 5 months ago
Other metadata
metadata
{
  "author": "https://github.com/Jeffallan",
  "version": "1.1.0",
  "domain": "platform",
  "triggers": "WordPress, WooCommerce, Gutenberg, WordPress theme, WordPress plugin, custom blocks, ACF, WordPress REST API, hooks, filters, WordPress performance, WordPress security",
  "role": "expert",
  "scope": "implementation",
  "output-format": "code",
  "related-skills": "php-pro, laravel-specialist, fullstack-guardian, security-reviewer"
}
  • Security
  • Performance
  • wordpress
  • php
  • gutenberg
  • woocommerce
  • theme-development
  • plugin-development
  • rest-api
  • acf

README badge

README badge for jeffallan/claude-skills/wordpress-pro

Develops custom WordPress themes, plugins, Gutenberg blocks, and WooCommerce stores with security hardening (nonces, sanitization, escaping, capability checks) and performance optimization. Use when building WordPress features, extending with the REST API, applying hooks and filters, or hardening against common vulnerabilities.

Generated from the current SKILL.md.

Does this skill cover WooCommerce customization?
Yes. The skill includes configuring WooCommerce stores, extending product/order functionality, and applying hooks and filters specific to WooCommerce workflows.
What PHP and WordPress versions does this target?
WordPress 6.4+ and PHP 8.1+. The skill enforces modern standards and will not generate code using deprecated functions.
Does this skill handle Gutenberg block development?
Yes. It covers creating and registering custom Gutenberg blocks, block patterns, and full-site editing (FSE) workflows.
Will this skill validate code against WordPress standards?
The skill references phpcs with the WordPress Coding Standard (WPCS) for validation and expects you to run checks manually, but does not automatically lint generated code.
Does this cover ACF integration?
Yes. Working with Advanced Custom Fields (ACF) is listed as a supported use case, including field registration and retrieval patterns.

Generated from the current SKILL.md. These answers refresh after source changes.