gebruederheitz/wp-easy-cpt

用于强大WordPress自定义帖子类型设置的简单易用的工具。

v2.3.3 2024-01-15 09:32 UTC

This package is auto-updated.

Last update: 2024-09-15 11:21:15 UTC


README

用于强大WordPress自定义帖子类型设置的简单易用的工具。

安装

通过composer

> composer require gebruederheitz/wp-easy-cpt

确保您已安装Composer自动加载或备用的类加载器。

用法

帖子类型抽象类

use Gebruederheitz\Wordpress\CustomPostType\PostType;
use Gebruederheitz\Wordpress\CustomPostType\PostTypeRegistrationArgs;

class NewsPostType extends PostType 
{
    /** 
     * @required Supply a slug-type name which will be used in the DB amongst 
     *           other places 
     */
    public static $postTypeName = 'news';
    
    /** 
     * @required The name users will see 
     */
    public static $prettyName = 'News';
    
    /**
     * @optional Where the metabox will appear: one of 'side', 'normal', 'advanced'
     * @default 'side'
     */
    public static $context = 'normal';

    /** 
     * @var bool Whether to use a Gutenberg editor and call the allowed block 
     *           types hook. Set to "false" by default, so if you don't need
     *           Gutenberg you can just leave this out.
     */
    protected $withGutenberg = true;

    /** 
     * @var bool Whether to load the media picker scripts on the edit page. 
     *           If you don't need to use a media picker, you can leave this
     *           bit out. 
     */
    protected $withMedia = true;

    /** 
     * @var array List of allowed block types if Gutenberg is enabled. If you
     *            did not set $withGutenberg to `true` you won't need this.
     *            Otherwise supply a string-array of block names 
     *            (e.g. `core/button`). 
     */
    protected $allowedBlockTypes = [
        'core/paragraph',
        'core/columns',
    ];
    
    
    /** 
     * @var string The translation domain to use for menu labels etc. 
     * 
     * If you are using the "ghwp" domain, you can skip this setting, otherwise
     * set it to your theme / plugin's i18n domain.
     */
    protected $translationDomain = 'ghwp';
    
    // -------------------------------------------------------------------------
    // There are only two methods you need to define, and one you will want to
    // override:
    // -------------------------------------------------------------------------
    
    /**
     * Renders the meta box for users to edit additional post type fields.
     *
     * @return void
     */
    public function renderMetabox() 
    {
        /** @var WP_Post */
        global $post;
        
        // ---------------------------------------------------------------------
        // You could go old-school
        // ---------------------------------------------------------------------
        ?>
            <input name="postid" type="text" value="<?php echo $post->ID; ?>" />
        <?php

        // or use a library like cmb2
        $metabox = new_cmb2_box([
            'id' => self::$postTypeName . 'something',
            'title' => 'Details',
        ]);
        
        // ---------------------------------------------------------------------
        // or use the dependency-free MetaForms class and maybe even combine it
        // with the repository (more below)
        // ---------------------------------------------------------------------
    
        /** @var Newspost $news */
        $news = NewspostRepository::getInstance()->find($post->ID);

        MetaForms::renderTextInputField(
            Newspost::tagMetaFieldName,
            $news->getTagsAsString(),
            'Tags (separate with semicolons)',
            false
        );
    }

    /**
     * Handle the submission of user edited metadata.
     *
     * @param WP_POST $post
     * @param array $data 
     *
     * @return void
     */
    public function savePostMeta($post, $data) 
    {
        /** @var Newspost $news */
        $news = NewspostRepository::getInstance()->find($post->ID);

        $news->setTagsFromString($data[Newspost::tagMetaFieldName] ?? '');

        NewspostRepository::getInstance()->save($news)->flush();
    }

    /*
     * The PostType class handles the registration for you. For easy access and
     * type definitions it uses the PostTypeRegistrationArgs configuration
     * object.
     * To modify the arguments passed to `register_post_type` you can override
     * the method editRegistrationArgs() and call the fluent setters on the
     * PostTypeRegistrationArgs object provided.
     */

    /**
     * Modify the arguments for the post type's registration call.
     */
    protected function editRegistrationArgs(PostTypeRegistrationArgs $args): PostTypeRegistrationArgs 
    {
         $args->addAuthorSupport();
         // Setters in PostTypeRegistrationArgs can be chained:
         $args->setCanExport(true)
             ->setPluralLabel('Newsposts')
             ->makePublic();
         
         return $args;
    }

}

使用实体存储库

