關於付費外掛的更新部署,我都是透過 Appsero 來完成,其他類似的服務還有 Freemius,如果是免費外掛的話可以直接上架 WordPress(.)org 進行託管,那麼在什麼情境下會需要自行託管呢?
如果你跟我一樣遇到上述服務不適合你的使用情境,又不想為了上架 .org 而花費時間去符合規範,又或者是你希望設計自己的更新流程而不想用別人寫好的框架,那麼這篇文章提及的工具可以作為一個基礎架構,了解整個程式碼的運作邏輯後,可以根據需求再自行客製化,這樣就不用被第三方服務所約束。
我的情境很單純:讓使用者可以在 WordPress 後台外掛頁面更新我所提供的免費外掛。
一、事前準備
需要一個主機空間存放外掛的資訊檔以及新版外掛的 zip 檔,可以使用 Dropbox 或是 Google 雲端硬碟之類的空間,也可以放在自己的伺服器上,我最推薦的是放在 Github,這樣就可以跟版本控管的流程整合。
外掛資訊檔是一個 JSON 格式的文件,範例如下:
{
"name": "My Plugin",
"slug": "my-plugin",
"author": "<a href='https://mydomain.com/my-plugin/'>Author</a>",
"author_profile": "https://mydomain.com",
"version": "1.0.1",
"download_url": "https://mydomain.com/my-plugin.zip",
"requires": "5.6",
"tested": "6.2.2",
"requires_php": "7.4",
"added": "2021-04-05 00:00:00",
"last_updated": "2023-06-29 00:00:00",
"homepage": "https://mydoamin.com",
"sections": {
"description": "My plugin description",
"installation": "My plugin installation",
"changelog": "<p>v1.0.1</p><p>Change log</p>"
},
"banners": {
"low": "https://mydomain.com/my-plugin-banner-low.jpg",
"high": "https://mydomain.com/my-plugin-banner-high.jpg"
}
}
這份檔案可以命名為 my-plugin.json
然後放在自己的空間,上傳後的路徑我們會拿來作為取得新版外掛的依據,該資訊檔的關鍵是 version
以及 download_url
這兩個參數。如果要推播新的更新通知,只要把 version
版本號升版即可。
而 download_url
是新版外掛的下載位置,如果放在 Github 上面的話可以使用 Release 來產出 zip,搭配 Github Actions 還可以自動打包不用手動處理。
二、更新邏輯與相關技術
處理 WordPress 外掛主要有三個勾點,分別是 plugins_api
、site_transient_update_plugins
以及 upgrader_process_complete
。WordPress 判斷是否有外掛需要更新依賴的是資料庫暫存 Transient API。
在 wp_options
資料表裡面有一個 _site_transient_update_plugins
欄位,裡面以序列化的方式記錄了所有外掛的相關資訊。其中一個參數叫做 new_version
,當 WordPress 讀到我們的外掛版本小於 new_version
時就會觸發更新通知,並且以 package
參數中的網址作為新版外掛的下載連結:
當使用者進入 WordPress 後台的外掛頁或是更新頁時,會觸發 get_transient()
方法,其中勾點 site_transient_update_plugins
就可以拿來修改上面這一堆序列化的內容,具體的邏輯是:
- 在
site_transient_update_plugins
裡面取得放在我們主機上的外掛資訊檔my-plugin.json
- 檢查
my-plugin.json
中的version
是否比目前外掛的版本號高 - 是的話取得
my-plugin.json
中的version
與download_url
資訊 - 改寫
_site_transient_update_plugins
中my-plugin
的new_verison
與pacekage
- 使用者收到外掛的更新通知並點擊更新
簡化過後的程式碼如下:
<?php
add_filter(
'site_transient_update_plugins',
function( $transient ) {
// 步驟一
$remote = wp_remote_get(
'https://mydomain.com/my-plugin.json',
array(
'timeout' => 60,
'headers' => array(
'Accept' => 'application/json',
),
)
);
if ( is_wp_error( $remote ) || 200 !== wp_remote_retrieve_response_code( $remote ) || empty( wp_remote_retrieve_body( $remote ) ) ) {
return false;
}
$remote = json_decode( wp_remote_retrieve_body( $remote ) );
// 步驟二
if ( $remote &&
version_compare( '1.0.0', $remote->version, '<' ) &&
version_compare( $remote->requires, get_bloginfo( 'version' ), '<=' ) &&
version_compare( $remote->requires_php, PHP_VERSION, '<' )
) {
// 步驟三&四
$response = new \stdClass();
$response->slug = 'my-plugin';
$response->plugin = 'my-plugin/my-plugin.php';
$response->new_version = $remote->version;
$response->package = $remote->download_url;
$transient->response[ $response->plugin ] = $response;
}
return $transient;
}
);
需要注意的是步驟二的第二個判斷是 version_compare( '1.0.0', $remote->version, '<' )
的第一個參數要換成你目前外掛的版本號,通常我會用常數來定義,這樣在更版時就會比較好控制,第三與第四個判斷是檢查使用者的 WordPress 與 PHP 版本,要符合 my-plugin.json
裡面定義的最低版本要求才會收到更新通知。
三、Composer 套件
除了主要的更新機制外,還有顯示新版資訊以及更新檔安裝完成後的處理,完整的程式碼可以參考 Misha Rudrastyh 大大的文章,為了方便使用,我把它加以整理後做成 Composer 套件,使用方法如下:
透過 Composer 進行安裝:
$ composer require oberonlai/wp-updater
使用時建立 Updater
物件並提供三個參數,分別是外掛代稱、目前外掛版本以及外掛資訊檔的下載路徑:
<?php
$updater = new ODS\Updater( array(
'plugin_slug' => 'my-plugin',
'version' => '1.0.0',
'json_url' => 'https://mydomain.com/my-plugin.json',
));
另外我還在外掛更新後增加了一個勾點 ods_updater_after_purge
,這是讓開發者可以在外掛更新完成後加入一些任務的處理,像是更新資料表、跳出提醒通知等行為,都可以透過該勾點進行處理。
具體用法可以參考 Github 存放庫的說明:https://github.com/oberonlai/wp-updater
有個這個框架後接下來要託管付費外掛也能以此基礎繼續發展下去,像是在外掛下載連結可以帶入序號,當伺服器收到請求後就可以驗證該序號是否由已授權的網站發出的,確認沒問題才能下載,也可以整合私有存放庫讓部署流程更加自動化,這部分待日後實作後再分享出來~