Multiple Custom Metabox Help

时间:2010-11-15 作者:matt

一年多以来,我一直在努力解决这个问题,尝试了很多方法,但都没有成功。如果有人能帮我,我将不胜感激。这就是我想做的。。。

我需要在我的自定义帖子类型上为幻灯片(lom\\U slideshow)的写入屏幕创建15个自定义元框。除了幻灯片数量(例如,幻灯片1、幻灯片2…)外,每个元盒都是相同的。我希望元数据库将每个字段保存到单独的自定义字段中。

以下是每个元数据库中需要包含的字段:

  • Number/Text Field <标题:幻灯片编号自定义字段键:幻灯片编号Checkbox <标题:隐藏此幻灯片自定义字段键:幻灯片1隐藏Radio toggle <标题:幻灯片类型自定义字段键:幻灯片1幻灯片类型值:图像幻灯片或视频幻灯片Text Field <标题:幻灯片标题自定义字段键:幻灯片1标题Image (这一个可以通过多种方式工作,我真的不在乎它是如何工作的,只要它能做到这一点)标题:幻灯片图像自定义字段键:幻灯片1图像如何工作:只要它能做以下事情之一,我就会很高兴
  • A) 与帖子关联的所有图像附件的下拉列表
  • B) 与帖子关联的所有附加图像的缩略图列表
  • C) 图像上载字段,用于上载图像并将其附加到帖子
  • WYSIWYG<标题:幻灯片说明自定义字段键:幻灯片说明类型:所见即所得Textarea <标题:视频嵌入代码,自定义字段键:幻灯片1嵌入,幻灯片2将相同,但所有自定义字段值将附加“slide2-”而不是“slide1-”

    唯一需要考虑的是,我希望它们出现在主内容列中,并且只在我的幻灯片自定义帖子类型(lom\\u Slideshow)上可见。

    就像我说的,我已经试了很多次了,但都没有成功。我在过去已经很接近了,除了我无法在页面上显示所见即所得的多个实例外,整个过程都正常进行。我不再有那个代码了,因为从那以后我已经更改了很多次了。我最终决定使用More Fields插件来完成这项工作,而不是使用我自己的代码,并用5个元框对其进行了测试以进行开发,但当我试图用15个框来实现它时,我需要的插件对我的数据库表造成了严重破坏。我真的不想要其他插件。

    实际上,我在找人告诉我如何编写代码。我不需要教程的链接,因为我相信我已经尝试了所有教程,除非它是在最后一个小时左右发布的。

    以下是我最初尝试的背景:Help Creating a Slideshow Custom Post Type with Custom Meta Boxes?

    伊曼给了我一个非常全面的答案,但这超出了我的技能范围,我无法实现。我已经把我的要求简化为这个问题,希望最终能解决这个问题。

  • 2 个回复
    最合适的回答,由SO网友:hakre 整理而成

    Solve a complex Problem (rationally)

    To solve complex problems, there is a kind of standardized/well known approach: Divide your complex problem into a set of smaller problems. Smaller problems are easier to solve. If you have solved each smaller problem you most often have solved your complex problem already.

    Locate and Divide into Parts

    So far for the theory. Let\'s check your needs. I line-up in my own words what you described above:

    • Handle a set of options in multiple instances (slide-1 to slide-N).
    • Store values in post meta values.
    • A Metabox in multiple instances (slide-1 to slide-N).
    • A Slideshow Editor (Form) within the Metabox (again multiple Instances)
    • A Basic Form to edit the Data (e.g. using simple HTML Form Elements)
    • The technical problem to solve, having multiple WYSIWYG editors.
    • Your more complex Image Input.

    So how to code that? I think the most important part is that before you start to actual coding, you make up your mind what you really want to achieve and how to divide the problem into the smaller parts. The list above might not be complete it\'s just what I could read from your question. And the format is quite unspecific. It\'s just more or less a repetition from what you wrote but a bit ordered into single points.

    So check if those are your needs and extend that list to all you see fit.

    When done, the next step is to look how those needed stuff could be expressed in simple words and as a list of features of your plugin

    1. A Plugin for this: SlideshowPlugin.
    2. A Slideshow that consists of Slides.
    3. An Editor to edit a Slideshow.
    4. A place to store and retrieve the Slideshow data from.

    Looks quite simple now, right? I probably might have missed something here, so double check on your own before you continue. As written, before starting to code just make up your mind in simple wording and terms. Don\'t even think about which parts are problematic and which are not or how to code some detail like the naming of HTML Input elements. I know that\'s hard if you already tried for such a long time now to restart from scratch because there are many ideas in your mind\'s back that come up again.

    Grab a pencil and some paper. This often helps to make up someone\'s mind.

    As you can see I did not specify the need of a Metabox or a Custom Post Type here. It\'s too specific to learn about the parts of your problem first. Metabox or Custom Post Type is very concrete, possibly something how to code the plugin already. So I kept this out for the moment and tried to short but precisely describe the needs. The Metabox or similar is something which might play a role in the Design. Let\'s see.

    Design your Plugin

    After you know what you need/want to achieve, you can make up the mind about how to design the plugin. This could be done by drawing a little picture. Just Identify the parts from your Feature List and set them in relation to each other. As you can see, you actually do not need to do fine arts ;) :

    design of a slideshow plugin

    Excuse my bad writing, I hope this can be read. In any case, you can create your own image, this is just an example. Designs can vary, so if you would not draw it the same way, that\'s just normal.

    I like to do the beginning of the design step on paper because it helps to get a better view on the problem from above and it\'s much faster on paper then on the computer.

    So now you could compare your list with your design and check if all features from the list are covered by the parts you have in the design. Looks good so far for my list and my image, so I continue, but don\'t skip this step. Otherwise you do not know if you have missed something. And as you start to code soon, it\'s much more hard to change something that\'s already coded than an image or a list.

    Separation of Problems in the Design

    Now this plugin becomes more concrete in ones mind. After some little design, it\'s probably the right time to start coding. As we have the list on top, I could go through and think about each point on it\'s own and cross-check with the Design so that I know how the parts are in relationship to each other.

    Because if every single part is done, the plugin is ready without making up the mind on the whole thing at once, which was the original problem.

    I do the coding now a bit in comment style and some sample code. It\'s a first implementation idea and the code is untested. It\'s just to get the hands dirty for me and for you to probably have an example of how - but not must - this can be written. I tend to be too specific sometimes already, so mind me. When I write code, I re-write it quite often while creating it, but I can not make this visible while creating the sample code. So bear this in mind. If you see something to be done simpler, choose your route. You need to change and extend your code later on, so this is really only some example code.

    Plugin

    Just a class that handles the basic operation like registering hooks and providing the Slideshow with it\'s Slides and the Metaboxes for the Editor. This is where everything starts. Plugins are started at a single point of code. I call that bootstrap:

    <?php
    /** Plugin Headers ... */
    
    return SlideshowPlugin::bootstrap(); 
    
    class SlideshowPlugin {
        /** @var Slideshow */
        private $slideshow;
        /** @var SlideshowMetaboxes */
        private $metaboxes;
        /** @var SlideshowPlugin */
        static $instance;
        static public function bootstrap() {
            $pluginNeeded = (is_admin() && /* more checks based your needs */ );
            if (!$pluginNeeded) 
                return;
            }
            if (NULL === self::$instance) {
                // only load the plugin code while it\'s really needed:
                require_once(\'library/Slideshow.php\');
                require_once(\'library/SlideshowSlide.php\');
                require_once(\'library/Store.php\');
                require_once(\'library/Metaboxes.php\');
                require_once(\'library/Metabox.php\');
                require_once(\'library/Form.php\');
                // ...
                self::$instance = new SlideshowPlugin();
            }
            return self::$instance;
        }
        private function addAction($action, $parameters = 0, $priority = 10) {
            $callback = array($this, $action);
            if (!is_callable($callback)) {
                throw new InvalidArgumentExeception(sprintf(\'Plugin %s does not supports the %s action.\', __CLASS__, $action));
            }
            add_action($action, $callback, $parameters, $priority);
        }
        public function __construct() {
            $this->addAction(\'admin_init\');
        }
        /**
         * @return bool
         */
        private function isEditorRequest() {
            // return if or not the request is the editor page
        }
        /**
         * @-wp-hook
         */
        public function admin_init() {
            // register anything based on custom post type and location in the admin.
            // I don\'t care about the details with CPT right now.
            // It\'s just that editorAction is called when we\'re on the slideshow
            // editor page:
            if ($this->isEditorRequest()) {
                $this->editorAction();
            }
        }
        private function getPostID() {
            // ... code goes here to get post id for request 
            return $postID;
        }
        private function getSlideshow() {
            is_null($this->slideshow)
                && ($postID = $this->getPostID())
                && $slideshow = new Slideshow($postID)
                ;
            return $slideshow;
        }
        private function getMetaboxes() {
            is_null($this->metaboxes) 
                && ($slideshow = $this->getSlideshow())
                && $this->metaboxes = new SlideshowMetaboxes($slideshow)
                ;
            return $this->metaboxes;
        }
        private function editorAction() {
            $metaboxes = $this->getMetaboxes();
        }
    }
    

    So this plugin class is already quite complete. I did not specify how to retrieve the postID but it\'s already encapsulated in a function on it\'s own. Next to that I did not code to check whether or not this is the right page to display the editor, but there\'s already some stub code for that.

    In the end, the editorAction() is called if the request is on the actual custom post type editor page and in there, the Metaboxes are aquired. That\'s it. Plugin should be pretty complete now. It has the slideshow and takes care of the Metaboxes. Compared with the design, those are the parts which are linked with the plugin. Compare with the image:

    1. The decision whether or not the Editor is to be displayed.
    2. The connection between the plugin and the slideshow. The plugin has a slideshow already.
    3. The connection to the Metaboxes. The plugin has the Metaboxes already.

    Looks complete. Job done on that part.

    Slideshow and Slides

    A Slideshow is 1:1 mapped to a post. So it needs to have the Post ID. The Slideshow can take care of holding the data, so it\'s basically a Datatype. It stores all the values you need there. It\'s a compound datatype in the sense that it consist have 0 to N slides. A Slide again is another Datatype that holds the information for each Slide.

    The slide then is used by a metabox and probably a form.

    I additionally choosed to implement the storage and retrieval of the slideshow data as well into those datatypes (the box labeled Store in the design). That\'s somehow dirty as it mixes datatypes and actual objects.

    But as the Store is connected to Slideshow and Slides only in the Design I connected them with each other. Probably too close for the future, but for a first implementation of the design, I think it\'s a valid approach for the moment. As this is the first approach it won\'t take much time after it will get refactored anyway so even with some issues I\'m quite confident that the direction is right:

    class SlideshowSlide {
        private $slideshow;
        private $index;
        public $number, $hide, $type, $title, $image, $wysiwyg, $embed
        public function __construct($slideshow, $index) {
            $this->slideshow = $slideshow;
            $this->index = $index;
        }
        public function getSlideshow() { return $this->slideshow; }
        public function getIndex() { return $this->index; }
    }
    
    class Slideshow implements Countable, OuterIterator {
        private $postID;
        private $slides = array();
        private function loadSlidesCount() {
            $postID = $this->postID;
            // implement the loading of the count of slides here
        }
        private function loadSlide($index) {
            $postID = $this->postID;
            // implement the loading of slide data here
            $data = ... ;
            $slide = new SlideshowSlide($this, $index);
            $slide->setData($data); // however this is done.
            return $slide;            
        }
        private function loadSlides() {
            $count = $this->loadSlidesCount();
            $slides = array();
            $index = 0;
            while(($index < $count) && ($slide = $this->loadSlide($index++)))
                FALSE === $slide || $slides[] = $slide
                ;
            $this->slides = $slides;
        }
        public function __construct($postID) {
            $this->postID = $postID;
            $this->loadSlides();
        }
        public function count() {
            return count($this->slides);
        }
        public function getInnerIterator() {
            return new ArrayIterator($this->slides);
        }
        private function touchIndex($index) {
            $index = (int) $index;
            if ($index < 0 || $index >= count($this->slides) {
                throw new InvalidArgumentExpression(sprintf(\'Invalid index %d.\', $index));
            }
            return $index;
        }
        public function getSlide($index) {
            $index = $this->touchIndex($index);
            return $this->slides[$index];
        }
    }
    

    The Slideshow and Slide classes are also quite complete but lacks actual code as well. It\'s just to show my idea of having the properties / methods and some handling stuff as well on how the retrieval of data could be implemented.

    Metabox

    The Metabox needs to know which Slide it represents. So it needs to know the Slideshow and the concrete Slide. The Slideshow can be provided by the Plugin, the Slide could be represented by the index (e.g. 0 ... N where N is count of slides in the slideshow - 1).

    class Metabox {
        public function __construct(SlideshowSlide $slide) {
        }
    }
    

    The Metabox class is actually extending the plugin class somehow. It does some of the work that could be done by the plugin class as well, but as I wanted to have it represent the slide in context of the plugin while being able to have multiple instances, I choosed this way.

    The Metabox now needs to take care of the request logic: It represents one Metabox which is actually somehow output but it\'s also input as it needs to deal with form input.

    The good thing is, it actually does not deal with the details because form output and input are done by the form objects.

    So probably if I would have coded this class to an end, I would have removed it completely. I don\'t know right now. For the moment it represents the Metabox on the editor page for one specific slide.

    Metaboxes

    class Metaboxes
        private $slideshow;
        private $boxes;
        public function __construct(Slideshow $slideshow) {
            $this->slideshow = $slideshow;
            $this->editorAction();
        }
        private function createMetaboxes() {
            $slides = $this->slideshow;
            $boxes = array();
            foreach($slides as $slide) {
                $boxes[] = new Metabox($slide);
            }
            $this->boxes = $boxes;
        }
        private function editorAction() {
            $this->createMetaboxes();
        }
    

    I only wrote some little code here so far. The Metaboxes class acts as a manager for all metaboxes. The representative of the Slideshow as Metabox represents a slide.

    That stub code does not much but instantiating one Metabox per Slide. It can and must do more in the end.

    You probably might want to make use the of the Composite Pattern here, so to do an action on a Metaboxes object will do the same action on every Metabox it carries. Comparable with the instantiation, where it creates new Metaboxes. So you don\'t need to deal with individual Metaboxes later on, just with the Metaboxes object. Just an Idea.

    Form

    The Form is probably the most complex thing you have in terms of dealing with stuff and lines of code. It needs to abstract your data to be processed via the Browser. So it must be able to handle multiple instances. You can achieve this by prefixing form element names (as they need to be unique) with a genreal prefix (e.g. "slide"), then an identifier (the slide index, I name it index here as you want to be able to change the number e.g. to have a sort key) and then the actual value identifier (e.g. "number" or "hide").

    Let\'s see: A form knows about it\'s prefix, the slide\'s number and all fields it contains. This pretty much maps to the Slideshow and Slide datatypes spoken about above. Some little stub-code:

    /**
     * SlideForm
     *
     * Draw a Form based on Slide Data and a Form definition. Process it\'s Input.
     */
    class SlideForm {
        /** @var Slide */
        private $slide;
        private $prefix = \'slide\';
        public function __construct(Slide $slide) {
           $this->slide = $slide;
        }
        private function inputNamePrefix() {
           $index = $this->slide->getIndex();
           $prefix = $this->prefix;
           return sprintf(\'%s-%d-\', $prefix, $index);
        }
        private function inputName($name) {
           return $this->inputNamePrefix().$name;
        }
        private function printInput(array $element) {
            list($type, $parameters) = $element;
            $function = \'printInput\'.$type;
            $callback = array($this, $function)
            call_user_func_array($callback, $parameters);
        }
        private function printInputText($value, $name, $label, $size = 4) {
            $inputName = $this->inputName($name);
            ?>
            <label for="<?php echo $inputName ; ?>">
              <?php echo htmlspecialchars($label); ?>:
            </label>
            <input type="text" 
              name="<?php echo $inputName; ?>"
              size="<?php echo $size; ?>"  
              value="<?php echo htmlspecialchars($value); ?>">
            <?php
        }
        private function printInputCheckbox($value, $name, $label, ... ) { ... }
        private function printInputRadio($value, $name, $label, ... ) { ... }
        private function printInputImage($value, $name, $label, ... ) { ... }
        private function printInputWysiwyg($value, $name, $label, ... ) { ... }
        private function printInputTextarea($value, $name, $label, ... ) { ... }
        // ...
        private function mapSlideValueTo(array $element) {
            $slide = $this->slide;
            list($type, $parameters) = $element;
            list($name) = $parameters;
            $value = $slide->$name;
            array_unshift($parameters, $value);
            $element[1] = $parameters;
            return $element;
        }
        /**
         * @return array form definition
         */
        private function getForm() {
            // Form Definition
            $form = array(
               array(
                   \'Text\', 
                   array(
                       \'number\', \'Number\'
                   ),
               array(
                   \'Checkbox\', 
                   array(
                       \'hide\', \'Display\', \'Hide This Slide\'
                   ),
               ),
               array(
                   \'Radio\', 
                   array(
                       \'type\', \'Type\', array(\'Image\', \'Video\')
                   ),
               ),
               array(
                   \'Text\', 
                   array(
                       \'title\', \'Title\', 16
                   ),
               ),
               // ...
            );
            return $form;
        }
        public function printFormHtml() {
            $form = $this->getForm();
    
            foreach($form as $element) {
                $element = $this->mapSlideValueTo($element);
                $this->printInput($element);
            }
        }
        public function processFormElement($element) {
            list($type, $parameters) = $element;
            list($name) = $parameters;
            $inputName = $this->inputName($name);
    
            $map = array(
                \'Text\' => \'String\', 
                \'Checkbox\' => \'Checkbox\', 
                \'Radio\' => \'Radio\', 
            );
    
            // I would need to continue to code there.
            throw new Exception(\'Continue to code: Process Form Input based on Form Definition\');
        }
        public function processForm() {
            $form = $this->getForm();
    
            foreach($form as $element) {
                $this->processFormElement($element);  // <- this function needs to be coded
            }
        }
        // ...
    }
    

    This class is quite large now because it takes care of three things at once:

    1. Form Definition
    2. Form Output Rendering
    3. Form Input Processing

    It is wiser to split this up into the three parts it represents. I leave that up to you. It does already show how you can encapsulate the forms functionality into smaller tasks so it\'s easier to comply with your needs. E.g. in case the Image Input element form output needs tweaks, it can be easily extended / polished. Same for WYSIWYG. You can change the implementation later on as it won\'t interfere for you slideshow and slide datatypes much.

    The principle behind this is also called Separation of Concerns, and that is just about how I started my answer: Divide the problem into smaller problems. Those separated problems are easier to solve.

    I hope this helps for the moment. In the end I didn\'t come back to Custom Post Types even. I know they must go inside the plugin, but with a new design it\'s probably easy to find the place where to write the code to.

    What\'s left?

    • Split the code into multiple files. One class per file. You can merge them together later easily, but for development, keep things apart as you want to solve the smaller problems / parts on their own.
    • Testing: You need to test the functionality of the parts on their own. Is the slide doing what it should do? Gladly you can make use of Unittests if you write classes, for PHP there is PHPUnit. This helps you to develop the code while knowing that it does exactly what it does. This helps to solve the simple problems in a unit of each own and you can more easily change your code later on as you can run tests all the time.
    • Testing #2: Sure you need to test your plugin as a whole as well so you know that it is doing what you\'re looking for. Can you imagine how that can be done automatically so you can repeat the test with the same quality all the time?
    SO网友:Wyck

    我知道你说过你不想要另一个插件,但我建议你看看“Verve meta Box”或“Custom Field Templates”,后者有很多选项,但Verve有一个很好的拖放界面。

    至于您的datatabse问题,您能否详细说明为什么需要多个wysiwyg字段,以及为什么这会影响数据库?我在一些页面上使用了40多个自定义字段,没有发现任何问题。

    至于幻灯片,我也做了类似的事情,必须使用jquery和query\\u post类型编写自己的幻灯片。例如,这会在滑块中显示一个名为“movies”的自定义帖子类型的随机帖子。您可以使用get\\u post\\u meta添加任何自定义meta值。

    <?php $rand_posts = query_posts(\'post_type=movies&posts_per_page=5&orderby=rand\');
    foreach($rand_posts as $post) :
    ?>
    

    结束

    相关推荐

    我可以将类别Metabox添加到附件吗?

    我在用register_taxonomy_for_object_type() 将类别分类字段添加到媒体上载(附件)。我正在使用此代码执行此操作:add_action(\'init\', \'reg_tax\'); function reg_tax() { register_taxonomy_for_object_type(\'category\', \'attachment\'); } 这可以在查看图像时为媒体页面添加一个简单的分类文本字段。我真正想要的是让它显示实