Git 和 WordPress:如何使用合并请求自动更新帖子
在 Bitfalls.com,我们现在也使用 WordPress,并使用与 SitePoint 相同的内容同行评审方法。
我们决定构建一个工具,自动将合并的拉取请求中的内容提取到文章中,使我们能够修复拼写错误和更新来自 Github 的帖子,并查看实时站点上反映的更改。 本教程将引导您完成此工具的创建,因此您可以开始将其用于您自己的 WordPress 站点,或构建您自己的版本。
计划
第一部分是确定问题及其周围情况。
- 我们使用 WPGlobus 来支持多语言,这意味着内容可以这样保存:
{:en}English content{:}{:hr}Croatian content{:}
. 作者通过 Github 提交 PR,PR 经过同行评审和合并,然后(目前)通过浏览器手动导入到 WP 的 Posts UI 中。 每个帖子都有相同的文件夹布局: author_folder/post_folder/language/final.md
这是缓慢且容易出错的,有时错误会溜走。 这也使更新帖子变得乏味。
解决方案如下:
- 添加一个钩子处理器,它将检测到主分支的推送(即从 PRs 合并)处理器应该在提交中寻找一个元文件,其中包含有关在何处保存更新内容的信息处理器自动将 MD 内容转换为 HTML,合并 WPGlobus 格式的语言,并将它们保存到数据库中
自举
如果您想跟随(强烈推荐),请启动一个良好的虚拟机环境,在其上安装最新版本的 WordPress,并添加 WPGlobus 插件。 或者,您可以使用准备好的 WordPress 框,如 VVV。 此外,请确保您的环境已安装 ngrok——我们将使用它来将 Github 钩子触发器通过管道传输到我们的本地机器,这样我们就可以在本地进行测试,而不必进行部署。
挂钩
对于这个实验,让我们创建一个新的存储库。 我会调用我的自动推送。
在这个存储库的设置中,我们需要添加一个新的钩子。 由于我们正在谈论一个临时的 Ngrok URL,所以让我们首先启动它。 在我的例子中,在主机上输入以下内容就可以了:
ngrok http homestead.app:80
我得到了链接 http://03672a64.ngrok.io
,所以这就是进入 webhook 的内容,带有任意后缀,例如 githook
. 我们只需要推送事件。 这 json
数据类型更清晰,因此选择它作为首选项,最终的 webhook 设置如下所示:
现在让我们测试一下。
git clone https://github.com/swader/autopush
cd autopush
touch README.md
echo "This is a README file" >> README.md
git add -A
git commit -am "We're pushing for the first time"
git push origin master
ngrok 日志屏幕应显示如下内容:
POST /githook/ 404 Not Found
这可以。 我们还没有做出 /githook
终点还没有。
处理 Webhook
我们将使用自定义逻辑将这些新数据读入 WordPress。 由于 WP 本身的意大利面条代码性质,使用小型自定义应用程序更容易完全规避它。 首先,我们将创建 githook
WordPress 项目根目录中的文件夹,以及 index.php
里面的文件。 这使得 /githook/
路径可访问,钩子将不再返回 404,而是 200 OK。
根据文档,有效载荷将有一个 commits
领域与 modified
每个提交中的字段。 因为我们只是想更新帖子,而不是安排它们或删除它们——为了安全起见,这些步骤仍然是手动的——我们只会关注那一个。 让我们看看我们是否可以在测试推送中抓住它。
首先,我们将请求数据保存到一个文本文件中,以供调试之用。 我们可以通过修改我们的 githook/index.php
文件:
<?php
file_put_contents('test.txt', file_get_contents('php://input'));
然后我们会新建一个分支,添加一个文件,然后推送上线。
git checkout -b test-branch
touch testfile.md
git add testfile.md
git commit -am "Added test file"
git push origin test-branch
果然,我们的 test.json
文件现在充满了有效载荷。 这是我得到的有效载荷。 你可以看到我们只有一个提交,而那个提交的 modified
字段为空,而 added
领域有 testfile.md
. 我们也可以看到这发生在 refs/heads/test-branch
,因此,我们对此不感兴趣。 但是如果我们从这个分支中创建一个 PR 并合并它会发生什么?
我们的有效载荷看起来不同。 最值得注意的是,我们现在有 refs/heads/master
作为 ref
领域,这意味着它发生在 master
分支,我们必须注意它。 我们还有 2 个提交而不是一个:第一个与原始 PR 中的相同,即添加文件。 第二个对应于 master 分支上的更改:合并本身。 两者引用相同 added
文件。
让我们做最后一个测试。 让我们编辑 testfile.md
,推送它,然后进行 PR 和合并。
echo "Hello" >> testfile.md
git add testfile.md
git commit -am "Added test file"
git push origin test-branch
啊,我们开始了。 我们现在在有效载荷中有一个修改过的文件。
现在让我们做一个“真实”的场景并模拟更新提交。 首先,我们将创建一个帖子的默认文件夹,然后我们将 PR 更新到其中。
git checkout master
git pull
mkdir -p authors/some-author/some-post/{en_EN,hr_HR,images}
echo "English content" >> authors/some-author/some-post/en_EN/final.md
echo "Croatian content" >> authors/some-author/some-post/hr_HR/final.md
touch authors/some-author/some-post/images/.gitkeep
git add -A
git commit -am "Added some author"
git push origin master
然后我们进行编辑。
git checkout -b edit-for-some-post
echo "This is a new line" >> authors/some-author/some-post/en_EN/final.md
git add -A
git commit -am "Added an update on the English version of the post"
git push origin edit-for-some-post
如果我们将其转换为 Github Web UI 中的拉取请求并合并 PR,我们将获得此有效负载。
如果我们沿着有效载荷中修改后的文件的路径,我们可以很容易地辨别出我们正在谈论的文件夹。 让我们修改 index.php
以前的文件。
$payload = json_decode($json, true);
$last_commit = array_pop($payload['commits']);
$modified = $last_commit['modified'];
$prefix = 'https://raw.githubusercontent.com/';
$repo = 'swader/autopush/master/';
$lvl = 2;
$folders = [];
foreach ($modified as $file) {
$folder = explode("https://www.sitepoint.com/", $file);
$folder = implode("https://www.sitepoint.com/", array_slice($folder, 0, -$lvl));
$folders[] = $folder;
}
$folders = array_unique($folders);
var_dump($folders);
我们获取有效负载中的最后一次提交,提取其修改的文件列表,并找到每个修改文件的父文件夹。 父级由 $lvl
变量——在我们的例子中它是 2 因为文件夹是 2 层:一个额外的语言 (en_EN
).
我们有它 – 包含需要更新的文件的文件夹的路径。 现在我们要做的就是获取内容,将这些文件的 Markdown 转换为 HTML,并将其保存到数据库中。
处理降价
要处理 MarkDown,我们可以使用 Parsedown 包。 我们将在 githooks
文件夹本身,使应用程序尽可能独立。
composer require erusev/parsedown
Parsedown 与我们在 Bitfalls 使用 Caret 编辑器编写时使用的 Markdown 风格相同,因此它是完美的搭配。
现在我们可以修改 index.php
再次。
$payload = json_decode($json, true);
$last_commit = array_pop($payload['commits']);
$modified = $last_commit['modified'];
$prefix = 'https://raw.githubusercontent.com/';
$repo = 'swader/autopush/';
$branch = 'master/';
$languages = [
'en_EN' => 'en',
'hr_HR' => 'hr'
];
$lvl = 2;
$folders = [];
foreach ($modified as $file) {
$folder = explode("https://www.sitepoint.com/", $file);
$folder = implode("https://www.sitepoint.com/", array_slice($folder, 0, -$lvl));
$folders[] = $folder;
}
$folders = array_unique($folders);
foreach ($folders as $folder) {
$fullFolderPath = $prefix.$repo.$branch.$folder."https://www.sitepoint.com/";
$content = '';
foreach ($languages as $langpath => $key) {
$url = $fullFolderPath.$langpath.'/final.md';
$content .= "{:$key}".mdToHtml(getContent($url))."{:}";
}
if (!empty($content)) {
// Save to database
}
}
function getContent(string $url): string {
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url.'?nonce=".md5(microtime()));
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
$data = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($code != 200) {
return "';
}
curl_close($ch);
return $data;
}
function mdToHtml(string $text): string {
$p = new Parsedown();
$p->setUrlsLinked(true);
return $p->parse($text);
}
我们制作了一些非常简单的功能以避免重复。 我们还添加了语言文件夹(区域设置)与其 WPGlobus 键的映射,以便在遍历文件夹中的所有文件时,我们知道如何在帖子正文中分隔它们。
注意:当只更新一个帖子时,我们必须更新帖子的所有语言版本,因为 WPGlobus 不使用额外的字段或不同的数据库行来保存帖子的另一种语言——它将它们全部保存在一个字段中,因此需要更新该字段的全部值。
我们遍历获得更新的文件夹(一个 PR 中可能有多个),获取文件的内容并将其转换为 HTML,然后将所有这些存储到 WPGlobus 友好的字符串中。 现在是时候将其保存到数据库中了。
注意:我们在 URL 的末尾使用了一个随机数来使原始 github 内容可能存在的缓存问题无效。
保存编辑的内容
我们不知道在哪里保存新内容。 我们需要添加对元文件的支持。
首先,我们将添加一个获取此元文件的新函数:
function getMeta(string $folder): ?array {
$data = getContent(trim($folder, "https://www.sitepoint.com/").'/meta.json');
if (!empty($data)) {
return json_decode($data, true);
}
return null;
}
很简单,如果它存在,它会返回它的内容。 元文件将是 JSON,因此我们需要的所有解析都已内置到 PHP 中。
然后,我们将向主循环添加一个检查,以便该过程跳过任何没有元文件的文件夹。
foreach ($folders as $folder) {
$fullFolderPath = $prefix.$repo.$branch.$folder."https://www.sitepoint.com/";
$meta = getMeta($fullFolderPath);
if (!$meta) {
continue;
}
// ...
我们将使用 WP CLI 进行更新。 可以使用以下命令安装 CLI:
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
sudo chmod +x /usr/local/bin/wp
这将下载 WP-CLI 工具,将其放入服务器路径(因此它可以从任何地方执行),并为其添加“可执行”权限。
这 post update
命令需要一个帖子 ID 和要更新的字段。 WordPress 帖子保存在 wp_posts
数据库表,我们要更新的字段是 post_content
场地。
让我们在命令行中尝试一下,以确保它按预期工作。 首先,我们将添加一个示例帖子。 我给了它一个英文的“Example post”和克罗地亚语的“Primjer”的示例标题,正文 This is some English content for a post!
对于英文内容,和 Ovo je primjer!
对于克罗地亚语的内容。 保存后,这是它在数据库中的样子:
在我的例子中,帖子的 ID 是 428。如果你的 WP 安装是新的,你的可能会接近 1。
现在让我们看看如果我们在命令行中执行以下命令会发生什么:
wp post update 428 --post_content='{:en}This is some English content for a post - edited!{:}{:hr}Ovo je primjer - editiran!{:}'
果然,我们的帖子更新了。
这看起来在处理需要转义的引号时可能会出现问题。 如果我们从文件更新,并让这个工具处理引号等,那就更好了。 试一试吧。
让我们把内容 :en}This is some English 'content' for a post - edited "again"!{:}{:hr}Ovo je 'primjer' - editiran "opet"!{:}
进入一个名为 updateme.txt
. 然后…
wp post update 428 updateme.txt
是的,一切都很好。
好的,现在让我们将其添加到我们的工具中。
现在,我们的元文件将只有帖子的 ID,所以让我们添加一个这样的文件到内容仓库中:
git checkout master
git pull
echo '{"id": 428}' >> authors/some-author/some-post/meta.json
git add -A
git commit -am "Added meta file for post 428"
git push origin master
注意:更新 ID 以匹配您的 ID。
此时,我们的内容存储库应该如下所示(保存为发布的版本,可以随意克隆)。
更换 // Save to database
在之前的代码及其周围的行中添加:
if (!empty($content)...