Day19 - 铁人付外挂设定介面(一)- 资料库结构

在开始开发金流外挂的後台设定页面前,我们先来快速认识一下 WordPress 的资料表,同时介绍读取写入会用到的函式,以下为有安装 WooCommerce 的 WordPress 网站所有的资料表:

wp_actionscheduler_actions
wp_actionscheduler_claims
wp_actionscheduler_groups
wp_actionscheduler_logs
* wp_commentmeta
* wp_comments
* wp_links
* wp_options
* wp_postmeta
* wp_posts
* wp_term_relationships
* wp_term_taxonomy
* wp_termmeta
* wp_terms
* wp_usermeta
* wp_users
wp_wc_admin_note_actions
wp_wc_admin_notes
wp_wc_category_lookup
wp_wc_customer_lookup
wp_wc_download_log
wp_wc_order_coupon_lookup
wp_wc_order_product_lookup
wp_wc_order_stats
wp_wc_order_tax_lookup
wp_wc_product_meta_lookup
wp_wc_reserved_stock
wp_wc_tax_rate_classes
wp_wc_webhooks
wp_woocommerce_api_keys
wp_woocommerce_attribute_taxonomies
wp_woocommerce_downloadable_product_permissions
wp_woocommerce_log
wp_woocommerce_order_itemmeta
wp_woocommerce_order_items
wp_woocommerce_payment_tokenmeta
wp_woocommerce_payment_tokens
wp_woocommerce_sessions
wp_woocommerce_shipping_zone_locations
wp_woocommerce_shipping_zone_methods
wp_woocommerce_shipping_zones
wp_woocommerce_tax_rate_locations
wp_woocommerce_tax_rates

WordPress 本身预设的资料表只有 * 注记的那几个,其他都是 WooCommerce 产生的,每张表都有 wp_ 的前缀,这可以在 wp-config.php 里面透过以下变数来修改:

$table_prefix = 'wp_'; // 在第一次安装还没有建立库时修改

如果你的 WordPress 是使用套装软件安装的,因为安全性考量他们会自动帮你修改资料表前缀,所以当你进入资料库要找的是 xxx_options、xxx_posts 这些资料表,看前缀後面的名称就好。接下来介绍五个表,分别是与设定、订单、以及会员资料相关的表。

wp_options

该表存放全站设定相关的资料,也就是在後台左侧选单 > 设定里面的设定都会存在这张表里面,我们要开发的 WooCommerce 金流外挂,也是把相关的变数存在这个表中。为了节省效能,在写入时可以先把设定的值组成阵列再进行 INSERT。

# column_name data_type character_set collation is_nullable column_default extra foreign_key comment
1 option_id bigint(20) unsigned NULL NULL NO NULL auto_increment
2 option_name varchar(191) utf8mb4 utf8mb4_unicode_ci NO
3 option_value longtext utf8mb4 utf8mb4_unicode_ci NO NULL
4 autoload varchar(20) utf8mb4 utf8mb4_unicode_ci NO yes

wp_options 相关的函式定义在 wp-include/option.php,常用的如下:

add_option( $option_name, $option_value, autoload=yes) 新增设定

它会先自动检查 $option_name 是否存在,不存在的话新增一笔资料,已存在的话会把 $option_value 进行覆写。autoload 预设为 yes,作用是设定该 $option 是否加入到快取之中。如果你确定每个页面都会用到这个设定的话,就保持预设值 yes,反之为 no。范例如下:

// add option
$twitters = array( '@abc', '@cde', '@fgh' );// 用阵列来整理 option_value
add_option( 'irp_twitter_accounts', $twitters );

update_option( $option_name, $option_value ) 更新设定

更新已存在的 $optioni_value,如果该 $option_name 不存在的会自动新增一笔,范例如下:

// update option
$twitters = array_merge(
	$twitters,
	array(
		'@ijk',
		'@lmn'
	)
);
update_option( 'irp_twitter_accounts', $twitters );

get_option( $option_name ) 取得设定栏位的值

范例如下:

// get option
$irp_twitter_accounts = get_option( 'irp_twitter_accounts' );
foreach( $irp_twitter_accounts as $account ){
	echo $account.', '; // 输出 @abc, @cde, @fgh, @ijk, @lmn
}

delete_option( $option_name ) 删除设定栏位

范例如下:

// delete option
delete_option( 'irp_twitter_accounts' );

