【开发笔记】开发一个基于 wordpress 和 woocommerceRest 的接口插件

近期使用 ReactNative 开发项目,后台用的 woocommerce 那一套,需要写 Rest 接口对接,虽然 woo 自带的有,问题都是服务端的,客户端调用用只读密钥还行,写就没办法了,只能自己开坑写服务端的 Rest 接口插件了。

目前插件已完成所需功能涉及:wp hook,wp rest,wp auth,woo hook,woo rest 等。基本的 token 认证到注册自定义产品类型,hook 已有产品定制化流程,拦截 rest 数据进行二次定制等。

1.wordpress 插件结构

下文以 booking 这个插件为例,记录一下开发要注意的地方,首先看一下插件结构。

首先你需要创建一个文件夹,名称就是你的插件名称,其次,你需要创建一个同名的 PHP 入口文件: omiBeaver_booking.php(后面会列出内容),如果你的插件不进行市场上架发布,那么只需要这一个必须的文件即可,如果需要上架,那么你需要多一个必须文件:readme.txt 用来在 wordpress 市场上发布使用。其他文件则可以根据你的项目需要自行创建,我这里是开发一个预约系统的 Rest 插件,其中涉及,登录授权,后台面板的定制功能,这里分了

1.Apis.php(对外 Rest 接口)

2.BookingInit.php(程序主类)

3.omiHooks.php(插件使用的 Hook 聚合类)

4.Views(定制后台用到的 HTML 代码)

image.png

先从入口文件看

<?php

/**
 * Plugin Name:       OmiBeaverBooking
 * Plugin URI:        https://codecanyon.net/user/omibeaver/portfolio
 * Description:       Virtual goods course reservations Rest API
 * Version:           1.0.0
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            omiBeaver
 * Author URI:        https://codecanyon.net/user/omibeaver/portfolio
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Update URI:        https://codecanyon.net/user/omibeaver/portfolio
 * Text Domain:       OmiBeaverBooking
 * Domain Path:       /languages
 */


require_once 'BookingInit.php';
$MACRO = new BookingInit;

可以看到 Plugin name 这里是与文件同名的,这里是需要与插件名称一直,其他元信息用来展示在插件管理面板,如下所示:

image.png

TIPS:这里一定要注意,头部注释一定要和插件名称文件夹一致,否则压缩打包后,安装插件会无法识别。

第一次接触 wp 开发的小伙伴可能一下子很难接受 wp 的开发模式,你会发现有很多插件都是 PHP 与 HTML 混合编程,代码里充斥着很多:

<?php ?> <div></div> <?php ?> 

了解 hook 后,就觉得很正常了,hook 就像前端写 React 或者 Vue 里的生命周期,或者安卓的 activity,在(wp)系统的启动后触发一系列的回调或者预定义的方法,wp 内置了非常多的 hook 回调,你可以在系统初始化的时候执行你的代码,在系统 CURD 的每个操作前后甚至中途插入你的代码,而你只需要使用 🌰1:

  add_filter('manage_macro_booking_record_posts_columns', function ($columns) {
            return omiHooks::booking_admin_columns($columns);
        });

// omiHooks::booking_admin_columns
 public static function booking_admin_column($column, $post_id)
    {
        if ($column == 'booking_status') {
            Macro_views::booking_status_change($post_id);
        }
        if ($column == 'booking_time') {
            echo str_replace('T', ' ', get_post_meta($post_id, 'booking_time', true));
        }
        if($column == 'ID'){
            echo $post_id;
        }

    }


//Macro_views::booking_status_change

public static function booking_status_change($post_id)
    {
        $bg = get_post_meta($post_id, 'booking_status', true) == '0' ? 'tomato' : '#578fff';
        echo "<span onclick='omiJS.changeBookingStatus($post_id)'  style='background:$bg;padding:4px 10px;cursor: pointer;border-radius: 5px;color: #fff'>" . (get_post_meta($post_id, 'booking_status', true) == '0' ? 'wait' : 'finish') . '</span>';
    }

示例代码修改了帖子类型在后台表格上的展示效果,这里是展示用户预约记录的表,所以需要展示用户的预约状态,我注入了一个状态变化的 HTML 文字,另外从数据库提取的自定义字段:预约时间在展示的时候进行了一些格式化拆分。

