问题描述

我一直试图让这个工作达一年多,并尝试了许多方法,并没有得到它的工作。如果有人可以帮助我,我真的很感激。这是我要做的事情

我需要为我的自定义帖子类型为幻灯片 (lom_slideshow) 创建 15 个自定义 metaboxes 用于写入屏幕。除了滑块的数量 (例如幻灯片 1,幻灯片 2 …),每个 metabox 将是相同的。我想让 metaboxes 将每个字段保存到单独的自定义字段中。

以下是每个 metabox 中需要的字段:

  • 数字/文本字段

    • 标题:幻灯片编号

    • 自定义字段键:slide1-number

  • 复选框

    • 标题:隐藏此幻灯片

    • 自定义字段键:slide1-hide

  • 无线电切换

    • 标题:幻灯片类型

    • 自定义字段键:slide1-slidesetype

    • 值:图像幻灯片或视频幻灯片

  • 文本域

    • 标题:幻灯片标题

    • 自定义字段键:slide1-title

  • 图像 (这个可以工作几种方式,我不太在乎它如何工作,只要它)

    • 标题:幻灯片图片

    • 自定义字段键:slide1-image

    • 它将如何运作:只要它做以下一件事我会很开心…

    • A) 与该帖子相关联的所有图像附件的下拉列表。

    • B) 与该帖子相关联的所有附加图像的缩略图列表。

    • C) 图像上传字段上传图像并将其附加到帖子。

  • 所见即所得

    • 标题:幻灯片说明

    • 自定义字段键:slide1-desc

    • 类型:wysiwyg

  • 多行文本

    • 标题:视频嵌入代码

    • 自定义字段键:slide1-embed

幻灯片 2 将是一样的,但所有的自定义字段值将附加”slide2-“ 而不是”slide1-“

唯一的其他考虑是,我希望他们在主要内容列,只有在我的幻灯片自定义帖子类型 (lom_slideshow) 上可见。

就像我说的,我已经尝试了这么多时间,从来没有这样做。我过去已经很近了,并且已经得到了整个事情的工作,除非我看不到所见即所得的多个实例显示在页面上。我不再有代码,因为我从那以后改变了很多次。我最终决定使用更多字段插件来做这个而不是我自己的代码,并用 5 个元框进行开发测试,但是当我尝试使用 15 个框来实现它时,我需要插件在我的数据库表上造成严重破坏。我真的不想要另外一个插件。

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

对于这里的一些背景,我原本想做的是:Help Creating a Slideshow Custom Post Type with Custom Meta Boxes?

EAMann 为我提供了一个非常全面的答案,但这是我的技能之外,我无法实现。我已经简化了我在这个问题上的请求,希望能够最终完成这个。

最佳解决方案

解决一个复杂的问题 (理性地)

为了解决复杂的问题,有一种标准化/众所周知的方法:将你的复杂问题分解成一套较小的问题。较小的问题更容易解决。如果您已经解决了每个较小的问题,那么您最经常已经解决了您的复杂问题。

找到并分割成零件

到目前为止的理论。我们来检查你的需要。我用自己的话说 line-up 你上面描述的内容:

  • 在多个实例 (Slide-1 到 slide-N) 中处理一组选项。

  • 在后期元值中存储值。

  • 多个实例中的 Metabox(Slide-1 至 slide-N) 。

  • Metabox 内的幻灯片编辑器 (Form)(再次是多个实例)

  • 编辑数据的基本格式 (例如使用简单的 HTML 表单元素)

  • 要解决的技术问题,有多个 WYSIWYG 编辑。

  • 您更复杂的图像输入。

那么怎么编码呢?我认为最重要的部分是,在开始实际编码之前,你会想起你真正想要实现的内容,以及如何将问题分成较小的部分。上面的列表可能不完整,这只是我从你的问题中可以看到的。而格式是非常特别的。它或多或少是从你写的重复,但有点订单到单点。

所以检查这些是否是您的需求,并将该列表扩展到您认为合适的所有内容。

完成后,下一步是看看这些需要的东西可以用简单的单词和插件的功能列表来表达

  1. 一个插件:SlideshowPlugin 。

  2. 幻灯片由幻灯片组成。

  3. 编辑幻灯片的编辑器。

  4. 存储和检索幻灯片数据的地方。