wp_posts

存放文章、页面、选单、文章版本、以及自定义文章的资料表,而自定义文章就是用这个表里面的 post_type 栏位来纪录不同的文章类型,WooCommerce 商品的 post_type 为 proudct,订单则为 shop_order,它并没有新建资料表来存放,而是放在 wp_posts 里面。

# column_name data_type character_set collation is_nullable column_default extra foreign_key comment
1 ID bigint(20) unsigned NULL NULL NO NULL auto_increment
2 post_author bigint(20) unsigned NULL NULL NO NULL
3 post_date datetime NULL NULL NO 0000-00-00 00:00:00
4 post_date_gmt datetime NULL NULL NO 0000-00-00 00:00:00
5 post_content longtext utf8mb4 utf8mb4_unicode_ci NO NULL
6 post_title text utf8mb4 utf8mb4_unicode_ci NO NULL
7 post_excerpt text utf8mb4 utf8mb4_unicode_ci NO NULL
8 post_status varchar(20) utf8mb4 utf8mb4_unicode_ci NO publish
9 comment_status varchar(20) utf8mb4 utf8mb4_unicode_ci NO open
10 ping_status varchar(20) utf8mb4 utf8mb4_unicode_ci NO open
11 post_password varchar(255) utf8mb4 utf8mb4_unicode_ci NO
12 post_name varchar(200) utf8mb4 utf8mb4_unicode_ci NO
13 to_ping text utf8mb4 utf8mb4_unicode_ci NO NULL
14 pinged text utf8mb4 utf8mb4_unicode_ci NO NULL
15 post_modified datetime NULL NULL NO 0000-00-00 00:00:00
16 post_modified_gmt datetime NULL NULL NO 0000-00-00 00:00:00
17 post_content_filtered longtext utf8mb4 utf8mb4_unicode_ci NO NULL
18 post_parent bigint(20) unsigned NULL NULL NO NULL
19 guid varchar(255) utf8mb4 utf8mb4_unicode_ci NO
20 menu_order int(11) NULL NULL NO NULL
21 post_type varchar(20) utf8mb4 utf8mb4_unicode_ci NO post
22 post_mime_type varchar(100) utf8mb4 utf8mb4_unicode_ci NO
23 comment_count bigint(20) NULL NULL NO NULL

wp_posts 相关的函式定义在 wp-include/post.php,常用的如下:

wp_inset_post( $postarr, $wp_error=false) 插入新文章

$postarr 是一个定义新文章内容的阵列,里面有许多参数可以定义,$wp_error 为 true 时会在文章插入失败时回传一个 WP_Error 物件,范例如下:

// insert post - set post status to draft
$args = array(
	'post_title'   => '文章标题',
	'post_excerpt' => '文章摘要',
	'post_content' => '内文',
	'post_status'  => 'draft',  // 文章状态,有 draft、publish
	'post_type'    => 'post', // 文章类型,预设为 post,可以是 page 或其他 CPT
	'post_author'  => 1, // 作者 ID
	'menu_order'   => 0 // 文章顺序
);
$post_id = wp_insert_post( $args );  // 新文章插入完成後会回传该文章的 post ID
echo 'post ID: ' . $post_id . '<br>';

wp_update_post( $postarr, $wp_error = false) 更新文章资料

同样使用 $postarr 阵列来更新资料,记得要提供需要被更新的文章 ID,范例如下:

// update post - change post status to publish
$args = array(
	'ID'          => $post_id,
	'post_status' => 'publish',
);
wp_update_post( $args );

get_post( $post=null, $output=OBJECT, $filter=’raw’) 取得文章资料

第一个参数指定要取得的文章的 ID,如果是在 post loop 中的话留空会自动取得当前文章。$output 资料格式可以设定为 OBJECT 或是 ARRAY_A (关联式阵列) 与 ARRAY_N(索引式阵列)。$filter 参数为设定要 sanitize 的模式,可选值有 raw、edit、db、display、attribute 与 js,可以做输出内容的过滤。

// get post - return post data as an object
$post = get_post( $post_id );
echo 'Object Title: ' . $post->post_title . '<br>';

// get post - return post data as an array
$post = get_post( $post_id, ARRAY_A );
echo 'Array Title: ' . $post['post_title'] . '<br>';

get_posts( $args = null ) 取得文章列表