image.png

上述 eg 有一些全局方法与 hook 稍后会说到,仅作为开头的示例展示。

1.wordpress 自定义 post

有了一个 hook 案例,可以发现可以根据系统提供的各种 hook 来定制自己的 wp,wp 常见的 hook 分为两种

一种是 actionHook,一种是 filterHook,第一种是事件触发的时候,你可以注入你的自定义代码,他是如何执行的呢,举个 🌰2:假如有个 hook 在某个帖子保存前需要执行你的自定义代码: save_post 这个 hook,使用 action 的 hook 你需要使用 add_action()方法,第一个参数是需要使用的 hook,第二个是 hook 的回调,第三个是优先级,第四个是参数个数。比如我们想在预约记录编辑后可以自定义预约时间或者状态,在回调里,我们可以写下如下:

  add_action('save_post', function ($post_id, $view) {
            omiHooks::booking_cus_save($post_id, $view);
        }, 10, 2);
//omiHooks::booking_cus_save
 public static function booking_cus_save($post_id, $view)
    {
        if ($view->post_type == 'macro_booking_record') {
            if (isset($_POST['booking_time']) && $_POST['booking_time'] != '') {
                update_post_meta($post_id, 'booking_time', $_POST['booking_time']);
            }
            if (isset($_POST['booking_status']) && $_POST['booking_status'] != '') {
                update_post_meta($post_id, 'booking_status', $_POST['booking_status']);
            }


        }
    }

在 Wp 中,当系统收到 post 数据后,处理完正常逻辑后会在提交前执行指定位置预设的回调

//wp 保存post逻辑xxxxx

//wp保存前最后一步
do_action(your_callback,$some_params);
//wp 保存提交完成

可想而知我们在系统 HTML 页面上的时候,如果需要定制页面,那么只需要使用 HTML 页面里预设的 hook 即可,所以会出现 php 与 HTML 混合情况。

默认的帖子结构是不支持我们预约表单的,我们需要定制一下自己的字段与表单类型。

1.创建自定义的帖子类型

很多时候,默认的 post 类型不支持我们的业务,通常来说,都是倾向于写作的的字段,而预约的产品可能有次数限制,有变体选择。如果我们使用 woocommerce 插件这里就直接定制好了。但是如果我们购买了产品课程后续需要预约使用,预约记录就需要我们自己定制了。

 add_action('init', function () {
            omiHooks::booking_record_fun();
        });
//omiHooks::booking_record_fun
 public static function booking_record_fun()
    {
        register_post_type('macro_booking_record',
            array(
                'label' => 'booking record',
                'labels' => array(
                    'name' => 'course booking',
                    'singular_name' => 'course booking list',
                    'add_new' => 'add booking record',
                    'add_new_item' => 'add booking record',
                    'edit' => 'update booking record',
                    'edit_item' => 'update',
                    'new_item' => 'create',
                    'view' => 'detail',
                    'view_item' => 'to detail',
                    'search_items' => 'query',
                    'not_found' => 'not found',
                    'not_found_in_trash' => 'not found'
                ),
                'show_ui' => true,
                'show_in_menu' => true,
                'public' => true,
                'description' => 'Booking Manage',
                'has_archive' => false,
                'show_in_rest' => false,
                'supports' => [
                    'title',
                    'author'
                ]
            )
        );
    }

这里注册了一个新的 post 类型,预约类型。参数请参阅:wp 文档直达

注册以后:

image.png

正常是没有后面几个字段的,这里需要我们的自定义字段啦,请看下面!

自定义帖子类型字段

这里需要用到 Meta 数据,wp 预设留下了可扩展的 meta 类型,可以在该字段里写入新的 key=>value,顶级默认是数组类型。后续取出可以使用全局方法来指定 key 取出,无需使用 array[0]格式。根据我们预约需要自定义两个字段:booking_time,booking_status,下面是在后台表单里 hook 增加我们的字段

 add_action('admin_init', function () {
            omiHooks::booking_view_ext();
        });

//omiHooks::booking_view_ext
 public static function booking_view_ext()
    {
        add_meta_box('macro_review_meta_box', 'Booking',
            function ($view) {
                Macro_views::booking_cus_view($view);
            },
            'macro_booking_record', 'normal', 'high'
        );

    }

