WordPress Child Theme
Tags: wordpress
Published: 2023-10-12 05:53:21

## Theme

Theme 的檔案由許多 PHP 模板構成，而這些模板之間是有階層關係存在的，階層關係可以參考 [The WordPress Template Hierarchy](https://wphierarchy.com/)。

WordPress 會根據當前訪客的訪問頁面嘗試以該頁面歸屬之模板檔內容組版出完整的網頁回應給訪客，如果專屬的模板不存在，則會退一步抓取通用模板組版回應。

舉例來說，訪客訪問一篇網誌，對照階層表，WordPress 會首先找 single-post.php 作為模板回應，但假設 single-post.php 不存在，則會找 single.php，如果還是沒有，繼續找 singular.php，再沒有就用 index.php，當然往往越通用的模板越不可能是恰當的回應內容，上述只是舉例用於解釋 WordPress 的模板階層與回退機制（fall-back）而已。

## Child theme

一般的 theme 都會常態性的更新，因此如果要客製，不建議直接魔改 theme 的檔案，以免 theme 更新把我們的修改覆蓋掉，在 WordPress 有設計 child theme 的機制，child theme 可以繼承 parent theme 的模板與函式，並且可以自由覆蓋 parent theme 任意的模板或函式。

上面提到 WordPress 對模板檔的階層與回退機制，如果再把 child theme 的概念加入，依然是同樣的原則，會先去 child theme 找與當前請求頁面較匹配的模板檔，例如網誌會去 child theme 內的 single-post.php，若 child theme 沒有則會到 parent theme 找 single-post.php，再沒有才會去找 child theme 的 single.php，再沒有去找 parent theme 的 single.php，以此類推。

## 建立 child theme 目錄與檔案

1. 在 wp-content/theme/ 新建一個 storefront-child 資料夾，資料夾名字可以自取，可以識別就好，這個 storefront-child 就是我們的專案資料夾，也是 Git 專案資料夾。
2. 在專案資料夾內建立 functions.php 和 style.css。
3. 截至目前 wp-content/ 目錄結構會長這樣：

```
wp-content/
├─ languages/
├─ mu-plugins/
├─ plugins/
├─ themes/
│  ├─ storefront/
│  └─ storefront-child/
│     ├─ style.css
│     └─ functions.php
├─ upgrade/
└─ uploads/
```

## 在樣式表寫入 child theme 基本資料

截至目前檔案都還沒有內容，在 style.css 填入一些基本資料：

```css
/*
Theme Name:   Storefront Child
Theme URI:    http://example.com/wordpress-theme-child/
Description:  WordPress Child Theme
Author:       Leon
Author URI:   http://example.com
Template:     storefront
Version:      1.0
License:      GNU General Public License v2 or later
License URI:  http://www.gnu.org/licenses/gpl-2.0.html
Tags:         WooCommerce
Text Domain:  storefrontchild
*/
```

- Theme Name：不可與其它既有的佈景主題同名，必填。
- Theme URI：非必填。
- Description：非必填。
- Author：非必填。
- Author URI：非必填。
- Template：parent theme 的目錄名。
- Version：非必填。
- License：非必填。
- License URI：非必填。
- Tags：非必填。
- Text Domain：此主題在程式碼內的代稱，某些地方會用到，非必填。

這些基本資料對 CSS 來說是註解，但對 WP 來說是作為辨識 child theme 資訊的欄位。

## 在 functions.php 載入 parent theme 的樣式表

目前的 child theme 不管是樣式或頁面都是空白，雖然 WordPress 對 child theme 的模板有回退機制，但對 CSS 檔案而言這樣的機制並不生效，所以要透過 functions.php 內的函數把 parent theme 的樣式表載入。

在 child theme 的資料夾內編輯 functions.php：

```php
<?php
/* enqueue script for parent theme stylesheeet */
function childtheme_parent_styles() {

    // enqueue style
    wp_enqueue_style('parent', get_template_directory_uri().'/style.css');
}
add_action('wp_enqueue_scripts', 'childtheme_parent_styles');
```

截至目前為止，雖然還是沒有自己的樣式與模板，但因為 WordPress 的模板回退機制，以及我們也載入了 parent theme 的 CSS，所以在 WordPress Admin 勇敢的把佈景主題切換成 child theme 後，一切如常，頁面、樣式都沒跑掉。

## 客製模板

從 parent theme 複製想改的模板檔到 child theme 再做修改，確保與為異動的部分仍然與 parent theme 保持一致以確保相容性。

## 客製樣式

直接在 child theme 的樣式表做編修。

## 客製函式

客製函式前，必須先確定要做的功能是應該在 theme 內實現還是應該在 plugin 內實現，一般會以功能的目的來判斷，如果是與外觀內容無關的部分，會以 plugin 實現；反之若是與 theme 外觀或內容相關的部分，會以 theme 實現。

WordPress 內預埋了大量的 hook 點，這些 hook 點會在頁面組版產生的時候依序執行。我們客製的函式會透過把函式註冊到 hook 點的方式讓函式在特定的位置生效。

WordPress hook 又可分為 action hook 與 filter hook，兩者差別可參考別人寫的優文〈[WordPress的Hook機制與原理](https://audilu.com/2011/10/10/wordpress-hook/)〉。

客製函式的一般規則：

- 如果要添加的函式與 parent theme 無關，可自由添加。
- 如果是要變更／追加於 parent theme 內的函式，則可以有三種途徑：
    - 對 pluggable function，可直接以同樣的函式名建立於 child theme 的 functions.php 內，可完全取代 parent theme 的同名函式。
    - 在 child theme 內定義一組函式，把 parent theme 的目標函式解除 hook。
    - 附加到 parent theme 的既有函式後執行。

## 取代 pluggable function

Pluggable function 是一種被判斷式包住的函式，判斷式用於確認是否已經有同名函式存在，依照 WordPress 的規則，child theme 的 functions.php 會先被載入，因此 parent theme 的 pluggable function 的檢查式會發現已經有同名函式存在，而採用 child theme 的同名函式並忽略 parent theme 的同名函式。

在 storefront/inc/storefront-template-functions.php 有個 `storefront_credit()` 就是 pluggable function，它結構長這樣：

```php
if ( ! function_exists( 'storefront_credit' ) ) {
	function storefront_credit() {
		// contents for function here
	}
}
```

可以看到在 `storefront_credit()` 外面還包了一層判斷式用於確認是否已經有同名的 `storefront_credit` 函式存在，沒有的話才會執行下面的 `storefront_credit()`。

## 解除 parent theme 的 hook

假設在 parent theme 有一函式：

```php
function parent_function() {
 //contents of function here
}
add_action( 'init', 'parent_function', 20 );
```

可以在 child theme 定義解除它的函式：

```php
function remove_parent_function() {
 remove_action( 'init', 'parent_function', 20 );
}
add_action( 'wp_head', 'remove_parent_function' );
```

上面是以 action hook 為例，如果是 filter hook 也是以此類推，把 `remove_action()` 改成 `remove_filter()` 即可。

## 附加到既有的函式後

最後一種模式，不異動 parent theme 既有函式，而是隨後執行。假設在 parent theme 有組函式用於產生 footer 的內容：

```php
function parent_footer_content() {
 // content of function here
}
add_action( 'parent_footer', 'parent_footer_content' );
```

我們可以在 child theme 定義另一組函式並 hook 到同一個位置，以及確保 child theme 的 priority 大於上面的 parent theme 函式，上面的 parent theme 函式沒有指定 priority，所以會被 WordPress 認定為 10。

Child theme 函式內容如下：

```php
function child_footer_extra_content() {
 // contents of function here
}
add_action( 'parent_footer', 'child_footer_extra_content', 20 );
```

## 參考文件

- [How to Create a Child Theme in WordPress (Extended Guide)](https://kinsta.com/blog/wordpress-child-theme/)
- [How to Create Child Theme in WordPress – Tutorial for Beginners](https://blog.templatetoaster.com/how-to-create-child-theme-wordpress/)
- [How to Create and Customize a WordPress Child Theme](https://www.hostinger.com/tutorials/how-to-create-wordpress-child-theme)