
在開發 WordPress 外掛時,一個關鍵步驟是預裝重要資料,確保外掛從一開始就能順利執行。以活動管理器外掛為例。安裝時,如果外掛能自動生成一個名為 “Upcoming Events” 的頁面,顯示未來活動的列表,這將大有裨益。
這個預先配置好的頁面嵌入了類似 [event_list number="10" scope="future" status="publish"]
這樣的簡碼,讓使用者無需閱讀文件就能立即利用外掛的功能。
安裝資料不僅在首次安裝外掛時有幫助,在隨後更新外掛時也有幫助。例如,如果更新引入了日曆檢視功能,外掛可以自動建立一個新頁面 “Events Calendar“,通過 [event_calendar status="publish"]
這樣的簡碼來展示這一新增功能。
一般來說,資料安裝的範圍涵蓋各種需求:
- 生成具有特定標題和內容的新頁面。
- 為外掛建立的自定義帖子型別(CPT)新增條目。
- 在
wp_options
表中插入預設設定
- 為使用者角色分配新功能
- 為外掛提供的新功能或更新功能向使用者分配後設資料(例如,使用者可更改事件日期格式,並首先為所有使用者新增預設值)
- 建立外掛上下文中常用的類別,如 “conferences” 或 “sports”。
安裝資料必須是一個漸進的過程,否則我們可能會建立重複的條目。
例如,如果外掛 1.1 版引入了 “Upcoming Events” 頁面,而使用者從 1.0 版進行了更新,則只應安裝與 1.1 版相關的新資料。這種增量式更新可確保在 1.2 版推出日曆功能時,只新增新的 “Events Calendar” 頁面,避免與 “Upcoming Events” 頁面重複。
因此,在更新時,外掛必須檢索之前安裝的版本,並只安裝與新版本相對應的資料。
本文將解釋如何在 WordPress 外掛中安裝初始資料,並在進一步更新時不斷新增新資料。
提供當前版本
為了處理增量過程,外掛必須跟蹤其當前版本,通常在外掛主檔案的標頭檔案中宣告。當然,我們不能直接引用它,因為它在 PHP 註釋中。因此,我們還需要在變數中定義該值,並將其提供給負責初始化和配置的 Plugin
類:
// Same version as in the header
new Plugin($pluginVersion)->setup();
<?php
/*
Plugin Name: My plugin
Version: 1.6
*/
// Same version as in the header
$pluginVersion = '1.6';
new Plugin($pluginVersion)->setup();
<?php
/*
Plugin Name: My plugin
Version: 1.6
*/
// Same version as in the header
$pluginVersion = '1.6';
new Plugin($pluginVersion)->setup();
Plugin
類利用 PHP 8.0 的建構函式屬性推廣功能,儲存了這個版本,以便我們以後引用:
public function __construct(
protected string $pluginVersion,
public function setup(): void
// Initialization logic here...
<?php
class Plugin {
public function __construct(
protected string $pluginVersion,
) {}
public function setup(): void
{
// Initialization logic here...
}
// ...
}
<?php
class Plugin {
public function __construct(
protected string $pluginVersion,
) {}
public function setup(): void
{
// Initialization logic here...
}
// ...
}
請注意,初始化和配置外掛的邏輯被新增到了 setup
方法中,而不是建構函式中。這是因為建構函式必須避免產生副作用;否則,我們在擴充套件或組合 Plugin
類時可能會產生錯誤。
讓我們來看看這種情況是如何發生的。假設我們最終新增了一個 BetterPlugin
類,該類由 Plugin
類的功能組成:
public function printSomething(): string
$plugin = new Plugin($pluginVersion);
return '<div class="wrapper">' . $plugin->printSomething() . '</div>';
class BetterPlugin {
public function printSomething(): string
{
$pluginVersion = '1.0';
$plugin = new Plugin($pluginVersion);
return '<div class="wrapper">' . $plugin->printSomething() . '</div>';
}
}
class BetterPlugin {
public function printSomething(): string
{
$pluginVersion = '1.0';
$plugin = new Plugin($pluginVersion);
return '<div class="wrapper">' . $plugin->printSomething() . '</div>';
}
}
每當在 printSomething
內執行 new Plugin()
時,都會建立一個新的 Plugin
例項。如果將配置邏輯新增到建構函式中,那麼每次建立新的 Plugin
物件時都會執行配置邏輯。在我們的例子中,我們只想建立一次 “Upcoming Events” 頁面,而不是多次。通過在設定方法中新增邏輯,我們可以避免這個問題。
跟蹤以前的版本
WordPress 沒有提供方便的方法來檢索被替換外掛的版本。因此,我們必須自己在資料庫的 wp_options
表中儲存這個值。
將版本儲存在 ” myplugin_version
” 條目下,其中 myplugin_
是外掛的名稱(例如 eventsmanager_version
)。重要的是,在所有設定前都要加上 myplugin_
,以避免潛在衝突,因為我們無法確保其他外掛不會新增 version
選項。
在每次請求中載入外掛時, Plugin
已經知道當前的版本(來自前面的屬性 $pluginVersion
),並將從資料庫中檢索上次儲存的版本。這種比較決定了外掛的狀態:
- 新安裝:檢測資料庫中是否缺少外掛的版本條目,表示首次安裝(即
$storedPluginVersion
為null
)
- 更新:噹噹前版本超過資料庫儲存的版本時識別,表明需要升級。
- 否則,沒有變化
無論何時發生變化,我們都會呼叫 prepareAndInstallPluginSetupData
來安裝相應的資料,無論是新安裝(這種情況下必須安裝所有版本的所有資料)還是更新(只安裝所有新版本的資料)。可為空的 $previousVersion
變數會指示是哪種情況( $previousVersion
為 null
=> 新安裝)。
呼叫此方法後,我們還必須在資料庫中儲存當前外掛版本,使其成為新的 “最後儲存” 版本。這項工作必須在呼叫 prepareAndInstallPluginSetupData
之後完成,這樣,如果該方法出錯(如丟擲 RuntimeException
)且資料未安裝,資料庫中仍會儲存上一版本,並在下一次請求時嘗試新一輪安裝資料。
public function setup(): void
$this->managePluginDataVersioning();
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
protected function managePluginDataVersioning(): void
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
update_option($myPluginVersionOptionName, $this->pluginVersion);
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
<?php
class Plugin {
// ...
public function setup(): void
{
if (!is_admin()) {
return;
}
$this->managePluginDataVersioning();
}
/**
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
*/
protected function managePluginDataVersioning(): void
{
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
return;
}
\add_action(
'init',
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
// Update on the DB
update_option($myPluginVersionOptionName, $this->pluginVersion);
}
);
}
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
// Installation logic...
}
}
<?php
class Plugin {
// ...
public function setup(): void
{
if (!is_admin()) {
return;
}
$this->managePluginDataVersioning();
}
/**
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
*/
protected function managePluginDataVersioning(): void
{
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
return;
}
\add_action(
'init',
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
// Update on the DB
update_option($myPluginVersionOptionName, $this->pluginVersion);
}
);
}
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
// Installation logic...
}
}
請注意, prepareAndInstallPluginSetupData
(以及隨後的資料庫更新)是在 init
操作鉤子上執行的。這是為了確保內容管理系統中的所有資料都已準備好供檢索和操作。
特別是,在 init
鉤子之前,不能訪問分類標準(標籤和類別)。如果外掛的安裝過程需要建立 CPT 條目併為其分配自定義類別,則該過程只能從 init
鉤子開始執行。
從效能角度看,每次請求都從資料庫訪問上次儲存的版本並不理想。為了改善這種情況,可以將外掛所需的所有選項合併到一個陣列中,將它們儲存在一個條目中,然後通過對資料庫的一次呼叫訪問所有選項。
例如,如果外掛還需要儲存一個 myplugin_date_format
選項來顯示事件日期,我們可以建立一個具有 version
和 date_format
屬性的單一條目 myplugin_options
。
要訪問上次儲存的版本,PHP 程式碼必須進行如下調整:
protected function managePluginDataVersioning(): void
$myPluginOptionsOptionName = 'myplugin_options';
$myPluginOptions = get_option($myPluginOptionsOptionName, []);
$storedPluginVersion = $myPluginOptions['version'] ?? null;
function () use ($myPluginOptionsOptionName, $myPluginOptions): void {
$myPluginOptions['version'] = $this->pluginVersion;
update_option($myPluginOptionsOptionName, $myPluginOptions);
<?php
class Plugin {
// ...
protected function managePluginDataVersioning(): void
{
$myPluginOptionsOptionName = 'myplugin_options';
$myPluginOptions = get_option($myPluginOptionsOptionName, []);
$storedPluginVersion = $myPluginOptions['version'] ?? null;
// ...
\add_action(
'init',
function () use ($myPluginOptionsOptionName, $myPluginOptions): void {
// ...
// Update on the DB
$myPluginOptions['version'] = $this->pluginVersion;
update_option($myPluginOptionsOptionName, $myPluginOptions);
}
);
}
}
<?php
class Plugin {
// ...
protected function managePluginDataVersioning(): void
{
$myPluginOptionsOptionName = 'myplugin_options';
$myPluginOptions = get_option($myPluginOptionsOptionName, []);
$storedPluginVersion = $myPluginOptions['version'] ?? null;
// ...
\add_action(
'init',
function () use ($myPluginOptionsOptionName, $myPluginOptions): void {
// ...
// Update on the DB
$myPluginOptions['version'] = $this->pluginVersion;
update_option($myPluginOptionsOptionName, $myPluginOptions);
}
);
}
}
避免同時請求安裝重複資料
如果兩個或兩個以上的使用者在同一時間訪問 wp-admin,安裝過程可能會被觸發一次以上。為避免同一資料被安裝兩次或更多次,我們使用暫態作為標記,只允許第一個請求安裝資料:
- 檢查暫存器
myplugin_installing_plugin_setup_data
是否存在(再次強調,該名稱必須以 myplugin_
作為字首);如果存在,則什麼也不做(因為其他程序正在安裝資料)。
- 否則,在資料庫中儲存瞬時資料,以安裝資料所需的合理最長時間(例如 30 秒)
- 安裝資料
- 刪除暫存資料
程式碼如下:
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might execute the logic
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
protected function installPluginSetupData(?string $previousVersion): void
<?php
class Plugin {
// ...
/**
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might execute the logic
*/
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
return;
}
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
}
protected function installPluginSetupData(?string $previousVersion): void
{
// Do something...
}
}
<?php
class Plugin {
// ...
/**
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might execute the logic
*/
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
return;
}
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
}
protected function installPluginSetupData(?string $previousVersion): void
{
// Do something...
}
}
安裝所有版本的資料
如前所述,如果更新外掛,我們必須只安裝新版本的資料,而不是所有版本的資料。這意味著我們需要按版本管理要安裝的資料。
在下面的程式碼中,陣列 $versionCallbacks
表示每個版本要執行的函式,該函式執行安裝資料的邏輯。我們遍歷所有版本的列表,使用 version_compare
將每個版本與上一個版本進行比較,如果比較結果大於上一個版本,則執行相應的函式安裝相應的資料。
請注意,如果 $previousVersion
為null
(即新安裝的版本),則會執行所有函式。
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
* The plugin's setup data will be installed if:
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
protected function installPluginSetupData(?string $previousVersion): void
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
foreach ($versionCallbacks as $version => $callback) {
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
protected function installPluginSetupDataForVersion1Dot1(): void
protected function installPluginSetupDataForVersion1Dot2(): void
class Plugin {
/**
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
*
* The plugin's setup data will be installed if:
*
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
*/
protected function installPluginSetupData(?string $previousVersion): void
{
$versionCallbacks = [
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
// ... Add more versions
];
foreach ($versionCallbacks as $version => $callback) {
/**
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
*/
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
continue;
}
$callback();
}
}
protected function installPluginSetupDataForVersion1Dot1(): void
{
// Do something...
}
protected function installPluginSetupDataForVersion1Dot2(): void
{
// Do something...
}
}
class Plugin {
/**
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
*
* The plugin's setup data will be installed if:
*
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
*/
protected function installPluginSetupData(?string $previousVersion): void
{
$versionCallbacks = [
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
// ... Add more versions
];
foreach ($versionCallbacks as $version => $callback) {
/**
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
*/
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
continue;
}
$callback();
}
}
protected function installPluginSetupDataForVersion1Dot1(): void
{
// Do something...
}
protected function installPluginSetupDataForVersion1Dot2(): void
{
// Do something...
}
}
為每個特定版本安裝資料
最後,我們必須為每個版本安裝實際資料(建立頁面、CPT 條目、新增選項等)。
在這段程式碼中,我們為事件管理器外掛新增了適用於 v1.1
版本的 “Upcoming Events” 頁面:
protected function installPluginSetupDataForVersion1Dot1(): void
'post_status' => 'publish',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future"]',
class Plugin {
// ...
protected function installPluginSetupDataForVersion1Dot1(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future"]',
]);
}
// ...
}
class Plugin {
// ...
protected function installPluginSetupDataForVersion1Dot1(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future"]',
]);
}
// ...
}
然後,我們為 v1.2
版建立 “Events Calendar” 頁面(在本例中,使用頁面上的 Gutenberg 區塊,新增名為 event-calendar
的自定義塊):
protected function installPluginSetupDataForVersion1Dot2(): void
'post_status' => 'publish',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
'blockName' => 'myplugin/event-calendar',
class Plugin {
// ...
protected function installPluginSetupDataForVersion1Dot2(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
[
'blockName' => 'myplugin/event-calendar',
'attrs' => [
'status' => 'publish',
],
'innerContent' => [],
],
]),
]);
}
}
class Plugin {
// ...
protected function installPluginSetupDataForVersion1Dot2(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
[
'blockName' => 'myplugin/event-calendar',
'attrs' => [
'status' => 'publish',
],
'innerContent' => [],
],
]),
]);
}
}
將所有程式碼整合在一起
我們完成了!Plugin
類的全部 PHP 程式碼如下,其中包含跟蹤外掛版本和安裝相應資料的邏輯:
public function __construct(
protected string $pluginVersion,
public function setup(): void
$this->managePluginDataVersioning();
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
protected function managePluginDataVersioning(): void
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
update_option($myPluginVersionOptionName, $this->pluginVersion);
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might both execute
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
* The plugin's setup data will be installed if:
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
protected function installPluginSetupData(?string $previousVersion): void
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
foreach ($versionCallbacks as $version => $callback) {
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
protected function installPluginSetupDataForVersion1Dot1(): void
'post_status' => 'publish',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future" status="publish"]',
protected function installPluginSetupDataForVersion1Dot2(): void
'post_status' => 'publish',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
'blockName' => 'myplugin/event-calendar',
<?php
class Plugin {
public function __construct(
protected string $pluginVersion,
) {
}
public function setup(): void
{
if (!is_admin()) {
return;
}
$this->managePluginDataVersioning();
}
/**
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
*/
protected function managePluginDataVersioning(): void
{
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
return;
}
\add_action(
'init',
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
// Update on the DB
update_option($myPluginVersionOptionName, $this->pluginVersion);
}
);
}
/**
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might both execute
* this logic
*/
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
return;
}
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
}
/**
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
*
* The plugin's setup data will be installed if:
*
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
*/
protected function installPluginSetupData(?string $previousVersion): void
{
$versionCallbacks = [
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
// ... Add more versions
];
foreach ($versionCallbacks as $version => $callback) {
/**
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
*/
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
continue;
}
$callback();
}
}
protected function installPluginSetupDataForVersion1Dot1(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future" status="publish"]',
]);
}
protected function installPluginSetupDataForVersion1Dot2(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
[
'blockName' => 'myplugin/event-calendar',
'attrs' => [
'status' => 'publish',
],
'innerContent' => [],
],
]),
]);
}
}
<?php
class Plugin {
public function __construct(
protected string $pluginVersion,
) {
}
public function setup(): void
{
if (!is_admin()) {
return;
}
$this->managePluginDataVersioning();
}
/**
* If the plugin has just been newly-installed + activated
* or updated, install the appropriate data.
*/
protected function managePluginDataVersioning(): void
{
$myPluginVersionOptionName = 'myplugin_version';
$storedPluginVersion = get_option($myPluginVersionOptionName, null);
// Check if the main plugin has been activated or updated
$isPluginJustFirstTimeActivated = $storedPluginVersion === null;
$isPluginJustUpdated = !$isPluginJustFirstTimeActivated && $storedPluginVersion !== $this->pluginVersion;
// If there were no changes, nothing to do
if (!$isPluginJustFirstTimeActivated && !$isPluginJustUpdated) {
return;
}
\add_action(
'init',
function () use ($myPluginVersionOptionName, $storedPluginVersion): void {
$this->prepareAndInstallPluginSetupData($storedPluginVersion);
// Update on the DB
update_option($myPluginVersionOptionName, $this->pluginVersion);
}
);
}
/**
* Use a transient to make sure that only one instance
* will install the data. Otherwise, two requests
* happening simultaneously might both execute
* this logic
*/
protected function prepareAndInstallPluginSetupData(?string $previousVersion): void
{
$transientName = 'myplugin_installing_plugin_setup_data';
$transient = \get_transient($transientName);
if ($transient !== false) {
// Another instance is executing this code right now
return;
}
\set_transient($transientName, true, 30);
$this->installPluginSetupData($previousVersion);
\delete_transient($transientName);
}
/**
* Provide the installation in stages, version by version, to
* be able to execute it both when installing/activating the plugin,
* or updating it to a new version with setup data.
*
* The plugin's setup data will be installed if:
*
* - $previousVersion = null => Activating the plugin for first time
* - $previousVersion < someVersion => Updating to a new version that has data to install
*/
protected function installPluginSetupData(?string $previousVersion): void
{
$versionCallbacks = [
'1.1' => $this->installPluginSetupDataForVersion1Dot1(...),
'1.2' => $this->installPluginSetupDataForVersion1Dot2(...),
// ... Add more versions
];
foreach ($versionCallbacks as $version => $callback) {
/**
* If the previous version is provided, check if the corresponding update
* has already been performed, then skip
*/
if ($previousVersion !== null && version_compare($previousVersion, $version, '>=')) {
continue;
}
$callback();
}
}
protected function installPluginSetupDataForVersion1Dot1(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Upcoming Events', 'myplugin'),
'post_content' => '[event_list number="10" scope="future" status="publish"]',
]);
}
protected function installPluginSetupDataForVersion1Dot2(): void
{
\wp_insert_post([
'post_status' => 'publish',
'post_type' => 'page',
'post_title' => \__('Events Calendar', 'myplugin'),
'post_content' => serialize_blocks([
[
'blockName' => 'myplugin/event-calendar',
'attrs' => [
'status' => 'publish',
],
'innerContent' => [],
],
]),
]);
}
}
小結
WordPress 外掛在安裝時通常需要安裝資料。此外,當新版本的外掛提供新功能時,外掛也可能需要在更新時安裝資料。
在本文中,我們學習瞭如何跟蹤版本併為外掛安裝相應的資料。
您的 WordPress 外掛可以從安裝資料中受益嗎?請在評論中告訴我們。
評論留言