// Macro_views::booking_cus_view
public static function booking_cus_view($view)
    {
        ?>
        <table>
            <tr>
                <td style="width: 100%">Booking Status</td>
                <td>
                    <select name="booking_status">
                        <option value="0" <?php echo $view->booking_status == '0' ? 'selected' : '' ?>>Wait</option>
                        <option value="1" <?php echo $view->booking_status == '1' ? 'selected' : '' ?>>Finish</option>
                    </select>
                </td>
            </tr>
            <tr>
                <td style="width: 100%">Booking Date</td>
                <td>
                    <input name="booking_time" value="<?php echo $view->booking_time ?>" type="datetime-local"/>

                </td>
            </tr>

        </table>
        <?php

    }

image.png

来读代码啦,首先这里使用了一个系统事件的 hook,后台初始化的时候,执行一下我们的回调,我们向系统注册了一个新的 post 类型。

add_action('init', function () {
            omiHooks::booking_record_fun();
        });

接下来我们需要:

1.在编辑表单支持新的字段

booking_cus_view 方法,这是我们自定义的回调,这里我们拿到了当前表格的行数据,并且可以在指定 row 的单元格进行定制输出,这里使用了 select 和时间选择器(wp 有默认样式),在表单 name 里写入新的字段名称即可,后面你可以使用保存的 hook 来存储这个自定义表单,如果还记得开始的 🌰2,会发现:

update_post_meta(post_id, 'booking_time', _POST['booking_time']);

这里就是保存的 hook,因为我们使用了自定义的字段,所以需要使用 meta 函数来存储我们的数据。到此 save 完成,接下来我们完成展示。

2.在后台面板展示出来

🌰1 里面使用了 filter hook:manage_macro_booking_record_posts_columns,注意 hook 的使用规范,如果是自定义 post 相关的 hook,你需要在 hook 加上你的自定义类型名称,比如本案例的

macro_booking_record

在这个 hook 的回调我们可以对表格的输出进行控制,隐藏或者临时新增。具体看 🌰1。

2.后台表格面板自定义交互事件

在上面的面板里有个状态显示,我们如果想通过后台来更改,打开编辑过于麻烦,直接点击这个按钮触发比较合适,这里就涉及到如何绑定一个 JS 点击事件以及触发一个内部的 ajax 事件。

1.自定义后台 HTML 绑定 JS 事件(载入 JS 文件)

如果我们按照 HTML 规则来写,在:

 echo "<span onclick='omiJS.changeBookingStatus($post_id)'  style='background:$bg;padding:4px 10px;cursor: pointer;border-radius: 5px;color: #fff'>" . (get_post_meta($post_id, 'booking_status', true) == '0' ? 'wait' : 'finish') . '</span>';

这里触发事件的 JS 怎么加载的呢,起初我是直接在次数写了一个 script 标签来实现 js 代码,结果由于这里的 hook 会根据 row 的渲染重复数次,又修改成全局只存在一个实例,总觉得不妥,最后按照 wp 的规范实现的载入外部文件。

 add_action( 'admin_enqueue_scripts', function (){
            omiHooks::loadJs();
        } );

上面这个 hook,可以在后台初始化的时候载入我们的 JS 代码。

 public static function loadJs()
    {

        wp_enqueue_script('ajax-script', plugins_url('/assets/utils.js', __FILE__));
        wp_localize_script(
            'ajax-script',
            'winter_ajax_obj',
            array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('omiBeaver'),
            )
        );
    }

上面的代码我们可以发现几个要注意的点,首先 enqueue 载入我们的代码文件后,还需要使用 localize 实现本地化,不是语言的本地化,是向后台进行 JS 的配置,如基础 URL 与随机数(防止跨站),当然也可以在特定页面加载只需判断当前页面是否是你要加载的 post type 类型再执行即可。

看一下这个 utils 文件

/**
 * * @author  omibeaver
 * Admin Booking Manage pane JS
 * @type {{changeBookingStatus(*): void}}
 */