看起来很简单,对吧?我可能会错过这里的东西,所以在继续之前先仔细检查一下。正如写的,开始编写之前,只需用简单的措辞和术语来弥补你的想法。甚至不要考虑哪些部分是有问题的,哪些部分不是或如何编写一些细节,如 HTML Input 元素的命名。我知道如果你已经尝试了很长时间,从头开始重新启动,这是很难的,因为你脑海里有许多想法再次出现。

拿铅笔和一些纸。这通常有助于弥补某人的心灵。

你可以看到我没有在这里指定 Metabox 或 Custom Post Type 的需要。首先了解您的问题的部分太具体了。 Metabox 或 Custom Post Type 非常具体,可能是如何编写插件的。所以我暂时停下来,试图缩短但正确地描述需求。 Metabox 或类似的东西可能在设计中发挥作用。让我们来看看。

设计你的插件

在你知道你需要/想要实现的之后,你可以想出如何设计这个插件。这可以通过绘制一点图片来完成。只需从功能列表中识别零件,并将它们相互关联。你可以看到,实际上不需要做美术;):

不好意思,我希望这可以读。无论如何,您可以创建自己的图像,这只是一个例子。设计可以有所不同,所以如果你不会以同样的方式绘制,那就是正常的。

我喜欢在纸上开始设计步骤,因为它有助于从上面更好地了解问题,并且在电脑上的纸张上快得多。

因此,现在您可以将您的列表与您的设计进行比较,并检查列表中的所有功能是否涵盖在设计中的部件。看起来好像我的清单和我的形象,所以我继续,但不要跳过这一步。否则你不知道你是否错过了一些东西。随着你开始编写代码,改变一些已经被编码的东西比图像或列表要困难得多。

分离设计中的问题

现在这个插件变得更具体了。经过一些小的设计,可能是开始编码的正确时机。由于我们有上面的列表,我可以通过设计了解每一个点,并且设计 cross-check,以便我知道这些零件是如何相互关联的。

因为如果每一个部分都完成,那么插件就可以准备就绪,不需要立即就整个事情进行整理,这是原来的问题。

我现在做的编码有点评论风格和一些示例代码。这是第一个实现方案,代码未经测试。这只是为了让我的手变脏,为了你可能有一个例子,可以写下如何 – 但不是必须的 – 。有时候,我往往太具体了,请记住我。当我编写代码时,re-write 在创建时很常见,但是在创建示例代码时我无法使其显示。所以记住这一点。如果您看到要做的更简单,请选择您的路线。您需要稍后更改和扩展代码,所以这只是一些示例代码。

Plugin

只需一个类来处理基本操作,例如注册钩子,并为幻灯片提供幻灯片和编辑器的 Metaboxes 。这是一切开始的地方。插件以单个代码开始。我称之为 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();
    }
}

所以这个插件类已经很完整了。我没有指定如何检索 postID,但它已经被封装在它自己的函数中。旁边,我没有代码来检查这是否是显示编辑器的正确页面,但是已经有一些存根代码。

最后,如果请求是在实际的自定义帖子类型编辑器页面上,则会调用 editorAction(),在那里,Metaboxes 已经被收录。而已。插件现在应该很完整。它有幻灯片和照顾 Metaboxes 。与设计相比,这些是与插件相关的部分。与图像比较:

  1. 决定是否显示编辑器。

  2. 插件和幻灯片之间的连接。该插件已经有幻灯片放映。

  3. 连接到 Metaboxes 。该插件已经有了 Metaboxes 。

看起来完整在这方面做的工作。

幻灯片和幻灯片

幻灯片映射为 1:1 映射到一个帖子。所以它需要有邮政编号。幻灯片可以保存数据,所以它基本上是一个数据类型。它存储您需要的所有值。它是一个复合数据类型,它包含 0 到 N 个幻灯片。另一个幻灯片是另一个数据类型,其中包含每个幻灯片的信息。

幻灯片然后由 metabox 使用,可能是一种形式。

我还选择实现幻灯片数据的存储和检索以及这些数据类型 (设计中标记为 「存储」) 的框。这是混乱的数据类型和实际的对象。

