post meta, term meta和theme option字段框架

PHP

<?php
/**
 * Field Framework for WordPress
 * Handles theme options, post meta, and term meta fields
 */

class WP_Field_Framework {
    private static $instance = null;
    private $fields = [];
    private $sections = [];
    private $options = [];
    private $page_slug = 'theme-options';
    private $option_group = 'theme_options';

     public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

     public function __construct() {
        add_action('admin_menu', [$this, 'add_options_page']);
        add_action('admin_init', [$this, 'register_settings']);
        add_action('add_meta_boxes', [$this, 'register_meta_boxes']);
        add_action('created_term', [$this, 'save_term_fields'], 10, 3);
        add_action('edit_term', [$this, 'save_term_fields'], 10, 3);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);

        add_action('save_post', [$this, 'save_post_meta']); // Add this line

        // Add term meta field actions
        add_action('admin_init', [$this, 'register_term_fields']);
    }

       /**
     * Enqueue scripts and styles
     */
    public function enqueue_scripts($hook) {
        // Only load on our settings page or term/post edit screens
        if ($hook === 'settings_page_' . $this->page_slug || 
            $hook === 'post.php' || 
            $hook === 'post-new.php' || 
            $hook === 'term.php' || 
            $hook === 'edit-tags.php') {
            
            wp_enqueue_media();
            wp_enqueue_style('wp-color-picker');
            wp_enqueue_script('wp-color-picker');
            
            // Add custom scripts
            wp_enqueue_script(
                'field-framework',
                get_stylesheet_directory_uri() . '/js/field-framework.js',
                ['jquery', 'wp-color-picker'],
                '1.0.0',
                true
            );
        }
    }
     /**
     * Add options page to admin menu
     */
    public function add_options_page() {
        add_options_page(
            'Theme Options',
            'Theme Options',
            'manage_options',
            $this->page_slug,
            [$this, 'render_options_page']
        );
    }

        /**
     * Add a new section
     */
    public function add_section($args) {
        $defaults = [
            'id' => '',
            'title' => '',
            'description' => '',
            'icon' => '', // Dashicons class
            'priority' => 10
        ];

        $section = wp_parse_args($args, $defaults);
        $this->sections[$section['id']] = $section;

        return $this;
    }

   /**
     * Add a new field
     */
    public function add_field($args) {
        $defaults = [
            'id' => '',
            'type' => 'text',
            'title' => '',
            'description' => '',
            'default' => '',
            'options' => [],
            'context' => 'theme_option',
            'post_type' => 'post',
            'taxonomy' => '',
            'section' => 'general' // Default section
        ];

        $field = wp_parse_args($args, $defaults);
        $this->fields[] = $field;

        return $this;
    }


       /**
     * Sanitize field value based on type
     */
    private function sanitize_field_value($value, $type) {
        switch ($type) {
            case 'text':
                return sanitize_text_field($value);
            case 'textarea':
                return sanitize_textarea_field($value);
            case 'checkbox':
                return ($value == '1') ? '1' : '0';
            case 'select':
                return sanitize_text_field($value);
            case 'color':
                return sanitize_hex_color($value);
            case 'image':
                return absint($value);
            default:
                return sanitize_text_field($value);
        }
    }

    /**
     * Register settings and sections
     */
    public function register_settings() {
        // Register setting
        register_setting(
            $this->option_group,
            $this->option_group,
            [$this, 'sanitize_options']
        );

        // Sort sections by priority
        uasort($this->sections, function($a, $b) {
            return $a['priority'] - $b['priority'];
        });

        // If no sections defined, add default
        if (empty($this->sections)) {
            $this->sections['general'] = [
                'id' => 'general',
                'title' => 'General Settings',
                'description' => '',
                'priority' => 10
            ];
        }

        // Get current tab
        $current_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : array_key_first($this->sections);

        // Add only the current section
        if (isset($this->sections[$current_tab])) {
            add_settings_section(
                $current_tab,
                $this->sections[$current_tab]['title'],
                [$this, 'section_callback'],
                $this->page_slug
            );
        }

        // Add only fields for the current section
        foreach ($this->fields as $field) {
            if ($field['context'] === 'theme_option' && $field['section'] === $current_tab) {
                add_settings_field(
                    $field['id'],
                    $field['title'],
                    [$this, 'render_field'],
                    $this->page_slug,
                    $current_tab,
                    $field
                );
            }
        }
    }

        /**
     * Section callback
     */
    public function section_callback($args) {
        $section_id = $args['id'];
        if (isset($this->sections[$section_id]['description'])) {
            echo '<p class="description">' . esc_html($this->sections[$section_id]['description']) . '</p>';
        }
    }


      /**
     * Sanitize options
     */
    public function sanitize_options($options) {
        $sanitized = [];
        
        // Get existing options to preserve other tab values
        $existing_options = get_option($this->option_group, []);
        $sanitized = $existing_options;

        foreach ($this->fields as $field) {
            if (isset($options[$field['id']])) {
                $sanitized[$field['id']] = $this->sanitize_field_value($options[$field['id']], $field['type']);
            }
        }

        // Add redirect to current tab after save
        if (isset($options[$this->option_group . '_current_tab'])) {
            add_settings_error(
                'general',
                'settings_updated',
                __('Settings saved.'),
                'updated'
            );
            
            wp_redirect(add_query_arg([
                'page' => $this->page_slug,
                'tab' => $options[$this->option_group . '_current_tab']
            ], admin_url('options-general.php')));
            exit;
        }

        return $sanitized;
    }

 /**
     * Render options page
     */
    public function render_options_page() {
        if (!current_user_can('manage_options')) {
            wp_die(__('You do not have sufficient permissions to access this page.'));
        }

        // Get current tab
        $current_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : array_key_first($this->sections);
        
        // Store current tab in hidden field for post-save redirect
        ?>
        <div class="wrap">
            <h1><?php echo esc_html(get_admin_page_title()); ?></h1>

            <nav class="nav-tab-wrapper">
                <?php foreach ($this->sections as $section_id => $section) : ?>
                    <?php
                    $active_class = ($current_tab === $section_id) ? 'nav-tab-active' : '';
                    $icon = !empty($section['icon']) ? '<span class="dashicons ' . esc_attr($section['icon']) . '"></span> ' : '';
                    ?>
                    <a href="?page=<?php echo $this->page_slug; ?>&tab=<?php echo esc_attr($section_id); ?>" 
                       class="nav-tab <?php echo $active_class; ?>">
                        <?php echo wp_kses_post($icon . $section['title']); ?>
                    </a>
                <?php endforeach; ?>
            </nav>

            <form action="options.php" method="post">
                <input type="hidden" name="<?php echo $this->option_group; ?>_current_tab" value="<?php echo esc_attr($current_tab); ?>">
                <?php
                settings_fields($this->option_group);
                do_settings_sections($this->page_slug);
                submit_button('Save Settings');
                ?>
            </form>
        </div>

        <style>
            .nav-tab .dashicons {
                line-height: 1.35;
            }
            .form-table th {
                padding: 20px 10px;
            }
        </style>
        <?php
    }

    /**
     * Register meta boxes
     */
    public function register_meta_boxes() {
        foreach ($this->fields as $field) {
            if ($field['context'] === 'post_meta') {
                add_meta_box(
                    $field['id'],
                    $field['title'],
                    [$this, 'render_meta_box'],
                    $field['post_type'],
                    'normal',
                    'default',
                    ['field' => $field]
                );
            }
        }
    }


    /**
     * Get field value based on context
     */
    private function get_field_value($field, $object = null) {
        switch ($field['context']) {
            case 'theme_option':
                $options = get_option($this->option_group, []);
                return isset($options[$field['id']]) ? $options[$field['id']] : $field['default'];

            case 'post_meta':
                $post_id = $object instanceof WP_Post ? $object->ID : get_the_ID();
                return get_post_meta($post_id, $field['id'], true) ?: $field['default'];

            case 'term_meta':
                if ($object instanceof WP_Term) {
                    return get_term_meta($object->term_id, $field['id'], true) ?: $field['default'];
                }
                return $field['default'];

            default:
                return $field['default'];
        }
    }

    /**
     * Get field name based on context
     */
    private function get_field_name($field) {
        switch ($field['context']) {
            case 'theme_option':
                return $this->option_group . '[' . $field['id'] . ']';
            default:
                return $field['id'];
        }
    }

       /**
     * Render image field
     */
    private function render_image_field($id, $name, $value) {
        $image_url = wp_get_attachment_url($value);
        ?>
        <div class="image-field-wrapper">
            <input type="hidden" id="<?php echo $id; ?>" name="<?php echo $name; ?>" value="<?php echo esc_attr($value); ?>">
            <div class="image-preview" style="margin-bottom: 10px;">
                <?php if ($image_url) : ?>
                    <img src="<?php echo esc_url($image_url); ?>" style="max-width: 200px;">
                <?php endif; ?>
            </div>
            <button type="button" class="button select-image"><?php _e('Select Image'); ?></button>
            <button type="button" class="button remove-image" <?php echo empty($value) ? 'style="display:none;"' : ''; ?>>
                <?php _e('Remove Image'); ?>
            </button>
        </div>
        <?php
    }

    /**
     * Unified field renderer
     */
    public function render_field($field, $object = null) {
        // Get the field value based on context
        $value = $this->get_field_value($field, $object);
        
        // Setup field name and ID
        $id = esc_attr($field['id']);
        $name = $this->get_field_name($field);
        
        // Render the appropriate field type
        switch ($field['type']) {
            case 'text':
                printf(
                    '<input type="text" id="%s" name="%s" value="%s" class="regular-text">',
                    $id,
                    $name,
                    esc_attr($value)
                );
                break;

            case 'textarea':
                printf(
                    '<textarea id="%s" name="%s" rows="5" class="large-text">%s</textarea>',
                    $id,
                    $name,
                    esc_textarea($value)
                );
                break;

            case 'select':
                echo '<select id="' . $id . '" name="' . $name . '">';
                foreach ($field['options'] as $key => $label) {
                    printf(
                        '<option value="%s" %s>%s</option>',
                        esc_attr($key),
                        selected($value, $key, false),
                        esc_html($label)
                    );
                }
                echo '</select>';
                break;

            case 'checkbox':
                printf(
                    '<input type="checkbox" id="%s" name="%s" value="1" %s>',
                    $id,
                    $name,
                    checked($value, '1', false)
                );
                break;

            case 'color':
                printf(
                    '<input type="text" id="%s" name="%s" value="%s" class="color-picker">',
                    $id,
                    $name,
                    esc_attr($value)
                );
                break;

            case 'image':
                $this->render_image_field($id, $name, $value);
                break;
        }

        // Show description if exists
        if (!empty($field['description'])) {
            printf(
                '<p class="description">%s</p>',
                esc_html($field['description'])
            );
        }
    }

  


       /**
     * Render meta box
     */
    public function render_meta_box($post, $args) {
        $field = $args['args']['field'];
        $value = get_post_meta($post->ID, $field['id'], true);
        // Create nonce with consistent naming
        wp_nonce_field('save_' . $field['id'], $field['id'] . '_nonce');
        $this->render_field($field, $post);
    }


      /**
     * Save post meta
     */
    public function save_post_meta($post_id) {
        // Basic security checks
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }

        if (!current_user_can('edit_post', $post_id)) {
            return;
        }

        // Save each field
        foreach ($this->fields as $field) {
            if ($field['context'] === 'post_meta') {
                // Verify nonce
                $nonce_name = $field['id'] . '_nonce';
                if (!isset($_POST[$nonce_name]) || 
                    !wp_verify_nonce($_POST[$nonce_name], 'save_' . $field['id'])) {
                    continue;
                }

                // Handle checkbox fields specifically
                if ($field['type'] === 'checkbox') {
                    $value = isset($_POST[$field['id']]) ? '1' : '0';
                } else {
                    $value = isset($_POST[$field['id']]) ? $_POST[$field['id']] : '';
                }

                // Sanitize and save the value
                $sanitized_value = $this->sanitize_field_value($value, $field['type']);
                update_post_meta($post_id, $field['id'], $sanitized_value);
            }
        }
    }

     /**
     * Register term fields
     */
    public function register_term_fields() {
        foreach ($this->fields as $field) {
            if ($field['context'] === 'term_meta' && !empty($field['taxonomy'])) {
                // Add field to term add form
                add_action("{$field['taxonomy']}_add_form_fields", function() use ($field) {
                    // Wrap in div for add form styling
                    echo '<div class="form-field term-meta-wrap">';
                    echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
                    $this->render_field($field);
                    echo '</div>';
                });

                // Add field to term edit form
                add_action("{$field['taxonomy']}_edit_form_fields", function($term) use ($field) {
                    echo '<tr class="form-field term-meta-wrap">';
                    echo '<th scope="row">';
                    echo '<label for="' . esc_attr($field['id']) . '">' . esc_html($field['title']) . '</label>';
                    echo '</th>';
                    echo '<td>';
                    $this->render_field($field, $term);
                    echo '</td>';
                    echo '</tr>';
                });

                // Save term meta
                add_action("created_{$field['taxonomy']}", function($term_id) use ($field) {
                    $this->save_term_meta($term_id, $field);
                });
                add_action("edited_{$field['taxonomy']}", function($term_id) use ($field) {
                    $this->save_term_meta($term_id, $field);
                });
            }
        }
    }
    /**
     * Save term fields
     */
    public function save_term_fields($term_id, $tt_id, $taxonomy) {
        foreach ($this->fields as $field) {
            if ($field['context'] === 'term_meta' && $field['taxonomy'] === $taxonomy) {
                if (isset($_POST[$field['id']])) {
                    update_term_meta($term_id, $field['id'], sanitize_text_field($_POST[$field['id']]));
                }
            }
        }
    }

        /**
     * Save term meta
     */
    private function save_term_meta($term_id, $field) {
        if (isset($_POST[$field['id']])) {
            $value = $_POST[$field['id']];
            $sanitized_value = $this->sanitize_field_value($value, $field['type']);
            update_term_meta($term_id, $field['id'], $sanitized_value);
        }
    }

    /**
     * Get field value
     */
    public function get_value($field_id, $context = 'theme_option', $object_id = null) {
        switch ($context) {
            case 'theme_option':
                $options = get_option($this->option_group, []);
                return isset($options[$field_id]) ? $options[$field_id] : '';
            
            case 'post_meta':
                return get_post_meta($object_id, $field_id, true);
            
            case 'term_meta':
                return get_term_meta($object_id, $field_id, true);
            
            default:
                return null;
        }
    }


      /**
     * Get field by ID
     */
    private function get_field_by_id($field_id) {
        foreach ($this->fields as $field) {
            if ($field['id'] === $field_id) {
               return $field;
            }
        }
        return null;
    }
}