const omiJS = {
    changeBookingStatus(post_id){
        if(winter_ajax_obj){
            let formData = new FormData();
            formData.append('_ajax_nonce',winter_ajax_obj.nonce);
            formData.append('action',"change_booking_status");
            formData.append('post_id',post_id)
            fetch(winter_ajax_obj.ajax_url, {
                    method:'POST',
                    body:formData
                }
            ).then((body)=>{
                body.json().then((data)=>{
                    alert(data.msg);
                    setTimeout(()=>{location.reload()},1000)
                }).catch((e)=>alert(e.msg))
            })
        }else{
            alert('init failed')
        }

    }


}

可以看见在 ajax 的参数部分加入了随机数。如果只是对某个页面修改建议在后台判断页面。我这里是写的全局一个工具对象。

3.wordpress Hook 开发

到此,完成了新的 post 类型与表单定制,表格定制输出。那么代码写在哪呢,我在哪里写入我的 hook 呢。

wp 提供了几个常用的全局生命周期方法,插件激活,插件禁用,在入口文件里,还记得吗与文件夹插件名称同名的那个文件,在这里会被 wp 执行,可以在这里写入 hook,或者和我一样,自定义一个类初始化注入全局 hook 即可,生命周期方法不是必须!

入口文件:

bookingInit 类

<?php

require_once 'Views.php';
require_once 'Apis.php';
require_once 'omiHooks.php';

/**
 * @author  omibeaver
 * @name BookingInit WP init hooks
 *
 */
class BookingInit
{


    function __construct()
    {

        //create booking post type.
        add_action('init', function () {
            omiHooks::booking_record_fun();
        });

        //init routes.
        add_action('rest_api_init', function () {
            self::register_api();
        });

        //custom admin booking pane  btn column.
        add_action('admin_init', function () {
            omiHooks::booking_view_ext();
        });

        //custom admin booking pane  save post.
        add_action('save_post', function ($post_id, $view) {
            omiHooks::booking_cus_save($post_id, $view);
        }, 10, 2);


        //custom admin booking pane column.
        add_action('manage_macro_booking_record_posts_custom_column', function ($column, $post_id) {
            omiHooks::booking_admin_column($column, $post_id);
        }, 10, 2);

        //custom admin booking pane columns.
        add_filter('manage_macro_booking_record_posts_columns', function ($columns) {
            return omiHooks::booking_admin_columns($columns);
        });

        //load custom JS.
        add_action( 'admin_enqueue_scripts', function (){
            omiHooks::loadJs();
        } );

        //hook admin pane booking statue change.
        add_action('wp_ajax_change_booking_status', function () {
            omiHooks::change_booking_status();
        });

        //hook admin pane booking statue change.
        add_action('wp_ajax_nopriv_change_booking_status',function (){
            omiHooks::change_booking_status();
        });

        //custom admin booking pane row
        add_action( 'post_row_actions',function ($actions,$post ){
           return omiHooks::removeRowBtn($actions,$post );
        } ,10,2);

        //custom woocommerce product
        add_filter( 'woocommerce_rest_prepare_shop_order_object', function ($data, $post, $context){
            return omiHooks::customOrderQuery($data, $post, $context);
        }, 12, 3 );

        //close auto-update tips.
        omiHooks::closeUpdate();

    }


    //start register all hooks.
    private static function register_api()
    {

        //rest loginIn
        register_rest_route('macro', '/booking_signIn', array(
            'methods' => 'GET',
            'callback' => function ($request) {
                return (new Apis($request))->bookingSignIn();
            },
            'permission_callback' => '__return_true'
        ));

        //rest query booking list by user
        register_rest_route('macro', '/booking_list', array(
            'methods' => 'GET',
            'callback' => function ($request) {
                return (new Apis($request))->bookingListQuery();
            },
            'permission_callback' => '__return_true'
        ));

        //rest signUp
        register_rest_route('macro', '/booking_signUp', array(
            'methods' => 'POST',
            'callback' => function ($request) {
                return (new Apis($request))->bookingSignUp();
            },
            'permission_callback' => '__return_true'
        ));

        //rest create new booking
        register_rest_route('macro', '/booking_create', array(
            'methods' => 'POST',
            'callback' => function ($request) {
                return (new Apis($request))->bookingCreate();
            },
            'permission_callback' => '__return_true'
        ));


        //rest change  booking status
        register_rest_route('macro', '/booking_update', array(
            'methods' => 'PUT',
            'callback' => function ($request) {
                return (new Apis($request))->bookingStatusUpdate();
            },
            'permission_callback' => '__return_true'
        ));


        register_rest_route('macro', '/create_order', array(
            'methods' => 'POST',
            'callback' => function ($request) {
                return (new Apis($request))->createOrder();
            },
            'permission_callback' => '__return_true'
        ));


    }


}