当要取得文章清单时,像是最新文章列表、特定分类列表就可以使用这支函式。它是根据 WP_Query 类别所设计的。$args 参数可以用阵列的方式来提供文章取得条件,范例如下:

// get posts - return 100 posts
$posts = get_posts(
	array(
		'numberposts'      => '100', // 显示文章数量
		'category'         => 0,  // 文章分类 ID
		'orderby'          => 'date',  // 排序条件
		'order'            => 'DESC',  // 排序方式,DESC 为降幂,ASC 为升幂
		'include'          => array( 1, 2, 3 ),  // 只显示特定的文章
		'exclude'          => array( 4, 5, 6 ),  // 排除指定文章
		'meta_key'         => '',  // 显示带有指定栏位的文章
		'meta_value'       => '',  // 显示带有指定栏位值的文章
		'post_type'        => 'post',  // 文章类型
		'suppress_filters' => true,  // 如果需要使用 filter 来修改 get_posts 的结果,这个参数要设为 false
	)
);
// loop all posts and display the ID & title
foreach ( $posts as $post ) {
	echo $post->ID . ': ' . $post->post_title . '<br>';
}

wp_delete_post( $postid=0, $force_delete =false ) 删除指定文章

第一个参数为要删除的文章 ID,第二个参数如果为 true 的话,文章就不会进垃圾桶而直接被删除,代表连救回来的机会都没有,预设值为 false,范例如下:

// delete post - skip the trash and permanently delete it  
wp_delete_post( $post_id, true );

wp_postmeta

如果内建的文章栏位不够用,想要新增其他栏位的话就会写在这张表中。WordPress 提供很方便的作法让你不用再自行增加资料表,每个 postmeta 使用 post_id 来对应到文章。

如果你需要新增栏位但又不想让使用者在文章编辑画面看到该栏位,可以用下底线开头的栏位名称,这样 WordPress 会自动在後台隐藏这个栏位而不被使用者看见。

在开发金流外挂时会需要新增一些订单栏位,像是从金流商回传的订单资料、卡号末四码、以及相关资讯,而这些资料就可以放在 postmeta。

| # | column_name | data_type           | character_set | collation          | is_nullable | column_default | extra          | foreign_key | comment |
|---|-------------|---------------------|---------------|--------------------|-------------|----------------|----------------|-------------|---------|
| 1 | meta_id     | bigint(20) unsigned | NULL          | NULL               | NO          | NULL           | auto_increment |             |         |
| 2 | post_id     | bigint(20) unsigned | NULL          | NULL               | NO          | NULL           |                |             |         |
| 3 | meta_key    | varchar(255)        | utf8mb4       | utf8mb4_unicode_ci | YES         | NULL           |                |             |         |
| 4 | meta_value  | longtext            | utf8mb4       | utf8mb4_unicode_ci | YES         | NULL           |                |             |         |

相关函式如下:

get_post_meta( $post_id, $meta_key, $single=false ) 取得文章栏位值

第一个参数为文章 ID,第二个为栏位名称,如果留空值,会回传所有相同 $meta_key 的 $meta_value,$single 为 true 的话会以字串回传符合第一个 $meta_key 的 $meta_value,false 的话则会使用阵列回传所有符合的 $meta_value,范例如下:

// get post meta - get 1st instance of key
$student = get_post_meta( $post_id, 'irp_order', true );
echo 'oldest student: ' . $student;

update_post_meta( $post_id, $meta_key, $meta_value, $prev_value=’’) 更新文章栏位

需要注意的是第四个参数,如果 $meta_key 有多笔符合,在 $prev_value 为空的情况下,$meta_value 只会修改第一笔 $meta_key,如果 $prev_value 有符合多笔 $meta_key 的 $meta_value 其中一笔,则这一笔的 value 会被第三个 $meta_value 给取代掉。范例如下:

// update post meta - public metadata
$content = 'You SHOULD see this custom field when editing your latest post.';
update_post_meta( $post_id, 'irp_displayed_field', $content );

// update post meta - hidden metadata
$content = str_replace( 'SHOULD', 'SHOULD NOT', $content );
update_post_meta( $post_id, '_irp_hidden_field', $content );

add_post_meta( $post_id, $meta_key, $meta_value, $unique=false)

习惯上新增文章栏位会使用 update_post_meta(),因为它有判断 $meta_key 是否存在的功能,会用 add_post_meta 的情境在於要新增多笔 $meat_value 到相同的 $meta_key。第四个参数 $unique 为 true 时代表不允许有多笔相同的 $meta_key,范例如下:

