Git and WordPress: How to Auto-Update Posts with Pull Requests php training trivandrum
Git and WordPress: How to Auto-Update Posts with Pull Requests
The Plan
- we use WPGlobus for multi-language support, which means content gets saved like this:
{:en}English content{:}{:hr}Croatian content{:}. - authors submit PRs via Github, the PRs are peer reviewed and merged, and then (currently) manually imported into WP’s Posts UI through the browser.
- every post has the same folder layout:
author_folder/post_folder/language/final.md - this is slow and error prone, and sometimes mistakes slip by. It also makes updating posts tedious.
- add a hook processor which will detect pushes to the master branch (i.e. merges from PRs)
- the processor should look for a meta file in the commit which would contain information on where to save the updated content
- the processor automatically converts the MD content to HTML, merges the languages in the WPGlobus format, and saves them into the database
Bootstrapping
Hooks
ngrok http homestead.app:80

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
POST /githook/ 404 Not Found
Processing Webhooks
<?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
echo "Hello" >> testfile.md
git add testfile.md
git commit -am "Added test file"
git push origin test-branch
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

$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('/', $file);
$folder = implode('/', array_slice($folder, 0, -$lvl));
$folders[] = $folder;
}
$folders = array_unique($folders);
var_dump($folders);
Processing Markdown
composer require erusev/parsedown
$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('/', $file);
$folder = implode('/', array_slice($folder, 0, -$lvl));
$folders[] = $folder;
}
$folders = array_unique($folders);
foreach ($folders as $folder) {
$fullFolderPath = $prefix.$repo.$branch.$folder.'/';
$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);
}
Saving Edited Content
function getMeta(string $folder): ?array {
$data = getContent(trim($folder, '/').'/meta.json');
if (!empty($data)) {
return json_decode($data, true);
}
return null;
}
foreach ($folders as $folder) {
$fullFolderPath = $prefix.$repo.$branch.$folder.'/';
$meta = getMeta($fullFolderPath);
if (!$meta) {
continue;
}
// ...
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 post update 428 --post_content='{:en}This is some English content for a post - edited!{:}{:hr}Ovo je primjer - editiran!{:}'

wp post update 428 updateme.txt

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
if (!empty($content) && is_numeric($meta['id'])) {
file_put_contents('/tmp/wpupdate', $content);
exec('wp post update '.$meta['id'].' /tmp/wpupdate', $output);
var_dump($output);
}
// ...
$payload = json_decode($json, true);
if (empty($json)) {
header("HTTP/1.1 500 Internal Server Error");
die('No data provided for parsing, payload invalid.');
}
if ($payload['ref'] !== 'refs/heads/master') {
die('Ignored. Not master.');
}
$last_commit = array_pop($payload['commits']);
// ...
<?php
require_once 'vendor/autoload.php';
$json = file_get_contents('php://input');
file_put_contents('test.json', $json);
$payload = json_decode($json, true);
if (empty($json)) {
header("HTTP/1.1 500 Internal Server Error");
die('No data provided for parsing, payload invalid.');
}
if ($payload['ref'] !== 'refs/heads/master') {
die('Ignored. Not master.');
}
$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('/', $file);
$folder = implode('/', array_slice($folder, 0, -$lvl));
$folders[] = $folder;
}
$folders = array_unique($folders);
foreach ($folders as $folder) {
$fullFolderPath = $prefix.$repo.$branch.$folder.'/';
$meta = getMeta($fullFolderPath);
if (!$meta) {
continue;
}
$content = '';
foreach ($languages as $langpath => $key) {
$url = $fullFolderPath.$langpath.'/final.md';
$content .= "{:$key}".mdToHtml(getContent($url))."{:}";
}
if (!empty($content) && is_numeric($meta['id'])) {
file_put_contents('/tmp/wpupdate', $content);
exec('wp post update '.$meta['id'].' /tmp/wpupdate', $output);
var_dump($output);
}
}
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 null;
}
curl_close($ch);
return $data;
}
function mdToHtml(string $text): string {
$p = new Parsedown();
$p->setUrlsLinked(true);
return $p->parse($text);
}
function getMeta(string $folder): ?array {
$data = getContent(trim($folder, '/').'/meta.json');
if (!empty($data)) {
return json_decode($data, true);
}
return null;
}
git checkout -b post-update
echo 'Adding a new line yay!' >> authors/some-author/some-post/en_EN/final.md
git add -A; git commit -am "Edit"; git push origin post-update

Conclusion
- modify the post updating procedure so that it uses
stdininstead of a file, making it compatible with no-writable-filesystem hosts like AWS, Heroku, or Google Cloud. - custom output types: instead of fixed
{:en}{:}{:hr}{:}, maybe someone else is using a different multi-language plugin, or doesn’t use one at all. This should be customizable somehow. - auto-insertion of images. Right now it’s manual, but the images are saved in the repo alongside the language versions and could probably be easily imported, auto-optimized, and added into the posts as well.
- staging mode – make sure the merged update first goes through to a staging version of the site before going to the main one, so the changes can be verified before being sent to master. Rather than having to activate and deactivate webhooks, why not make this programmable?
- a plugin interface: it would be handy to be able to define all this in the WP UI rather than in the code. A WP plugin abstraction around the functionality would, thus, be useful.
Comments
Post a Comment