Views

<?php

/**
 * @author  omibeaver
 * View Templates
 */
class Macro_views
{

    public static function booking_cus_view($view)
    {
        ?>
        <table>
            <tr>
                <td style="width: 100%">Booking Status</td>
                <td>
                    <select name="booking_status">
                        <option value="0" <?php echo $view->booking_status == '0' ? 'selected' : '' ?>>Wait</option>
                        <option value="1" <?php echo $view->booking_status == '1' ? 'selected' : '' ?>>Finish</option>
                    </select>
                </td>
            </tr>
            <tr>
                <td style="width: 100%">Booking Date</td>
                <td>
                    <input name="booking_time" value="<?php echo $view->booking_time ?>" type="datetime-local"/>

                </td>
            </tr>

        </table>
        <?php

    }

    public static function booking_status_change($post_id)
    {
        $bg = get_post_meta($post_id, 'booking_status', true) == '0' ? 'tomato' : '#578fff';
        echo "<span onclick='omiJS.changeBookingStatus($post_id)'  style='background:$bg;padding:4px 10px;cursor: pointer;border-radius: 5px;color: #fff'>" . (get_post_meta($post_id, 'booking_status', true) == '0' ? 'wait' : 'finish') . '</span>';
    }

}


wordpress Rest 接口开发

Apis 类:后面我们看一下代码

<?php
require_once 'omiHooks.php';

/**
 * @author  omibeaver
 * Rest APIs
 */
class Apis
{

    private $request;
    private $user;
    private const WOO_SECRET = 'cs_xxx';//可写secret
    private const WOO_KEY = 'ck_xxx';//可写key
    private const REMOTE_URL = 'xxx';


    function __construct($request)
    {

        $this->request = $request;
        $is_login = wp_validate_auth_cookie($request->get_param('token'), 'macro');
        if ($is_login) {
            $userAuthInfo = wp_parse_auth_cookie($request->get_param('token'), 'macro');
            $this->user = get_user_by('login', $userAuthInfo['username'])->data;
        }
    }


    public function bookingStatusUpdate(): array
    {
        if (!$this->user) return ['code' => -1, 'msg' => 'token invalid', 'data' => null];
        $post_id = $this->request->get_param('post_id');
        $user_id = self::getUserByCookie($this->request->get_param('token'))->ID;
        if (!$post_id) {
            return ['code' => -1, 'msg' => 'params error', 'data' => null];
        }
        $args = array(
            'post_type' => 'macro_booking_record',
            'posts_per_page' => 10,
            'p' => $post_id
        );
        $data = (new WP_Query($args))->posts;
        if (count($data) != 1) {
            return ['code' => -1, 'msg' => 'content not found', 'data' => null];
        }
        if ($data[0]->post_author != $user_id) {
            return ['code' => -1, 'msg' => 'not permission', 'data' => null];
        }
        if (get_post_meta($post_id, 'booking_status', true)['booking_status'] == '0') {
            update_post_meta($post_id, 'booking_status', 1);
            return ['code' => 1, 'msg' => 'success', 'data' => null];
        } else {
            return ['code' => -1, 'msg' => 'had changed', 'data' => null];

        }

    }


