Physical Address

304 North Cardinal St.
Dorchester Center, MA 02124

利用 add_meta_box 自行新增自訂欄位

實作目的

為了讓客戶可以在頁面層級加入要使用的 MW WP Form ID,同時想要跳過學習 ACF 以外的技能,所以使用 add_meta_box 來增加自訂欄位。

實作結果

步驟一:註冊欄位名稱

註冊欄位需要使用 add_meta_boxes 這個勾點(有關勾點的介紹,可以參考這篇文章)。

根據官方的開發者文件,add_meta_boxes 這個勾點中,需要透過 add_meta_box 這個函式,將欲註冊的欄位資訊填入:

add_meta_box(
‘註冊 ID (須唯一,避免與其他外掛衝突)‘,
‘欄位標題 (顯示於編輯頁中)’,
‘回傳函式 (也可以直接寫 function(){}),設定欄位樣式用’,
‘欲註冊的文章類型,可以是陣列’,
‘位置,side 代表在側邊’);

<?php
function reservation_register_meta( ) {
	global $post;
	if ( 'page-shopreservations.php' != get_post_meta( $post->ID , '_wp_page_template', true ) ) {
		return;
	} else {
		add_meta_box(
			'reservation-form-id',
			'MW WP Form ID',
			'reservation_meta_callback',
			'page',
			'side' );
	}
}
add_action( 'add_meta_boxes', 'reservation_register_meta' );

這裡還使用了 global $post 的全域變數,用來判斷只在特定的文章範本 (page-shopreservations.php) 中才會顯示自訂欄位。跟 ACF 的邏輯判斷一樣。

步驟二:設定欄位樣式

在步驟一中,add_meta_box 的第三個參數是回傳函式,我們要在這裡設定自訂欄位的樣式。

首先設定 $value 這組變數,用來查詢這篇文章是否已經存有自訂欄位的值。為了避免欄位值被注入怪怪的程式碼,因此使用 esc_attr 進行迴避。

function reservation_meta_callback( $post ) {
	$value = get_post_meta( $post->ID, '_reservation_id', true);
	wp_nonce_field( 'reservation_inner', 'reservation_inner_nonce');
	?>
	<label for="reservation_id">ID of MW WP Form</label>
	<input type="text" id="reservation_id" name="reservation_id" class="postbox" value="<?php echo esc_attr( $value );?>">
<?php }

這邊需要注意的地方是 wp_nonce_field 這個函式,nonce 在密碼學中是一組「只用一次」的加密數字 (a “number” that can be used only “once”),在這裡是用來確保資料只能透過這裡的表單傳遞,避免有心人士透過程式碼鑽漏洞,是在 WordPress 中必要的基本防護措施。

這裡使用 wp_nonce_field( ‘reservation_inner’, ‘reservation_inner_nonce’ ); 這個函式,定義將 ‘reservation_inner’ 這個行為進行加密,產生一組名為 reservation_inner_nonce 的 nonce。

步驟三:更新欄位資料 wp_postmeta

最後這個步驟較為複雜,是參考 WordPress 官方文件寫的。

在將自訂欄位的資料存入 save_post 這個鉤點以前,先檢查是否有 reservation_inner_nonce,並進一步檢查 reservation_inner_nonce 是否與我們定義的 reservation_inner 這個動作發生在同一個工作階段。

function reservation_save_meta( $post_id ) {
	if ( !isset( $_POST['reservation_inner_nonce'] ) ) {
		return $post_id;
	}
	$nonce = $_POST['reservation_inner_nonce'];
	if ( !wp_verify_nonce( $nonce, 'reservation_inner' ) ) {
		return $post_id;
	}
	/*
	* If this is an autosave, our form has not been submitted,
	* so we don't want to do anything.
	*/
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return $post_id;
	}
	// Check the user's permissions.
	if ( 'page' == $_POST['post_type'] ) {
		if ( ! current_user_can( 'edit_page', $post_id ) ) {
			return $post_id;
		}
	} else {
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return $post_id;
		}
	}
	$data = sanitize_text_field( $_POST['reservation_id'] );
	update_post_meta( $post_id, '_reservation_id', $data );
}
add_action( 'save_post', 'reservation_save_meta' );