add_post_meta( $post_id, 'irp_orders', $orders, true );

delete_post_meta( $post_id, $meta_key, $meta_value=’’ ) 删除文章栏位

第三个参数当有指定 $meta_value 只会删除符合该 value 的 $meta_key,范例如下

// delete post meta
delete_post_meta( $post_id, 'irp_student' );

wp_users

存放使用者帐密与基本资料,如果要从资料库修改使用者相关资讯,像是客户忘记密码或是要新增一个管理员帐号,就可以在这个表进行处理。

# column_name data_type character_set collation is_nullable column_default extra foreign_key comment
1 ID bigint(20) unsigned NULL NULL NO NULL auto_increment
2 user_login varchar(60) utf8mb4 utf8mb4_unicode_ci NO
3 user_pass varchar(255) utf8mb4 utf8mb4_unicode_ci NO
4 user_nicename varchar(50) utf8mb4 utf8mb4_unicode_ci NO
5 user_email varchar(100) utf8mb4 utf8mb4_unicode_ci NO
6 user_url varchar(100) utf8mb4 utf8mb4_unicode_ci NO
7 user_registered datetime NULL NULL NO 0000-00-00 00:00:00
8 user_activation_key varchar(255) utf8mb4 utf8mb4_unicode_ci NO
9 user_status int(11) NULL NULL NO NULL
10 display_name varchar(250) utf8mb4 utf8mb4_unicode_ci NO

wp_users 相关的函式定义在 wp-include/pluggable.php、wp-include/user.php,常用的如下:

wp_insert_user( $userdata ) 新增使用者

$userdata 为一个阵列,使用阵列来定义使用者的资料栏位,范例如下:

// insert user
$userdata = array(
	'user_login'    => 'oberon',
	'user_pass'     => '!@oberon!@#$',
	'user_nicename' => 'oberon',
	'user_url'      => 'https://oberonlai.blog/',
	'user_email'    => '[email protected]',
	'display_name'  => 'Oberon Lai',
	'nickname'      => 'Oberon',
	'first_name'    => 'Oberoni',
	'last_name'     => 'Lai',
	'description'   => 'This is a WordPress Administrator account.',
	'role'          => 'administrator',
);
wp_insert_user( $userdata );

wp_create_user( $username, $password, $email ) 新增使用者简易版

相较於 wp_insert_user(),它只要提供帐户名、密码、电子邮件三个参数就可以新增使用者,范例如下:

// create users
wp_create_user( 'oberon', '!@oberon!@#$', '[email protected]' );

wp_update_user( $userdata ) 修改 wp_users 与 wp_usermeta 里面的栏位

如果修改了使用者密码,所有的 cookie 会被清除,该使用者会强制登出,$userdata 的栏位与 wp_insert_user() 相同,范例如下:

// update user-change username fields and change role to admin
$userdata = array(
	'ID'         => $user->ID,
	'user_pass'  => '!@oberon!@#$',
	'first_name' => 'Oberon',
	'last_name'  => 'Lai',
	'user_url'   => 'https://oberonlai.blog',
	'role'       => 'administrator',
);
wp_update_user( $userdata );

如果要用这个函式来让使用者在前台更新登入密码,记得要在 init 执行,因为它会触发登出再登入的动作,这个动作需要清除与设定 Cookie,因此必须放在页面的最前面来执行,也就是要比 get_header() 还要前面。

此外,重设密码後的登入是分三个动作:wp_set_password()、wp_set_auth_cookie()、wp_set_current_user(),wp_update_user() 把这些动作都封装起来可以直接使用。

get_user_by( $field, $value ) 根据使用者资料来取得使用者

取得的使用者会以 WP_User 物件进行回传,这支的作用等同於 Sql 语句下:SELECT * FROM wp_users WHERE $field = $value,$field 是使用者资料栏位,$value 是使用者资料值,范例如下:

// get user by email
$user = get_user_by( 'email', '[email protected]' );
echo 'username: ' . $user->user_login . ' / ID: ' . $user->ID . '<br>';

wp_delete_user( $user_id, $reassign ) 删除使用者并把该使用者文章移转给其他人

第一个参数为要被删除的使用者 ID,第二个参数为要继承文章的使用者 ID,要注意的是如果第二个参数没有指定,则该使用者的文章都会被移除,范例如下:

