Publishing posts to a WordPress site with Git

WordPress Git post is a tool that allows you to publish or update posts to a remote WordPress site with Git commands.

The work are done through Git hooks and custom WP-CLI commands. Therefore you need to create a remote Git repository on the sever where your site is running and add custom Git hooks to it. When you make a push from your local repository to the remote one, corresponding Git hooks would be called to publish and/or update posts with the markdown files added and/or updated in this push.

How to use it?

Before installing the tool, let’s first see how it works. First you need to have a local Git repository on local machine and a remote one on the server.

On the remote server

First you need to have a Git repository on the server.

Create a Git non-bare (By default, it creates a non-bare repository) repository:

Login to the remote server and direct a folder where you want to put the repository. Execute below command:

# Create a non-bare Git repository
$ git init

To be able to push to a non-bare remote repository, configure receive.denyCurrentBranch with below command to allow pushes.

# Config with below command to allow pushes to a non-bare repository.
$ git config receive.denyCurrentBranch updateInstead

Then set up SSH access to the remote repository. See Setting up the server for reference if you are not familiar to that.

On the local machine

Step 1. Clone the remote repository

Direct to a folder where you want to put the repository.

$ git clone <remote-repository-url>

Or if you already has one, then add a remote to pointing to the remote Git repository.

# Config a remote to point to the remote Git repostiory
# Remember to replace the remote repostiory url with your own.
$ git remote add origin <remote-repostiory-url>

Note:

There are not requirements for how the repository’s folder structure is organized. You can organize the markdown files to different folders whose name are the corresponding category names the posts belong to, like:

