不要重复自己:使用 WP-CLI 自动执行重复任务
已发表: 2022-03-11你有没有发现自己进入 WordPress 管理区域来更新主题、插件和 WP 核心? 你当然有。 您是否被问过,“您可以创建/更新/删除此 CSV 文件上的所有用户吗?” 我相信你也遇到过。 您是否尝试过迁移网站并希望有一个插件或第三方工具可以让您完成这项工作? 我知道我有!
有一个非常强大的工具可以帮助您完成这些任务等等。 在我告诉你之前,我想建立一个简短的轶事。
问题:在最近的一个项目中,我需要定期重复几个程序化任务。 其中一项任务尤其涉及根据会员级别购买或订阅的证据更新用户级别权限。 如果公司找不到用户为特定会员级别支付的款项,他们希望从用户中删除会员级别。 为什么需要这个? 也许一个成员停止了订阅,但没有触发一个事件,因此即使他们没有为此付费,该成员仍然可以访问(哎呀!)。 或者也许有人在试用优惠,但该优惠已过期,客户仍然有订阅(也很糟糕!)。
解决方案:我没有进入管理面板并手动删除数百个(可能是数千个)订阅,而是选择使用我最喜欢的 WordPress 工具之一,WP-CLI,它通过几次击键解决了问题。
在这篇文章中,我想向您介绍 WP-CLI(假设您还不是亲密的朋友),引导您完成我为这种特殊情况编写的一个简单的自定义命令,并为您提供一些使用 WP-CLI 的想法和资源自己的发展。
什么是 WP-CLI?
如果您以前从未听说过 WP-CLI,那么您并不孤单。 该项目虽然已有几年历史,但似乎在 WordPress 雷达下飞行了一段时间。 以下是官方网站上关于 WP-CLI 是什么以及做什么的简要说明:
WP-CLI 是一组用于管理 WordPress 安装的命令行工具。 您无需使用网络浏览器即可更新插件、设置多站点安装等等。
以下命令向您展示了 WP-CLI 的强大功能:
-
wp plugin update --all
更新所有可更新的插件。 -
wp db export
导出数据库的 SQL 转储。 -
wp media regenerate
为附件重新生成缩略图(例如,在您更改主题大小之后)。 -
wp checksum core
验证 WordPress 核心文件没有被篡改。 -
wp search-replace
搜索并替换数据库中的字符串。
如果您在此处探索更多命令,您将看到每个 WordPress 开发人员或站点维护人员每天或每周执行的重复性任务都有大量可用命令。 这些命令在一年中为我节省了无数小时的指向、单击和等待页面重新加载的时间。
你确信吗? 准备好开始了吗? 伟大的!
您将需要在您的 WordPress 中安装 WP-CLI(或在您的本地计算机上全局安装)。 如果您尚未在本地开发环境中安装 WP-CLI,可以在此处的网站上找到安装说明。 如果您使用 Varying Vagrant Vagrants (VVV2),则包括 WP-CLI。 许多托管服务提供商的平台上也包含 WP-CLI。 我会假设你已经成功安装了这个。
使用 WP-CLI 解决问题
为了解决重复性任务的问题,我们需要为我们的 WordPress 安装提供一个自定义的 WP-CLI 命令。 向任何站点添加功能的最简单方法之一是创建插件。 我们将在本例中使用插件主要有以下三个原因:
- 如果我们不需要它,我们将能够关闭自定义命令
- 我们可以轻松地扩展我们的命令和子命令,同时保持模块化。
- 我们可以维护跨主题甚至其他 WordPress 安装的功能。
创建插件
要创建插件,我们需要在wp-content
目录中的/plugins
目录中添加一个目录。 我们可以将此目录toptal-wpcli
。 然后在该目录中创建两个文件:
-
index.php
,应该只有一行代码:<?php // Silence is golden
-
plugin.php
,这是我们的代码所在的地方(你可以随意命名这个文件。)
打开plugin.php
文件并添加以下代码:
<?php /** * Plugin Name: TOPTAL WP-CLI Commands * Version: 0.1 * Plugin URI: https://n8finch.com/ * Description: Some rando wp-cli commands to make life easier... * Author: Nate Finch * Author URI: https://n8finch.com/ * Text Domain: toptal-wpcli * Domain Path: /languages/ * License: GPL v3 */ /** * NOTE: THIS PLUGIN FILE WILL NOT WORK IN PRODUCTION AS IS AND IS ONLY FOR DEMONSTRATION PURPOSES! * You can of course take the code and repurpose it:-). */ if ( !defined( 'WP_CLI' ) && WP_CLI ) { //Then we don't want to load the plugin return; }
前几行有两个部分。
首先,我们有插件头。 此信息被拉入 WordPress 插件管理页面,并允许我们注册我们的插件并激活它。 只有插件名称是必需的,但我们应该为可能想要使用此代码的任何人(以及我们未来的自己!)包括其余的名称。
其次,我们要检查是否定义了 WP-CLI。 也就是说,我们正在检查是否存在 WP-CLI 常量。 如果不是,我们想保释而不运行插件。 如果它存在,我们就可以清楚地运行我们的其余代码。
在这两个部分之间,我添加了一条注释,即不应在生产中“按原样”使用此代码,因为某些函数是实际函数的占位符。 如果您将这些占位符函数更改为真实的活动函数,请随时删除此注释。
添加自定义命令
接下来,我们要包含以下代码:
class TOPTAL_WP_CLI_COMMANDS extends WP_CLI_Command { function remove_user() { echo "\n\n hello world \n\n"; } } WP_CLI::add_command( 'toptal', 'TOPTAL_WP_CLI_COMMANDS' );
这段代码为我们做了两件事:
- 它定义了类
TOPTAL_WP_CLI_COMMANDS
,我们可以将参数传递给它。 - 它将命令
toptal
分配给类,因此我们可以从命令行运行它。
现在,如果我们执行wp toptal remove_user
,我们会看到:
$ wp toptal hello hello world
这意味着我们的命令toptal
已注册并且我们的子命令remove_user
正在工作。
设置变量
由于我们正在批量处理删除用户,因此我们要设置以下变量:
// Keep a tally of warnings and loops $total_warnings = 0; $total_users_removed = 0; // If it's a dry run, add this to the end of the success message $dry_suffix = ''; // Keep a list of emails for users we may want to double check $emails_not_existing = array(); $emails_without_level = array(); // Get the args $dry_run = $assoc_args['dry-run']; $level = $assoc_args['level']; $emails = explode( ',', $assoc_args['email'] );
每个变量的意图如下:
-
total_warnings
:如果电子邮件不存在,或者电子邮件与我们要删除的会员级别无关,我们将发出警告。 -
$total_users_removed
:我们想要统计在此过程中删除的用户数量(请参阅下面的警告)。 -
$dry_suffix
:如果这是一次试运行,我们想在最终成功通知中添加措辞。 -
$emails_not_existing
:存储不存在的电子邮件列表。 -
$emails_without_level
:存储没有指定级别的电子邮件列表。 -
$dry_run
:一个布尔值,用于存储脚本是否正在执行空运行 (true) 或不 (false)。 -
$level
:表示要检查和可能删除的级别的整数。 -
$email
:要检查给定级别的电子邮件数组。 我们将遍历这个数组
设置好变量后,我们就可以实际运行该函数了。 以真正的 WordPress 方式,我们将运行一个循环。
编写函数本身
我们首先创建一个foreach
循环来循环遍历$emails
数组中的所有电子邮件:
// Loop through emails foreach ( $emails as $email ) { // code coming soon } // end foreach
然后,我们添加一个条件检查:
// Loop through emails foreach ( $emails as $email ) { //Get User ID $user_id = email_exists($email); if( !$user_id ) { WP_CLI::warning( "The user {$email} does not seem to exist." ); array_push( $emails_not_existing, $email ); $total_warnings++; continue; } } // end foreach
此检查确保我们有一个注册用户使用我们正在检查的电子邮件。 它使用email_exists()
函数来检查是否有用户使用该电子邮件。 如果它没有找到使用该电子邮件的用户,它会发出警告,以便我们在终端屏幕上知道未找到该电子邮件:
$ wp toptal remove_user [email protected] --dry-run Warning: The user [email protected] does not seem to exist.
然后将电子邮件存储在$emails_not_existing
数组中以供稍后显示。 然后我们将总警告加一并继续循环到下一封电子邮件。
如果电子邮件确实存在,我们将使用$user_id
和$level
变量来检查用户是否有权访问该级别。 我们将生成的布尔值存储在$has_level
变量中:
// Loop through emails foreach ( $emails as $email ) { //Get User ID $user_id = email_exists($email); if( !$user_id ) { WP_CLI::warning( "The user {$email} does not seem to exist." ); array_push( $emails_not_existing, $email ); $total_warnings++; continue; } // Check membership level. This is a made up function, but you could write one or your membership plugin probably has one. $has_level = function_to_check_membership_level( $level, $user_id ); } // end foreach
与此示例中的大多数函数一样,此function_to_check_membership_level()
函数是虚构的,但大多数会员插件应该具有帮助函数来获取此信息。

现在,我们将继续进行主要操作:从用户那里移除关卡。 我们将使用if/else
结构,如下所示:
foreach ( $emails as $email ) { // Previous code here... // Check membership level. This is a made up function, but you could write one or your membership plugin probably has one. $has_level = function_to_check_membership_level( $level, $user_id ); if ( $has_level ) { if ( !$dry_run ) { // Deactivate membership level. This is a made up function, but you could write one or your membership plugin probably has one. function_to_deactivate_membership_level( $level, $user_id, 'inactive' ); } WP_CLI::success( "Membership canceled for {$email}, Level {$level} removed" . PHP_EOL ); $total_users_removed++; } else { WP_CLI::warning( "The user {$email} does not have Level = {$level} membership." ); array_push( $emails_without_level, $email ); $total_warnings++; } // We could echo something here to show that things are processing... } // end foreach
如果$has_level
的值是“truthy”,这意味着用户可以访问会员级别,我们希望运行一个函数来删除该级别。 在此示例中,我们将使用function_to_deactivate_membership_level()
函数来执行此操作。
但是,在我们真正从用户那里移除关卡之前,我们希望将该函数包含在条件检查中,以查看这是否实际上是一个dry-run
。 如果是,我们不想删除任何东西,只报告我们做了。 如果它不是dry-run
,那么我们将继续从用户那里删除级别,将我们的成功消息记录到终端,并继续循环浏览电子邮件。
另一方面,如果$has_level
的值为“falsey”,这意味着用户无权访问会员级别,我们希望向终端记录警告,将电子邮件推送到$emails_without_level
数组,然后继续循环浏览电子邮件。
完成和报告
循环完成后,我们要将结果记录到控制台。 如果这是一次试运行,我们想在控制台中记录一条额外的消息:
if ( $dry_run ) { $dry_suffix = 'BUT, nothing really changed because this was a dry run:-).'; }
这个$dry-suffix
将附加到我们接下来记录的警告和成功通知中。
最后,我们希望将结果记录为成功消息,将警告记录为警告消息。 我们将这样做:
WP_CLI::success( "{$total_users_removed} User/s been removed, with {$total_warnings} warnings. {$dry_suffix}" ); if ( $total_warnings ) { $emails_not_existing = implode(',', $emails_not_existing); $emails_without_level = implode(',', $emails_without_level); WP_CLI::warning( "These are the emails to double check and make sure things are on the up and up:" . PHP_EOL . "Non-existent emails: " . $emails_not_existing . PHP_EOL . "Emails without the associated level: " . $emails_without_level . PHP_EOL ); }
请注意,我们使用的是WP_CLI::success
和WP_CLI::warning
辅助方法。 这些由 WP-CLI 提供,用于将信息记录到控制台。 您可以轻松地记录字符串,这就是我们在这里所做的,包括我们的$total_users_removed
、 $total_warnings
和$dry_suffix
变量。
最后,如果我们确实在整个脚本运行时产生了任何警告,我们希望将该信息打印到控制台。 运行条件检查后,我们将$emails_not_existing
和$emails_without_level
数组变量转换为字符串变量。 我们这样做是为了可以使用WP_CLI::warning
辅助方法将它们打印到控制台。
添加描述
我们都知道评论对其他人以及我们未来的自己有帮助,这些评论将在几周、几个月甚至几年后回到我们的代码中。 WP-CLI 提供了一个短描述(shortdesc)和长描述(longdesc)的接口,它允许我们注释我们的命令。 在定义TOPTAL_WP_CLI_COMMANDS
类之后,我们将放在命令的顶部:
/** * Remove a membership level from a user * * ## OPTIONS * --level=<number> * : Membership level to check for and remove * * --email=<email> * : Email of user to check against * * [--dry-run] * : Run the entire search/replace operation and show report, but don't save changes to the database. * * ## EXAMPLES * * wp toptal remove_user --level=5 [email protected],[email protected], [email protected] --dry-run * * @when after_wp_load */
在 longdesc 中,我们定义了我们期望自定义命令接收的内容。 shortdesc 和 longdesc 的语法是 Markdown Extra。 在## OPTIONS
部分下,我们定义了我们期望接收的参数。 如果需要一个参数,我们将它包装在< >
中,如果它是可选的,我们将它包装在[ ]
中。
这些选项在命令运行时被验证; 例如,如果我们省略了所需的电子邮件参数,我们会收到以下错误:
$ wp toptal remove_user --level=5 --dry-run Error: Parameter errors: missing --email parameter (Email of user to check against)
## EXAMPLES
部分包含一个示例,说明该命令在被调用时的外观。
我们的自定义命令现已完成。 你可以在这里看到最后的要点。
警告和改进空间
回顾我们在这里所做的工作以了解如何改进、扩展和重构代码非常重要。 这个脚本有很多改进的地方。 以下是一些关于可以改进的意见。
有时,我发现此脚本不会删除它记录为“已删除”的所有用户。 这很可能是由于脚本运行速度快于查询的执行速度。 您的体验可能会有所不同,具体取决于运行脚本的环境和设置。 解决此问题的快速方法是使用相同的输入重复运行; 它最终会归零并报告没有用户被删除。
可以改进脚本以等待并验证用户已被删除,然后再将用户记录为实际删除。 这会减慢脚本的执行速度,但会更准确,您只需运行一次。
类似地,如果发现这样的错误,脚本可能会抛出错误以提醒用户尚未删除关卡。
改进脚本的另一个方面是允许一次从一个电子邮件地址中删除多个级别。 该脚本可以自动检测是否有一个或多个级别以及一封或多封电子邮件要删除。 我按级别获得了 CSV 文件,因此我一次只需要运行一个级别。
我们还可以重构一些代码以使用三元运算符,而不是我们目前拥有的更冗长的条件检查。 为了演示,我选择让它更易于阅读,但您可以随意制作您自己的代码。
在最后一步中,我们还可以自动将它们导出为 CSV 或纯文本文件,而不是在最后一步将电子邮件打印到控制台
最后,没有检查以确保我们得到$level
变量的整数或$emails
变量中的电子邮件或逗号分隔的电子邮件列表。 目前,如果有人要包含字符串而不是整数,或者用户登录名而不是电子邮件,则脚本将无法运行(并且不会引发错误)。 可以添加对整数和电子邮件的检查。
进一步自动化和进一步阅读的想法
如您所见,即使在这个特定的用例中,WP-CLI 也非常灵活和强大,足以帮助您快速高效地完成工作。 您可能想知道,“我如何才能在我的每日和每周开发流程中开始实施 WP-CLI?”
您可以通过多种方式使用 WP-CLI。 以下是我的一些最爱:
- 无需进入管理面板即可更新主题、插件和 WP 核心。
- 如果我想测试 SQL 查询,导出数据库进行备份或执行快速 SQL 转储。
- 迁移 WordPress 网站。
- 使用虚拟数据或自定义插件套件设置安装新的 WordPress 站点。
- 对核心文件运行校验和以确保它们没有受到损害。 (实际上有一个项目正在进行中,将其扩展到 WP 存储库中的主题和插件。)
- 编写您自己的脚本来检查、更新和维护站点主机(我在这里写过)。
WP-CLI 的可能性几乎是无限的。 这里有一些资源可以让您继续前进:
- 主要的 WP-CLI 站点:http://wp-cli.org
- WP-CLI 命令:https://developer.wordpress.org/cli/commands/
- 官方 WP-CLI 博客:https://make.wordpress.org/cli/
- WP-CLI 手册:https://make.wordpress.org/cli/handbook/
- 进入 WooCommerce? 查看 WC-CLI:https://github.com/woocommerce/woocommerce/wiki/WC-CLI-Overview#woocommerce-commands
- 项目维护者 Daniel Bachhuber 的播客访谈:https://howibuilt.it/episode-28-daniel-bachhuber-wp-cli/