// Initialize field framework
function wp_field_framework() {
    return WP_Field_Framework::get_instance();
}

JAVASCRIPT

jQuery(document).ready(function($) {
    // Initialize color pickers
    $('.color-picker').wpColorPicker();

    // Image upload handling
    var frame;
    $('.select-image').on('click', function(e) {
        e.preventDefault();
        
        var button = $(this);
        var wrapper = button.closest('.image-field-wrapper');
        var input = wrapper.find('input[type="hidden"]');
        var preview = wrapper.find('.image-preview');
        
        // If frame exists, reuse it
        if (frame) {
            frame.open();
            return;
        }
        
        // Create WP media frame
        frame = wp.media({
            title: 'Select or Upload Image',
            button: {
                text: 'Use this image'
            },
            multiple: false
        });
        
        // Handle image selection
        frame.on('select', function() {
            var attachment = frame.state().get('selection').first().toJSON();
            input.val(attachment.id);
            preview.html('<img src="' + attachment.url + '" style="max-width: 200px;">');
            wrapper.find('.remove-image').show();
        });
        
        frame.open();
    });
    
    // Remove image
    $('.remove-image').on('click', function(e) {
        e.preventDefault();
        
        var button = $(this);
        var wrapper = button.closest('.image-field-wrapper');
        var input = wrapper.find('input[type="hidden"]');
        var preview = wrapper.find('.image-preview');
        
        input.val('');
        preview.empty();
        button.hide();
    });
});