.
|-- wordpress
|   |-- my-wordpress-post-1.md
|   |-- my-wordpress-post-1.md
|   `-- my-wordpress-post-3.md
|-- javascript
|   |-- my-javascript-post-1.md
|   |-- my-javascript-post-2.md
|   |-- my-javascript-post-3.md
|   `-- my-javascript-post-4.md
`-- about.md

Step 2. Make a commit

Write a new markdown file like my-first-post.md:.

my-first-post.md:

---
post_title: My first post
post_author: 9
post_type: post
post_status: publish
tags_input:
  - demo
post_category:
  - examples
description: "The articla is about ...."
---

You post content here....

Note:

This tool is depending on custom WP-CLI commands which is provided by WP-CLI markdown post tool GitHub repository. Find more information that tool’s page or here on how to create a post with markdown.

# Add all the new created or modified files, like "git add examples/my-first-post.md"
$ git add my-first-post.md

# You can add multiple new markdown files or updated existing ones in a commit.

# Commit the changes
$ git commit -m 'add my first post'

Step 3. Push to the remote repo

# "main" is the branch name, replace it with your own one if you are using another branch name.
$ git push -u origin main

It’s much easy and convenient, isn’t it?

Installing

The installing is simple, just some copy operations. What you need to do is to install Git hooks and WP-CLI commands on the remote server:

  • Install hooks

    Copy post-receive file inside server-hooks folder to the .git/hooks folder of the remote repository. Execute chmod a+x post-receive (for Linux) under .git/hooks folder to make it executable.

    > Note:
    >
    > If your WordPress site is running on Windows, change post-receive file to make it work properly as following steps. Otherwise you may meet 'wp' is not recognized as an internal or external command error.
    >
    > Change below line in post-receive file
    >
    > php
    &gt; define( "WP_CLI_TOOL", "wp " );
    &gt;

    >
    > to
    >
    > php
    &gt; define( "WP_CLI_TOOL", "php " );
    &gt;

    >
    > Use your own path to wp file to replace “. Be care that there is a space in the end.

  • Install custom WP-CLI commands

    Copy wp-cli-markdown-post-command.php to your current using theme on the server. To make it workd add below content to the functions.php file. Take care to use different code depending on whether you are using a parent theme or a child theme (name ends with -child).

    If you are using a parent theme:

    // Use below code if you are using a parent theme.
    if ( defined( 'WP_CLI' ) &amp;&amp; WP_CLI ) {
      require_once( get_parent_theme_file_path() . '/wp-cli-markdown-post-command.php' );
    }
    

    Or if you are using a child theme:

    // Use below code if you are using a child theme.
    if ( defined( 'WP_CLI' ) &amp;&amp; WP_CLI ) {
      require_once( get_stylesheet_directory() . '/wp-cli-markdown-post-command.php' );
    }
    

Tips

If you have another WordPress site running on your local machine for some purpose like testing the remote one, it is a good idea to install this custom WP-CLI command tool either on the local machine. With it it is more handy to create a new markdown file which will contain the YAML part.

Resource

Publishing posts to a WordPress site with markdown

WP-CLI markdown post is a custom WP-CLI command tool which allows you to publish and update posts with markdown files in a terminal.

How to use it?

Let’s see how to create a markdown file and publish it to your website.

Create a markdown file

Login to the machine which your website is running on.

# Create a markdown file with name "useful-git-commands"
# You do need to write the extension ".md", the tool does it for you.
$ wp new wp-cli-markdown-post
Success: wp-cli-markdown-post.md is created!

The new created wp-cli-markdown-post.md looks like below:

---
post_title:
post_name:
post_author:
post_type: post
post_status: publish
tags_input:
  -
post_category:
  -
description:
---

It contains a YAML part (wrapped by --- and ---) to allow you to set some meta fields and the post content which follows the YAML.

This a full example of a post written with markdown.

---
post_title: WP-CLI markdown post tool
post_author: 108
post_type: post
post_status: publish
tags_input:
  - wp-cli
post_category:
  - wordpress
description: "WP-CLI markdown post is a custom WP-CLI command tool which allows you to publish and update posts with markdown files in a terminal."
---

[WP-CLI markdown post](https://github.com/gloomic/wp-cli-markdown-post) is a custom WP-CLI command tool which allows you to publish and update posts with markdown files in a terminal.

The second paragraph ...

Publish a post with a markdown file

To publish the markdown file created previously:

$ wp create wp-cli-markdown-post.md
Success: 920

This command will publish the post to the website and update the file by adding an ID in the YAML part:

---
ID: 920
post_title: WP-CLI markdown post tool

Update a post with a markdown file

To update a post with a markdown file which has been published before:

# Update useful-git-commands.md and save it.

# Update to website.
$ wp update useful-git-commands.md

Installing

Prerequisites

To use it you need first install the official WP-CLI tool provided by wordpress.org. It is a compressed package, just download it, put it somewhere on the path and make it executable.

On Linux:

# Download wp-cli
$ curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
# Make it executable.
$ chmod +x wp-cli.phar
# Rename it for less typing and move it somewhere in your path.
$ sudo mv wp-cli.phar /usr/local/bin/wp

It is similar on Windows.

See Installing WP-CLI for more details.

Install WP-CLI markdown post tool

Copy wp-cli-markdown-post-command.php to your current using theme and include it in the functions.php file on the server. Take care to use different code depending on you are using a normal theme or a child one (name ends with -child).

If you are using a normal theme:

// Use below code if you are using a parent theme.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
    require_once( get_parent_theme_file_path() . '/wp-cli-markdown-post-command.php' );
}

Or if you are using a child theme:

// Use below code if you are using a child theme.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
    require_once( get_stylesheet_directory() . '/wp-cli-markdown-post-command.php' );
}

Read more

Adding custom commands to WP-CLI

WP-CLI allows you to add your own command. To achieve that, just write your command handler and use add_command() to register it to WP-CLI. In the following content, you’ll get to know how to add a single custom command or a group of custom commands to WP-CLI.

Example 1: Add a single custom WP-CLI command

If you are intending to add just a single simple command, you may just write a function handler. If you need to add a group of commands, see the second example.

Step 1: Write your custom command handler

The command handler accepts two parameters. Both of them are the parameters passed to the command.

  • $args, an array of positional arguments that does not start with --.
  • $assoc_args, an array of associative arguments that starts with --.

For example the command wp post update.

$ wp post update 26 --post_name=wp-cli-command-examples --post_category=25,33

In the above example, the values of $args and $assoc_args will be:

$args = array( "id" );
$argv = array(
    "post_name" => "wp-cli-command-exmples",
    "post_category" => "25,33"
);

Note:

In the above example, the value of post_category parameter is a string instead of an array. It is up to the handler to parse the value to an array.

If you want to update a post’s author, you need to pass an user ID to wp post update command instead of the user name. Therefore we implement a custom command that allows using user name.

In the example we name the custom command wp update-post-author. You may write it in a single file or just write it in your theme’s function.php file. Here we create a new file named wpc-cli-update-post-author.php in the current theme directory.

wp-cli-update-post-author.php:

/**
 * WP-CLI custom command: Update the author of a post
 * Syntax: wp update-post-author <id> <author name|author id>
 * Examples:
 * 1. Update author by name for post 123: wp update-post-author 123 john
 * 2. Update author by id for post 123:   wp udpate-post-author 123 2
 */
function wp_cli_update_post_author( $args, $assoc_args ) {

    if ( empty( $args ) || count( $args ) < 2 ) {
         WP_CLI::error( 'ID or post author is not specified.' );
    }

    // Get post_author ID if it is a name
    $post_author = $args[1];
    $post_author_id;
    if ( ! is_numeric( $post_author ) ) {
        $post_author_id = username_exists( $post_author );
        if ( $post_author_id === false ) {
            WP_CLI::error( "Post author '$post_author' not exist." );
        }
    } else {
        $post_author_id = intval( $post_author );
    }

    $params = array (
        'ID' => intval( $args[0] ),
        'post_author' => $post_author_id
    );

    $response = wp_update_post( $params, true );
    if ( is_wp_error( $response ) ) {
        WP_CLI::error( $response->get_error_message() );
    } else {
        WP_CLI::success( 'Updated!' );
    }
}

WP-CLI provides some functions for outputting to the command interface from the handler.

Note:

Make sure your command name in unique.

Step 2: Register your custom command to WP-CLI

Registering a command is simple, just add below code to your theme’s function.php .

if ( defined( 'WP_CLI' ) && WP_CLI ) {
    wp_require_once ( "wp-cli-update-post-author.php" );
    WP_CLI::add_command( 'update-post-author', 'wp_cli_update_post_author' );
}

Step 3: Test your custom command

Make sure your WordPress is started. Below is the output when running wp get-post-cats.

# Get the original author
$ wp post get 123 --field=post_author
1

$ wp update-post-author 123 john
Success: Updated!

# Check the result
$ wp post get 123 --field=post_author
2

Note:

If you gets some notice information besides the normal output, see how to supress WP-CLI notices.

Example 2: Add a group of custom WP-CLI commands

The process of adding a custom command with multiple subcommands is similar to that adding one. The difference is you need to extending WP_CLI_Command class and implement subcommands in it. WP_CLI::add_command() will automatically infer those subcommands inside it.

In the below example, we implement a command named wp post-category,it has two sub commands wp post-category add and wp post-category remove which adds or removes categories of a post.

<?php
/**
 * WP-CLI custom command: post-category
 * Subcommand:
 * - add
 * - remove
 */
class Post_Category_Command extends WP_CLI_Command {

    private function add_or_remove_category( $action, $args, $assoc_args ) {
        if ( empty( $args ) || count( $args ) < 2 ) {
            WP_CLI::error( 'ID or author is not specified.' );
        }

        $id = intval( $args[0] );
        $original_cats = get_the_category( $id );
        $original_cat_ids = array();
        foreach( $original_cats as $c ) {
            $original_cat_ids[$c->term_id] = $c->name;
        }

        array_shift( $args );
        $cats = $args;
        $updated_cats = array();
        foreach( $cats as $cat ) {
            $cat_id = null;
            if ( ! is_numeric( $cat ) ) {
                $cat_id = category_exists( $cat );
                if ( $cat_id === false ) {
                    WP_CLI::warning( "No such post category '$cat'." );
                    continue;
                }
            } else {
                $cat_id = intval( $cat );
            }

            if ( $action === 'add' ) {
                if ( ! array_key_exists( $cat_id, $original_cat_ids ) ) {
                    $original_cat_ids[$cat_id] = $cat_id;
                    $updated_cats[] = $cat;
                } else {
                    WP_CLI::warning( "Post category '$cat' already exists for the post." );
                }
            } else {
                if ( array_key_exists( $cat_id, $original_cat_ids ) ) {
                    unset( $original_cat_ids[ $cat_id ] );
                    $updated_cats[] = $cat;
                } else {
                    WP_CLI::warning( "Post category '$cat' does not exist for the post." );
                }
            }
        }

        $params = array (
            'ID' => $id,
            'post_category' => array_keys( $original_cat_ids )
        );

        $response = wp_update_post( $params, true );
        if ( is_wp_error( $response ) ) {
            WP_CLI::error( $response->get_error_message() );
        } else {
            $msg = ( $action === 'add' ) ? 'Added categories: ' : 'Removed categories: ';
            WP_CLI::success( $msg . implode( ',', $updated_cats ) );
        }
    }

    /**
     * Subcommand: Add one or multiple categories to a post.
     * Syntax: post-category add <id> <category_name|category_id> [category_name|category_id...]
     * Examples:
     * 1. Add categories of php and wordpress to post 123:
     *    "wp post-category add 123 php wordpress"
     * 2. Add category 2 to post 123:
     *    "wp post-category add 123 2"
     */
    public function add( $args, $assoc_args ) {
        $this->add_or_remove_category( 'add', $args, $assoc_args );
    }

    /**
     * Subcommand: Remove one or multiple categories from a post.
     * Syntax: post-category add <id> <category_name|category_id> [category_name|category_id...]
     * Examples:
     * 1. Remove category wordpress from post 123: "wp post-category remove 123 wordpress"
     * 2. Remove category 2 from post 123: "wp post-category remove 123 2"
     */
    public function remove( $args, $assoc_args ) {
        $this->add_or_remove_category( 'remove', $args, $assoc_args );
    }
}

Add below code to your theme’s function.php.

if ( defined( 'WP_CLI' ) && WP_CLI ) {
    WP_CLI::add_command( 'post-category', 'Post_Category_Command' );
}

Now you are ready to run these custom commands like below:

# Add category 'php' and 'wordpress' for post with ID '123'.
$ wp post-category add 123 php wordpress
Success: Removed categories: php

# Remove category 'php' for post with ID '123'.
$ wp post-category remove 123 php
Success: Removed categories: php

# Remove category 'php' for post with ID '123' again.
# Because the post has not belong to 'php', it gives warning for that information.
$ wp post-category remove 123 php
Warning: Post category 'php' does not exist for the post.
Success: Removed categories:

Troubleshooting: WP-CLI displays PHP notices when running commands

Issue

When running commands in WP-CLI, it shows a lot of notices like below:

$ wp post list
Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; QrctWp has a deprecated constructor in xxxwp-contentpluginsqr-code-taglibqrctQrctWp.php on line 11
PHP Notice:  wp_enqueue_script was called <strong>incorrectly</strong>. Scripts and styles should not be registered or enqueued until the <code>wp_enqueue_scripts</code>, <code>admin_enqueue_scripts</code>, or <code>login_enqueue_scripts</code> hooks. Please see <a href="https://codex.wordpress.org/Debugging_in_WordPress">Debugging in WordPress</a> for more information. (This message was added in version 3.3.0.) in xxxwp-includesfunctions.php on line 4231
Notice: wp_enqueue_script was called <strong>incorrectly</strong>. Scripts and styles should not be registered or enqueued until the <code>wp_enqueue_scripts</code>, <code>admin_enqueue_scripts</code>, or <code>login_enqueue_scripts</code> hooks. Please see <a href="https://codex.wordpress.org/Debugging_in_WordPress">Debugging in WordPress</a> for more information. (This message was added in version 3.3.0.) in xxxwp-includesfunctions.php on line 4231
...

Solution

Set WP_DEBUG from true to false in wp-config.php of your WordPress.

wp-config.php:

define( 'WP_DEBUG', false );

Running WP-CLI Remotely over SSH

In this article, it demonstrates you how to config SSH to run WP-CLI from remote server via a client.

Before you start, make sure WP-CLI and SSH are installed on both the client machine and the remote server.

Check whether WP-CLI is available on a remote server.

$ php wp-cli.phar --info

For convenience, rename it to wp,make it executable and move it to somewhere in your path.

$ chmod +x wp-cli.phar
$ sudo mv wp-cli.phar /usr/jane/bin/wp

Then you can run it like below.

$ wp --info

Config SSH

Generate SSH keys

If you do not have your SSH keys yet on your client, use ssh-keygen tool to generate a pair keys.

$ ssh-keygen -o
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/john/.ssh/id_rsa):
Created directory '/c/Users/john/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /c/Users/john/.ssh.
Your public key has been saved in /c/Users/john/.ssh/id_rsa.pub.
The key fingerprint is:
c0:72:36:8e:d7:f1:aa:75:s3:53:96:d8:49:85:36:e3 john@mylaptop.local

In the process, it will ask you whether to save the key in the default file, press enter to use the default file. Then it ask you whether to set a passphrase, leave it empty if you do not want a password for the key.Then a pair key will be generated, one(id_rsa) is private key, the other(id_rsa.pub) is public key.

Then just append the content of the public key file (.pub file) to the remote server’s ~/.ssh/authorized_keys file.

# If you have send the public key file to the remote server's home directory,
# use below command to append its content to authorized_keys.
$ cat ~/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys

Config SSH

In the client, edit the SSH’s configuration file ~/.ssh/config. If it does not exist, create it and add below content to config the remote server to connect over SSH.

Host my_server
  HostName xx.xx.xx.xx
  Port 22
  User jane
  IdentityFile c:usersjohn.sshid_rsa

The first line config the remote server’s name, here we name it “my_server_name”. The following lines config its IP, port, user as well as the private key file path on your client. When connecting to the remote server, it will be used for authorization.

Config WP-CLI

WP-CLI use a config file of YAML format. You need to create a file /home/john/.wp-cli/config.yml in the client if it does not exist. WP-CLI will automatically find it in the filesystem.

Run WP-CLI remotely on client

If you just run WP-CLI for remote server, add below content to the YAML file.

# Global parameters for remote WP-CLI
ssh: my-server:/home/jane

Run WP-CLI locally and remotely on the same client

If you want to run WP-CLI both for local WordPress and that on remote server, use an alias for remote access in the WP-CLI config file.

# Global default parameters for local WP-CLI
path: C:xampphtdocswordpress
user: xxx

# Global default Parameters for remote WP-CLI
@svr:
  ssh: my-server:/home/jane

For local WP-CLI, config the client’s WordPress’s path and WordPress user name.

For remote WP-CLI, config SSH connection information as an alias @svr. Here my-server is the server name configured in the SSH config file, /home/jane is the directory on remote server, when running remotely it automatically change to that directory.

Note: The path for WordPress on the remote server can be configured on remote’s WP-CLI config file.

# Global default parameters
path: /opt/wordpress/htdocs

Run WP-CLI locally and remotely on the same client.

# Local WP-CLI info
$ wp --info

# Remote WP-CLI info
$ wp @svr info

Resource