    public function bookingCreate(): array
    {


        if (!$this->user) return ['code' => -1, 'msg' => 'login invalid', 'data' => null];
        $post_name = $this->request->get_param('booking_name');
        $booking_time = $this->request->get_param('booking_time');
        if (empty($booking_time) || empty($post_name)) return ['code' => -1, 'msg' => 'params  invalid', 'data' => null];
        if (!date_create($booking_time)) return ['code' => -1, 'msg' => 'date invalid', 'data' => null];
        if (strtotime($booking_time) < time()) return ['code' => -1, 'msg' => 'Can\'t make an appointment before', 'data' => null];
        if (strlen($post_name) > 50) return ['code' => -1, 'msg' => 'params error', 'data' => null];
        $booking_course_title = explode(':', $post_name);
        if (count($booking_course_title) != 2) {
            return ['code' => -1, 'msg' => 'title format invalid', 'data' => null];
        }

        try {

            $booking_course_title = $booking_course_title[0];
            $booking_course_id = $this->request->get_param('course_id');

            $user_orders = (new WC_Order($booking_course_id))->get_items();

            if (count($user_orders) != 1) {
                return ['code' => -1, 'msg' => 'course not found', 'data' => null];
            }


            $user_orders = current($user_orders);
            $meta_data = current($user_orders->get_meta_data());
            $user_all_booking_count = (int)$meta_data->value;
            //Check the available schedule of the course


        } catch (Exception $exception) {
            return ['code' => -1, 'msg' => $exception->getMessage(), 'data' => null];
        }

        $args = array(
            'post_type' => 'macro_booking_record',
            'posts_per_page' => 10,
            'post_status' => 'publish',
            'author' => $this->user->ID,
            'meta_query' => [
                'booking_id' => $booking_course_id
            ]
        );
        $user_booking_count = (new WP_Query($args))->post_count;
        if ($user_booking_count > $user_all_booking_count+10) return ['code' => -1, 'msg' => 'The number of appointments has been used up', 'data' => null];
        $res = wp_insert_post([
            'post_author' => $this->user->ID,
            'post_title' => $post_name,
            'post_status' => 'publish',
            'post_name' => $post_name,
            'post_type' => 'macro_booking_record'

        ]);

        update_post_meta($res, 'booking_status', 0);
        update_post_meta($res, 'booking_time', $booking_time);
        update_post_meta($res, 'booking_course_id', $booking_course_id);
        update_post_meta($res, 'booking_course_title', $booking_course_title);
        return ['code' => 1, 'data' => ['booking_id' => $res, 'left' => $user_all_booking_count - $user_booking_count], 'msg' => 'SUCCESS'];
    }

    public function bookingSignUp(): array
    {

        $user_name = sanitize_user($this->request->get_body_params()['user_name'] ?? '');
        $password = trim($this->request->get_body_params()['password'] ?? '');
        $user_email = trim($this->request->get_body_params()['user_email'] ?? '');

        if (!$user_name || !$password || !$user_email) {
            return ['code' => -1, 'msg' => 'params error', 'data' => null];
        }

        if (strlen($user_name) > 20) {
            return ['code' => -1, 'msg' => 'params error', 'data' => null];
        }

        if (!is_email($user_email)) {
            return ['code' => -1, 'msg' => 'email error', 'data' => null];
        }

        $user_id = username_exists($user_name);
        if (!$user_id && !email_exists($user_email)) {
            $user_id = wp_create_user($user_name, $password, $user_email);
            return ['code' => 1, 'msg' => 'SUCCESS', 'data' => ['user_id' => $user_id]];
        } else {

            return ['code' => -1, 'msg' => 'account exist', 'data' => null];
        }

    }

    private static function getUserByCookie($cookie)
    {
        $userAuthInfo = wp_parse_auth_cookie($cookie, 'macro');
        return get_user_by('login', $userAuthInfo['username']);


    }


    public function bookingListQuery(): array
    {
        if (!$this->user) return ['code' => -1, 'msg' => 'login invalid', 'data' => null];
        if (empty($this->request->get_param('start_booking_time')) || empty($this->request->get_param('end_booking_time'))) return ['code' => -1, 'msg' => 'params booking_time  invalid', 'data' => null];
        if (!date_create($this->request->get_param('start_booking_time')) || !date_create($this->request->get_param('end_booking_time'))) return ['code' => -1, 'msg' => 'params booking_time  invalid', 'data' => null];
        $start_booking_time = date_create($this->request->get_param('start_booking_time'));
        $end_booking_time = date_create($this->request->get_param('end_booking_time'));
        $args = array(
            'post_type' => 'macro_booking_record',
            'posts_per_page' => 10,
            'author' => $this->user->ID,
            'meta_query' => [
                'booking_time' =>
                    array(
                        array('key' => 'booking_time', 'value' => $start_booking_time->format('Y/m/d'), 'compare' => '>=', 'type' => 'DATE'),
                        array('key' => 'booking_time', 'value' => $end_booking_time->format('Y/m/d'), 'compare' => '<=', 'type' => 'DATE'),
                    )

            ]
        );

        $data = (new WP_Query($args))->posts;
        foreach ($data as $post) {
            $post->booking_status = get_post_meta($post->ID, 'booking_status', true);
            $post->booking_time = get_post_meta($post->ID, 'booking_time', true);

        }
        return ['code' => 1, 'msg' => 'SUCCESS', 'data' => $data];


    }