用法

<?php 
// Initialize the framework
$fields = wp_field_framework();

// Add sections (groups)
$fields->add_section([
    'id' => 'header',
    'title' => 'Header Settings',
    'description' => 'Customize your site header',
    'icon' => 'dashicons-heading', // WordPress dashicons class
    'priority' => 10
]);

$fields->add_section([
    'id' => 'homepage',
    'title' => 'Homepage Settings',
    'description' => 'Customize your homepage layout',
    'icon' => 'dashicons-admin-home',
    'priority' => 20
]);

$fields->add_section([
    'id' => 'footer',
    'title' => 'Footer Settings',
    'description' => 'Customize your site footer',
    'icon' => 'dashicons-arrow-down',
    'priority' => 30
]);

// Add fields to specific sections
$fields->add_field([
    'id' => 'header_logo',
    'type' => 'image',
    'title' => 'Logo',
    'description' => 'Upload your site logo',
    'section' => 'header'
]);

$fields->add_field([
    'id' => 'homepage_hero_title',
    'type' => 'text',
    'title' => 'Hero Title',
    'description' => 'Enter your homepage hero title',
    'section' => 'homepage'
]);

$fields->add_field([
    'id' => 'footer_copyright',
    'type' => 'textarea',
    'title' => 'Copyright Text',
    'description' => 'Enter your footer copyright text',
    'section' => 'footer'
]);



$fields->add_field([
    'id' => 'footer_logo',
    'type' => 'image',
    'title' => 'category logo',
    'description' => 'Enter your footer copyright text',
    
    'context' => 'term_meta',
    'taxonomy'  => 'genre'
]);


$fields->add_field([
    'id' => 'footer_logo',
    'type' => 'image',
    'title' => 'post logo',
    'description' => 'Enter your footer copyright text',
    
    'context' => 'post_meta',
    'post_type'  => 'book'
]);
×
post meta, term meta和theme option字段框架

post meta, term meta和theme option字段框架

PHP <?php /** * F...