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)...

阅读更多

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注