但是当商店连接到幻灯片和幻灯片只在设计我连接他们彼此。对于未来可能太近了,但是为了首次实施设计,我认为这是一个有效的方法。由于这是第一种方法,因此即使有一些问题,我也相信这个方向是正确的:

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];
    }
}

幻灯片和幻灯片课程也相当完整,但也缺少实际的代码。这只是为了展示我的想法,即拥有属性/方法和一些处理内容以及如何实现数据检索。

Metabox

Metabox 需要知道它代表哪个幻灯片。所以它需要知道幻灯片和具体幻灯片。幻灯片可以由插件提供,幻灯片可以由索引 (例如 0 … N 表示,其中 N 是幻灯片中的幻灯片数量-1) 。

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

Metabox 类实际上是扩展插件类。它也可以通过插件类完成一些工作,但是当我想要在插件的上下文中可以显示幻灯片的同时可以有多个实例时,我选择了这种方式。

Metabox 现在需要处理请求逻辑:它代表一个实际上以某种方式输出的 Metabox,但是它也是输入,因为它需要处理表单输入。

好的是,它实际上并没有处理细节,因为表单输出和输入都是由表单对象完成的。

所以可能如果我将这个类编码到最后,我会完全删除它。我现在不知道目前,它代表了 Metabox 在编辑器页面上的一个特定幻灯片。

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();
    }

到目前为止,我只写了一些小代码。 Metaboxes 课堂是所有 metaboxes 的经理。幻灯片的代表 Metabox 代表幻灯片。

该存根代码并不多,但实例化一个 Metabox 每个幻灯片。它可以而且必须做更多的到最后。

您可能想在这里使用 Composite Pattern,因此对 Metaboxes 对象执行操作将对每个 Metabox 执行相同的操作。与实例化相比,它创建了新的 Metaboxes 。所以你以后不需要处理个别的 Metaboxes,只需要与 Metaboxes 对象。只是一个想法

Form

该表格可能是您处理代码和代码行最复杂的事情。它需要抽象您的数据以通过浏览器进行处理。所以它必须能够处理多个实例。您可以通过使用 genreal 前缀 (例如”slide”) 将表单元素名称 (如需要唯一) 前缀,然后是标识符 (幻灯片索引,我将此索引命名为您想要更改数字,例如具有排序键),然后是实际值标识符 (例如”number” 或”hide”) 。

我们来看看:一个表单知道它的前缀,幻灯片的数字和它包含的所有字段。这几乎映射到上面介绍的幻灯片和幻灯片数据类型。有些小 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
        }
    }
    // ...
}

这个课程现在很大,因为它一次处理三件事情:

  1. 表格定义

  2. 表单输出渲染

  3. 表单输入处理

将它分解成它代表的三个部分是更明智的。我离开了你。它已经显示了如何将表单功能封装到较小的任务中,以便更容易地满足您的需求。例如。如果 Image Input 元素输出需要调整,则可以轻松地进行扩展/抛光。所见即所得您可以稍后更改实现,因为它不会影响您的幻灯片和幻灯片数据类型。

这个背后的原则也被称为分离问题,那就是我如何开始我的答案:将问题分解成更小的问题。这些分离的问题更容易解决。

我希望这个时刻有所帮助。最后我甚至没有回到自定义帖子类型。我知道他们必须进入插件,但使用新的设计,很容易找到写入代码的地方。

还剩下什么?

  • 将代码分割成多个文件。每个文件一个类。您可以轻松地将它们合并在一起,但是为了开发,将东西分开,以便您自己解决较小的问题/部分。

  • 测试:您需要自行测试零件的功能。幻灯片是否应该做什么?很高兴你可以使用 Unittests,如果你写的类,对于 PHP 有 PHPUnit 。这有助于您开发代码,同时知道它完全正确。这有助于解决每个人的单位中的简单问题,您可以稍后更容易地更改代码,因为您可以一直运行测试。

  • 测试#2:当然你需要测试你的插件作为一个整体,所以你知道它正在做你正在寻找的。你能想像如何自动完成这些工作,所以您可以一直以相同的质量重复测试。

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。