实体存储库缓存帖子类型实体的数据库调用,并有助于帖子类型数据的规范化。

作为第一步,您将需要定义您的帖子类型实体(以及可能的子实体),实现StorableEntity接口

use Gebruederheitz\Wordpress\Domain\StorableEntity;

class Newspost implements StorableEntity
{
    /** @var int|null */
    private $postId;

    /** @var string */
    private $title = '';

    /** @var array */
    private $tags = [];

    /*
     * -------------------------------------------------------------------------
     * For easier access, and to avoid duplication and typo-style errors, we
     * define the post_meta keys as class constants. It's up to you if you
     * want to adopt this pattern.
     * You are encouraged to prefix your post_meta keys.
     * -------------------------------------------------------------------------
     */

    /** @var string */
    public const tagMetaFieldName = 'ghwp-faq-tags';
    
    /*
     * -------------------------------------------------------------------------
     * Construct the entity from the post object and the associated raw
     * post_meta.
     * -------------------------------------------------------------------------
     */
    
    public function __construct(WP_Post $post = null, $meta = []) {
        $this->postId = $post->ID ?? null;
        $this->title  = $post->post_title ?? '';
        $this->tags   = $this->unserializeTags($meta[self::tagMetaFieldName] ?? '');
    }
    
    /*
     * -------------------------------------------------------------------------
     * How you approach this bit is up to you. We'll be using a getter / setter
     * pattern in this example. These methods will be used wherever the CPT
     * is going to be consumed – template partials for instance.
      * -------------------------------------------------------------------------
    */

    public function getPostId(): ?int
    {
        return $this->postId;
    }

    public function setPostId(?int $postId): Newspost
    {
        $this->postId = $postId;

        return $this;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function setTitle(string $title): Newspost
    {
        $this->title = $title;

        return $this;
    }

    public function getTags(): array
    {
        return $this->tags;
    }

    public function getTagsAsString(): string
    {
        return $this->serializeTags($this->tags);
    }

    public function setTags(array $tags): Newspost
    {
        $this->tags = $tags;

        return $this;
    }

    public function setTagsFromString(string $tags): Newspost
    {
        $this->tags = $this->unserializeTags($tags);

        return $this;
    }
    
    /*
     * -------------------------------------------------------------------------
     * Implement the toMetaValues() method: Return an associative array of 
     * post_meta keys with the corresponding string value.
     * -------------------------------------------------------------------------
     */

    public function toMetaValues(): array
    {
        return [
            self::tagMetaFieldName => $this->serializeTags($this->tags),
        ];
    }
    
    /*
     * -------------------------------------------------------------------------
     * We're using some utility functions here; you could also use JSON or
     * any other serialization format. Beware however, that JSON might cause
     * issues with escaped quoting – your best bet is to base64_encode() your
     * JSON before storing and base64_decode() right after retrieving the values.
     * 
     * Alternatively you can work around this using wp_slash() on already
     * escaped JSON strings: 
     * https://developer.wordpress.org/reference/functions/update_post_meta/#character-escaping
     * -------------------------------------------------------------------------
     */

    private function serializeTags(array $tags): string
    {
        return join(';', $this->tags);
    }

    private function unserializeTags(string $tags): array
    {
        return array_map('trim', explode(';', $tags));
    }
}

然后您可以为此新实体创建一个存储库

use Gebruederheitz\Wordpress\Domain\CustomPostTypeRepository;

class NewspostRepository extends CustomPostTypeRepository
{
    /*
     * -------------------------------------------------------------------------
     * First off, define your meta key. Use a prefix. Prefix that with an
     * underscore to hide the metadata from WP menus.
     * -------------------------------------------------------------------------
     */
    public static $metaKey = '_ghwp_news';
    
    /*
     * -------------------------------------------------------------------------
     * Now define the entity and post type classes you want the repository
     * to use. The $entityClass needs to implement the StorableEntity interface,
     * while $postTypeClass must implement PostTypeInterface.
     * -------------------------------------------------------------------------
    */
     
      /** @var string */
    protected static $entityClass = Newspost::class;

    /** @var string */
    protected static $postTypeClass = NewsPostType::class;
    
    /*
     * -------------------------------------------------------------------------
     * You can override the type for the internal $entities property to get
     * better type hinting:
     * -------------------------------------------------------------------------
     */
    /** @var Newspost[] */
    protected $entities = [];
}

基本上您就完成了。如果您进行任何非常规操作,例如