// delete user-delete the original admin and set their posts to our new admin
wp_delete_user( 1, $user->ID );

wp_usermeta

记录使用者更多更详细的资料,也就是除了 wp_user 已经内建的栏位以外的使用者资料,可自行新增使用者栏位,相关函式常用的如下:

get_user_meta( $user_id, $meta_key, $single=false ) 取得特定使用者的资料

$user_id 为使用者 id,$meta_key 要取得的栏位,如果留空值,会回传所有相同 $meta_key 的 $meta_value,$single 为 true 的话会以字串回传符合第一个 $meta_key 的 $meta_value,false 的话则会使用阵列回传所有符合的 $meta_value,范例如下:

// get brian's id
$oberon_id = get_user_by( 'login', 'oberon' )->ID;
$oberons_wife = get_user_meta( $brian_id, 'oberon_wife', true); // Wife 可能有多个(?),true 的话回传第一个
echo "Oberon's wife: " . $oberons_wife . "<br>";

update_user_meta( $user_id, $meta_key, $meta_value, $prev_value=”” ) 更新使用者的资料

需要注意的是第四个参数,如果 $meta_key 有多笔符合,在 $prev_value 为空的情况下,$meta_value 只会修改第一笔\ $meta_key,如果 $prev_value 有符合多笔 $meta_key 的 $meta_value 其中一笔,则这一笔的 value 会被第三个 $meta_value 给取代掉。范例如下:

// update user meta - this will update oberon to oberon jr.
update_user_meta( $oberon_id, 'oberon_kid', 'Oberon Jr', 'Miffy' ); // Oberon 有多个小孩(多笔相同的 meta_key),名字叫做 Miffy 的会被修改为 Oberon Jr

add_user_meta( $user_id, $meta_key, $meta_value, $unique=false) 新增使用者资料栏位

习惯上新增使用者资料会使用 update_user_meta(),因为它有判断 $meta_key 是否存在的功能,会用 add_user_meta 的情境在於要新增多笔 $meat_value 到相同的 $meta_key。第四个参数 $unique 为 true 时代表不允许有多笔相同的 $meta_key,范例如下:

// add user meta - 3rd parameter is a unique value
add_user_meta( $oberon_id, 'oberon_kid', 'Dalya' );
add_user_meta( $oberon_id, 'oberon_kid', 'Oberon Jr' );
add_user_meta( $oberon_id, 'oberon_kid', 'Nina' );
add_user_meta( $oberon_id, 'oberon_kid', 'Cam' );
add_user_meta( $oberon_id, 'oberon_kid', 'Aksel' );

delete_user_meta( $user_id, $meta_key, $meta_value=””) 删除使用者资料

第三个参数当有指定 $meta_value 只会删除符合该 value 的 $meta_key,范例如下:

// delete oberon's user meta
delete_user_meta( $oberon_id, 'oberon_wife' );
delete_user_meta( $oberon_id, 'oberon_kid', 'Miffy' ); // 没指定 Miffy 的话则所有小孩都会被删除

关於 WordPress 资料库还有很多部分可以介绍,像是自订资料表以及全域变数 $wpdb 等等,现阶段我们把焦点先放在开发金流的设定页面上,下一篇会介绍 WooCommerce API。

本文同步发表於:https://oberonlai.blog/tw/woocommerce-payment-database/


<<:  Day 04 : 找不出的零钱 Non-constructible Change

>>:  [Day04] Wordpress

Python 演算法 Day 1 - 程序基础 & 简介

Chap.O 程序基础 & 简介: Part 1. 常用於演算法的开发程序,有以下几种: 1...

Day04:自我增进技术能力与观念的小方法

一、前言   上一篇文章的结尾有提到大家可以在职场上定时自我检视的小习惯,这边分享我自己维持几个月後...

事件回力镖 - 捕获与冒泡

传递机制听起来非常没有画面感,於是擅自替传递机制取了绰号叫回力镖,如同回力镖有去程,回程以及猎捕到猎...

Day28--Bootstrap&CSS文字排版&样式(6)

对元素设置.font-weight-normal可以让文字和符号以预设呈现。 <p clas...

[Day11] CH08:积沙成塔——Array & ArrayList(上)

很快地已经学了十天,今天又是一个新的开始,今天要来认识「阵列」。 阵列(Array)是由同型别的相关...