    public function bookingSignIn(): array
    {

        $username = sanitize_user($this->request->get_param('username'));
        $password = trim($this->request->get_param('password'));
        $user = wp_authenticate($username, $password);
        return ['code' => 1, 'msg' => 'success', 'user' => $user, 'token' => wp_generate_auth_cookie($user->ID, time() + 720000, 'macro')];

    }


    public function createOrder(): array
    {
        //默认订单数据
        $data = [
            'meta_data' => array(array(
                'key' => 'pay_status',
                'value' => '50%'
            )),

            'payment_method' => 'bacs',
            'payment_method_title' => 'Direct Bank Transfer',
            'set_paid' => true,
            'billing' => [
                'first_name' => 'testUser',
                'last_name' => 'testUser',
                'address_1' => '969 Market',
                'address_2' => '',
                'city' => 'San Francisco',
                'state' => 'CA',
                'postcode' => '94103',
                'country' => 'US',
                'email' => 'testUser@test.com',
                'phone' => '(555) 555-5555'
            ],
            'shipping' => [
                'first_name' => 'John',
                'last_name' => 'Doe',
                'address_1' => '969 Market',
                'address_2' => '',
                'city' => 'San Francisco',
                'state' => 'CA',
                'postcode' => '94103',
                'country' => 'US'
            ],
            'line_items' => [

                [
                    'product_id' => 65,
                    'variation_id' => 70,
                    'quantity' => 1
                ]
            ],
            'shipping_lines' => [
                [
                    'method_id' => 'flat_rate',
                    'method_title' => 'Flat Rate',
                    'total' => '0'
                ]
            ]
        ];

        try {

            $data = wp_remote_post(self::REMOTE_URL ."/wp-json/wc/v3/orders?consumer_key=" . self::WOO_KEY . "&consumer_secret=" . self::WOO_SECRET,
                array(
                    'headers' => array('Content-Type' => 'application/json'),
                    'timeout' => 30,
                    'body' => json_encode($data),
                )
            );

        } catch (Exception $exception) {
            return ['code' => -1, 'msg' => 'SUCCESS', 'data' => $exception->getMessage()];
        }

        return ['code' => 1, 'msg' => 'SUCCESS', 'data' => $data];

    }


}

Api 接口 wp 默认有提供,我们有自己逻辑需要定义,所以这里使用的自定义接口,首先我们使用一个 hook 来初始化路由:

//init routes.
        add_action('rest_api_init', function () {
            self::register_api();
        });

//start register all hooks.
    private static function register_api()
    {

        //rest loginIn
        register_rest_route('macro', '/booking_signIn', array(
            'methods' => 'GET',
            'callback' => function ($request) {
                return (new Apis($request))->bookingSignIn();
            },
            'permission_callback' => '__return_true'
        ));

        //rest query booking list by user
        register_rest_route('macro', '/booking_list', array(
            'methods' => 'GET',
            'callback' => function ($request) {
                return (new Apis($request))->bookingListQuery();
            },
            'permission_callback' => '__return_true'
        ));

        //rest signUp
        register_rest_route('macro', '/booking_signUp', array(
            'methods' => 'POST',
            'callback' => function ($request) {
                return (new Apis($request))->bookingSignUp();
            },
            'permission_callback' => '__return_true'
        ));

        //rest create new booking
        register_rest_route('macro', '/booking_create', array(
            'methods' => 'POST',
            'callback' => function ($request) {
                return (new Apis($request))->bookingCreate();
            },
            'permission_callback' => '__return_true'
        ));


        //rest change  booking status
        register_rest_route('macro', '/booking_update', array(
            'methods' => 'PUT',
            'callback' => function ($request) {
                return (new Apis($request))->bookingStatusUpdate();
            },
            'permission_callback' => '__return_true'
        ));


        register_rest_route('macro', '/create_order', array(
            'methods' => 'POST',
            'callback' => function ($request) {
                return (new Apis($request))->createOrder();
            },
            'permission_callback' => '__return_true'
        ));


    }