第二段在避免文章在自動儲存時,將自訂欄位值存入。第三段則是確保使用者有編輯文章或頁面的權限。最後透過 sanatize_text_field 來避免跨網站指令碼 (XSS,Cross-site Scripting) 的攻擊。

最後透過 update_post_meta 這個函式,在儲存文章時,將 name 屬性值為 reservation_id 的值同時存入 _reservation_id 這個中繼資料 (meta data) 裡。

將上面三個步驟全部組合起來之後就如下:

<?php
function reservation_register_meta( ) {
	global $post;
	if ( 'page-shopreservations.php' != get_post_meta( $post->ID , '_wp_page_template', true ) ) {
		return;
	} else {
		add_meta_box(
			'reservation-form-id',
			'MW WP Form ID',
			'reservation_meta_callback',
			'page',
			'side' );
	}
}
add_action( 'add_meta_boxes', 'reservation_register_meta' );
function reservation_meta_callback( $post ) {
	$value = get_post_meta($post->ID, '_reservation_id', true);
	wp_nonce_field( 'reservation_inner', 'reservation_inner_nonce');
	?>
	<label for="reservation_id">ID of MW WP Form</label>
	<input type="text" id="reservation_id" name="reservation_id" class="postbox" value="<?php echo esc_attr( $value );?>">
<?php }
function reservation_save_meta( $post_id ) {
	if ( !isset( $_POST['reservation_inner_nonce'] ) ) {
		return $post_id;
	}
	$nonce = $_POST['reservation_inner_nonce'];
	if ( !wp_verify_nonce( $nonce, 'reservation_inner' ) ) {
		return $post_id;
	}
	/*
	* If this is an autosave, our form has not been submitted,
	* so we don't want to do anything.
	*/
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return $post_id;
	}
	// Check the user's permissions.
	if ( 'page' == $_POST['post_type'] ) {
		if ( ! current_user_can( 'edit_page', $post_id ) ) {
			return $post_id;
		}
	} else {
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return $post_id;
		}
	}
	$data = sanitize_text_field( $_POST['reservation_id'] );
	update_post_meta( $post_id, '_reservation_id', $data );
}
add_action( 'save_post', 'reservation_save_meta' );

結語

自己實作了一次之後,更能了解 ACF 這款外掛是何等強大。上面整串程式碼,使需要透過幾個步驟點擊,就可以用 1/10 的時間透過 ACF 完成這項任務。尤其 ACF Pro 支援複雜的條件式,以及重複欄位 (repeated fields) 等進階功能,大多數的狀況,真的不建議自己實作出來。

至於什麼樣的情況之下,會選用非 ACF 的方式來實作自訂欄位呢?我認為大概有這三個原因:

  1. 只需要單一欄位,不希望為了單一欄位安裝整個 ACF 外掛。
    ACF 的功能強大,但是很多時候你的網站可能並不需要這麼多自訂欄位。如果你只是希望新增單一欄位的話,自己實作會是一個不錯的選擇。
  2. 想要開發免費外掛/佈景主題,免費的 ACF 功能卻無法滿足進階需求。
    根據 ACF 的授權,如果是付費的外掛或是佈景主題,開發者可以將原始碼中引入 ACF Pro 的原始碼,進行再開發。ACF Pro 的功能包含上述的重複欄位,以及圖庫與彈性內容 (flex contents) 等進階功能。
  3. 想要開發外掛/佈景主題,但是希望降低與其他外掛的相依性。
    這部分可能比較跟開發者的習慣有關。

除了上面的 3 種情形外,我認為如果要實作自訂欄位的話,ACF 還是比較好的選擇,畢竟都選用 WordPress 了,當然要盡量避免重新造輪的情況嘍!

參考資料

Eric Chuang
Eric Chuang

正職是廣告行銷人員,因為 Google Tag Manager 的關係開始踏入網站製作的領域,進一步把 WordPress 當成 PHP + HTML + CSS + JavaScript 的學習教材。此外,因為工作的關係,曾經用 Automattic 的 Underscores (_s) 替客戶與公司官網進行全客製化佈景主題開發。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料