  • 具有多个属性的大型实体,您希望将它们存储在不同的元字段中
  • 处理不是整个自定义帖子类型的元字段
  • ...

您可以在下面找到示例下面

实体存储库的非CPT使用

CustomPostTypeRepository类与PostType实体类紧密耦合,但有时您可能希望在不进行这种紧密关联的情况下使用实体存储库。例如,您可以使用它来存储普通postpage的元字段。

在这些场景中,您可以使用基类AbstractRepository,它不会将您限制在单个帖子类型上

use Gebruederheitz\Wordpress\Domain\AbstractRepository;

class ViewCountRepository extends AbstractRepository
{
    /*
     * -------------------------------------------------------------------------
     * Here, you do not need to provide a CPT, only the meta field key and the
     * entity class this repository will manage.
     * -------------------------------------------------------------------------
     */
    public static $metaKey = 'ghwp_view_count';
    protected static $entityClass = ViewCount::class;
    
    /* 
     * -------------------------------------------------------------------------
     * You will have to implement the abstract method `getPosts()` in order
     * to define the selection your entity is based on. We could, for instance,
     * introduce a view counter on all posts with a tag "counted", and by
     * default sort them alphabetically by title.
     * -------------------------------------------------------------------------
     */
     
    /**
     * Retrieve all posts that will be processed in getAllFromDB(). Use this
     * method to fetch the list of posts relevant to you.
     * If you're using a custom post type with
     * Gebruederheitz\Wordpress\CustomPostType\PostType this method has a basic
     * implementation in CustomPostTypeRepository.
     *
     * @return WP_Post[]
     */
    protected static function getPosts(): array {
        return get_posts(
            [
                'post_type' => 'post',
                'tag' => 'counted',
                'orderby' => 'title',
                'numberposts' => -1,
            ]
        );
    }
}

实体存储库示例

示例:getPosts()重写

    protected static function getPosts(): array
    {
        // Example: Sort entries by the meta value we define
        return get_posts(
            'post_type' => 'post',
            'tag' => 'counted',
            'orderby' => 'meta_value_num title',
            'meta_key' => self::$metaKey,
            'order' => 'ASC',
        )
        
        // Example: Retrieve all posts of a custom type ordered by a user-defined
        //          index stored in a separate meta field (using
        //          CustomPostTypeRepository)
        return get_posts(
            [
                'post_type'       => MyCustomPostType::$postTypeName,
                'orderby'         => 'meta_value_num title',
                'meta_key'        => self::$metaKey . '_order',
                'order'           => 'ASC',
                'numberposts'     => -1,
            ]
        );
        
        // This is the default for CustomPostTypeRepository – retrieves all
        // posts for the CPT class you set in $postTypeClass.
        return get_posts(
            [
                'post_type' => call_user_func([static::$postTypeClass, 'getPostTypeName']),
                'numberposts' => -1,
            ]
        );
    }

示例:在将数据存储在单独的元字段(cmb2等)时重写entityFromPostId

    /**
     * Instantiates an entity instance from a post ID.
     *
     * @param int $postId
     * @param ?WP_Post $post
     *
     * @return StorableEntity An instance of the entity type defined in static::$entityClass
     */
    protected static function entityFromPostId(int $postId, WP_Post $post = null): StorableEntity
    {
        if (empty($post)) {
            $post = get_post($postId);
        }

        $meta = self::getMetaValues($postId);
        // So far it's the default behaviour of this method. We get the post
        // object if it hasn't been passed, and get the meta values for
        // static::$metaKey.
        //
        // Now we add some meta values stored separately, e.g. when using cmb2 /
        // CustomMetaBoxes2
        $meta['some-other-key'] = get_post_meta($postId, '_some-other-key', true);
        
        // When we're done, we instantiate an object from the entity class,
        // passing it the $meta array we extended and return the entity:  
        return new static::$entityClass($post, $meta);
    }

示例:存储多个元字段时的persist()重写

     /**
     * Save a single Entity's meta fields to the database. Writes the common
     * fields to self::$metaKey and the "order" field's value to
     * "{$metaKey}_order" to allow custom sorting.
     *
     * @param StorableEntity $item The Entity to be persisted to the DB.
     */
    protected function persist(StorableEntity $item): void
    {
        update_post_meta($item->getPostId(), self::$metaKey, $item->toMetaValues());
        update_post_meta($item->getPostId(), self::$metaKey . '_order', $item->getOrder());
    }
}

开发

依赖项

  • PHP >= 7.4
  • Composer 2.x
  • NVM和nodeJS LTS(v16.x)
  • 推荐:GNU Make(或替代方案)