使用全局函数:register_rest_route 来注册自定义的路由,这里需要注意生成的链接格式为:

http(s)://your_host_url/wp-json/自定义前缀/路由

API 代码里面基本上很清楚,就不过多介绍了,里面使用的全局方法可以在 wp 文档直接找到。

woocommerce 定制接口

最后关于 woocommerce 定制的事情,这里我只演示一个接口作为开始吧,其他同理。

woo 在 wp 基础上进行了深度定制,提供了非常多的 hook,你可以在这里找到:woocommerce hooks

这里是提一下 filter hook 和 action 的区别,这个 hook 主要使用在数据获取中,下面这个 🌰,使用 woo 的数据过滤器 hook 对订单查询记录进行定制,我们这里给订单新增了一个字段:booking_left,可预约次数。

 add_filter( 'woocommerce_rest_prepare_shop_order_object', function ($data, $post, $context){
            return omiHooks::customOrderQuery($data, $post, $context);
        }, 12, 3 );

 public static function customOrderQuery($data, $post, $context): array
    {

        $data->data['booking_left'] = 2;

        return $data;
    }

woocommerceRest 调用下单

有时候会发现 woo 的功能比较分散,比如下订单,如果使用内部方法流程很多,这个时候可以使用 Rest 接口直接下单:

 public function createOrder(): array
    {
        //默认订单数据
        $data = [
            'meta_data' => array(array(
                'key' => 'pay_status',
                'value' => '50%'
            )),

            'payment_method' => 'bacs',
            'payment_method_title' => 'Direct Bank Transfer',
            'set_paid' => true,
            'billing' => [
                'first_name' => 'testUser',
                'last_name' => 'testUser',
                'address_1' => '969 Market',
                'address_2' => '',
                'city' => 'San Francisco',
                'state' => 'CA',
                'postcode' => '94103',
                'country' => 'US',
                'email' => 'testUser@test.com',
                'phone' => '(555) 555-5555'
            ],
            'shipping' => [
                'first_name' => 'John',
                'last_name' => 'Doe',
                'address_1' => '969 Market',
                'address_2' => '',
                'city' => 'San Francisco',
                'state' => 'CA',
                'postcode' => '94103',
                'country' => 'US'
            ],
            'line_items' => [

                [
                    'product_id' => 65,
                    'variation_id' => 70,
                    'quantity' => 1
                ]
            ],
            'shipping_lines' => [
                [
                    'method_id' => 'flat_rate',
                    'method_title' => 'Flat Rate',
                    'total' => '0'
                ]
            ]
        ];

        try {

            $data = wp_remote_post(self::REMOTE_URL ."/wp-json/wc/v3/orders?consumer_key=" . self::WOO_KEY . "&consumer_secret=" . self::WOO_SECRET,
                array(
                    'headers' => array('Content-Type' => 'application/json'),
                    'timeout' => 30,
                    'body' => json_encode($data),
                )
            );

        } catch (Exception $exception) {
            return ['code' => -1, 'msg' => 'SUCCESS', 'data' => $exception->getMessage()];
        }

        return ['code' => 1, 'msg' => 'SUCCESS', 'data' => $data];

    }

woocommerce 和 wp 一样,你可以自定义下单流程的 HTML 内容,当然你也可以注入 JS 开发,上文其实有使用一个哦,发现了吗!甚至你可以使用 React 来开发。

有其他疑问可以留言,我有空的时候会修正文章和回复。

本文涉及的插件 OmiBeaverBooking 已开源:OmiBeaverBooking

😋 有定制 wordpress 插件需求的可联系 winter_986@qq.com

  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    45 引用 • 113 回帖 • 402 关注
  • woocommerce
    1 引用 • 1 回帖
2 操作
yf98 在 2022-09-27 08:17:52 更新了该帖
yf98 在 2022-09-26 09:21:31 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • 0sheng
    订阅者

    期待更丰富、强大、完善得 WP 插件