Change saved gutenberg blocks

When a post is saved the Gutenberg Blocks are rendered and it’s html is saved in the post_content in the database, most of the time. But what if you want to change the rendered html for all posts in every post.
Here we will explain how to change the html of saved Gutenberg blocks.

Add a class to all paragraph blocks

This use case is highly unlikely, but it is a simple one. So making it easier to explain.
So lets assume you would want to add a newer class to every paragraph on a single post on your site.

To start the block looks like this:

<!-- wp:paragraph -->
<p>To start the block looks like this:</p>
<!-- /wp:paragraph -->

With the added newer class it will look like this:

<!-- wp:paragraph {"className":"newer"} -->
<p class="newer">To start the block looks like this:</p>
<!-- /wp:paragraph -->

With the use case explained lets show the code to handle this.

changing the html of a block

The following code will change this html

<?php
$post                 = get_post( 898 ); // Get 1 post
$parsed_blocks        = parse_blocks( $post->post_content ); // parse all blocks in this post.
$updated_post_content = ''; // The updated post content will be inside here.

// Loop over the parsed blocks.
foreach ( $parsed_blocks as $block ) {
	if ( 'core/paragraph' !== $block['blockName'] ) {
		// only convert paragraphs, the rest we re-add untouched.
		$updated_post_content .= serialize_block( $block );
		continue;
	}

	// Load the innerHTML as an object so it's safer to manipulate.
	$dom = new DOMDocument;
	$dom->loadHTML( $block['innerHTML'], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );

	/** @var DOMElement $paragraph_node Get the <p> as a object */
	$paragraph_node = $dom->getElementsByTagName( 'p' )->item( 0 );
	// Get already set classes.
	$current_classes = $paragraph_node->getAttribute( 'class' );
	// Append `newer` class to the current list.
	$all_classes = trim( $current_classes . ' newer' );

	// Add the class attribute.
	$paragraph_node->setAttribute( 'class', $all_classes );
	$new_content = $dom->saveHTML();

	// Now put the new block together.
	$new_block = array(
		// We keep this the same.
		'blockName'    => 'core/paragraph',
		// also add the class as block attributes.
		'attrs'        => array( 'className' => $current_classes ),
		// I'm guessing this will come into play with group/columns, not sure.
		'innerBlocks'  => array(),
		// The actual content.
		'innerHTML'    => $new_content,
		// Like innerBlocks, I guess this will is used for groups/columns.
		'innerContent' => array( $new_content ),
	);

	$updated_post_content .= serialize_block( $new_block );
}

// Update the post with the content.
wp_update_post( array( 'ID' => $post->ID, 'post_content' => $updated_post_content, ) );

Run this code once and it will update this one post, ID 898 in this case. Pretty much every line has an explaining comment. But let’s go through the main steps.

First we get the post we want and convert to content to an array of blocks.
We loop over these blocks one by one.
As we only are changing the paragraphs we check and re-add other block types.

The paragraph blocks we load as a DomDocument. We only need to add or edit one attribute. you might be able to do this with a str_replace but DomDocument is more flexible and it can handle edge cases with more safety.

After that we check for existing classes. And add our newer class to the existing ones.
Even if there are no existing ones that’s fine.
Once the class is added we re-render the html content of the block.

The last thing we do is re-create an array that is the definition of a block. And render is as a html block.

Finally after all blocks are looped over we update the post.
You now updated all the Paragraph Gutenberg blocks in the post.

Change multiple posts

Now we covered updating a single post, we can pretty much wrap this in a loop to cover all posts.

<?php
$all_post_types = get_posts( array(
	'post_type'   => array( 'post', 'page' ), // add any posttype you would want to check.
	'post_status' => 'any', // all except trash.
	'nopaging'    => true, // no limits, get all posts.
) );

foreach ( $all_post_types as $post ) {
	// insert the code from the sample above
}

Closing thoughts

Keep in mind the final code you only need to run it once.
Before doing this I recommend creating a test post with a bunch of the same blocks but with all kind of options/settings tweaked. That should give you a good test case.

And of course create a backup before converting all posts and do deep check when it’s done.

Add Color Pallets to Gutenberg child theme

How to customize the color pallets you can select in a gutenberg block in your theme. And the tweak if you are running a childtheme.

In Gutenberg you can customize the colors of the some blocks like the paragraph block (this is a paragraph block.) The theme can add some default colors to pick from or a user can pick his a custom color.

Define theme color pallets

In a theme add the following code to the functions.php:

add_theme_support( 'editor-color-palette', array(
	array(
		'name'  => __( 'grey', 'theme translation' ),
		'slug'  => 'grey',
		'color' => '#111111',
	),
	array(
		'name'  => __( 'red', 'theme translation' ),
		'slug'  => 'red',
		'color' => '#ee4035',
	),
	array(
		'name'  => __( 'orange', 'theme translation' ),
		'slug'  => 'orange',
		'color' => '#f37736',
	),
	array(
		'name'  => __( 'yellow', 'theme translation' ),
		'slug'  => 'yellow',
		'color' => '#fdf498',
	),
	array(
		'name'  => __( 'green', 'theme translation' ),
		'slug'  => 'green',
		'color' => '#7bc043',
	),
	array(
		'name'  => __( 'blue', 'theme translation' ),
		'slug'  => 'blue',
		'color' => '#0392cf',
	),
) );

Above is the example of the 6 colors that this theme uses. The name is used when hovering over the color pallet. the slug shouldn’t matter as long as they are different.
Finally the color is simple hexadecimal color notation.

This will work on normal theme’s. But not on child themes.

Define color pallets for child themes.

The following might differ a bit depending on the parent theme setup. But the trick is to add the colors after the parent does it. My theme is a child of twentysixteen.

The best way to overrule the parent should be to add the code as this:

function janwsixteen_after_setup() {
	// Adds support for editor color palette.
	add_theme_support( 'editor-color-palette', array(
		array(
			'name'  => __( 'grey', 'theme translation' ),
			'slug'  => 'grey',
			'color' => '#111111',
		),
		array(
			'name'  => __( 'red', 'theme translation' ),
			'slug'  => 'red',
			'color' => '#ee4035',
		),
		array(
			'name'  => __( 'orange', 'theme translation' ),
			'slug'  => 'orange',
			'color' => '#f37736',
		),
		array(
			'name'  => __( 'yellow', 'theme translation' ),
			'slug'  => 'yellow',
			'color' => '#fdf498',
		),
		array(
			'name'  => __( 'green', 'theme translation' ),
			'slug'  => 'green',
			'color' => '#7bc043',
		),
		array(
			'name'  => __( 'blue', 'theme translation' ),
			'slug'  => 'blue',
			'color' => '#0392cf',
		),
	) );
}
add_action( 'after_setup_theme', 'janwsixteen_after_setup', 100 );

Now this code will run during the after_setup_theme event at priority 100 (which is a late priority).
If this does not work I recommend setting replacing the action after_setup_theme to init.

Check DNS records in terminal

Currently I’m working on a big server migration. Where a lot of small sites get moved over. And a lot of the domain names are registered and managed at third parties.
So after each move I have to check a lot of DNS records to see if the move is final.

Usually I use the mxtoolbox which is great to check DNS records.
But I needed a more bulk option for this mass migration.

Enter the dig command

Dig commands

Check A record:

dig example.com A +short

Check AAAA record (ipv6)

dig example.com AAAA +short

Check CNAME

dig example.com CNAME +short

Check A, AAAA and CNAME all at once.

dig  example.com AAAA example.com CNAME example.com A +short

These commands return very compact ip’s or domainnames.
Which are easy to scan.

I’m not sure if these are cached.

WordPress Settings API save post

The WordPress Settings API (docs) is used to create settings pages and save settings in the options table. But I didn’t need a setting, I needed a settings page to create/update a post.

Of course I could have created my own custom settings page and not use the settings API. But the settings API does have a couple of advantages.

  • Nonces validation.
  • Still using a WordPress standard.
  • Saving and error notices by default and customizable.
  • Mix with regular options.

First off create a settings page like we are used to:

<?php

add_action( 'admin_menu', 'register_menu_item' );

function register_menu_item() {
	add_submenu_page(
		'options-general.php',
		__( 'Settings page title' ),
		__( 'Menu title' ),
		'manage_options',
		'page-slug',
		function () {
			require './templates/settings-page.php';
		}
	);
}

add_action( 'admin_init', 'register_settings' );

/**
 * Register settings to save.
 */
function register_settings() {
	register_setting(
		'fake-option',
		'fake-option',
		array( 'sanitize_callback' => 'save_post' )
	);
}

/**
 * This function will only be called when the "option" is valid to save.
 * Here we validate our own fields and create the post we want.
 * 
 * @param null|array $unused_fake_setting
 */
function save_post( $unused_fake_setting ) {
	// Nonces are validated before we get here.

	static $first_run = true; // this function can be called twice, but we only need it once.
	if ( ! $first_run ) {
		return;
	}
	$first_run = false; // Never run again, this request.

	if ( empty( $_POST['log_title'] ) ) {
		add_settings_error( 'fake-option', 'title-empty', __( "Log title can't be empty." ) );

		return;
	}
	$title = wp_unslash( $_POST['log_title'] );
	$title = sanitize_text_field( $title );

	// validate other fields...
    
    
    // save the post.
	wp_insert_post( array(
		'post_title' => $title
		// add other attributes.
	) );

	// success message, yes the function name is confusing.
	add_settings_error( 'wpjm-ch-save', 'term-added', __( 'Worker successfully connected', 'success' ) );

}

And the templates/settings-page.php file:

<div class="wrap">
    <h1><?php esc_html_e( 'Settings page title' ); ?></h1>
    <form action="options.php" method="POST">
		<?php settings_fields( 'fake-option' ); ?>
        <h2><?php esc_html_e( 'Add log' ); ?></h2>

        <table class="form-table" role="presentation">
            <tbody>
            <tr>
                <th scope="row"><label for="log_title"><?php esc_html_e( 'Log title' ); ?></label></th>
                <td><input name="log_title" id="log_title" type="url" class="regular-text" required/></td>
            </tr>
            <tr>
                <th scope="row">More fields</th>
                <td></td>
            </tr>
            </tbody>
        </table>
		<?php submit_button( __( 'Add log' ) ); ?>
    </form>
</div>

How it works

We basically add settings like normal, except we skip a few things
First off normally you would add a section using add_settings_section in the same function where we register the settings field(s). But as we don’t really render a settings field in the template, we don’t add it at all. The same goes for the do_settings_sections in the Template.

In the settings template we do have the regular settings_fields this makes sure all the nonce fields are printed. And the submit_button.

Finally the thing we do extra and where the magic happens.
In the sanitize_callback of the register_setting function we set the save_post function.
Normally this is for sanitizing the option you want to save. But in this case we are not saving a option, we are saving a post. In this function we validate all the data send by the form, sanitize the data and save a new post.

And when the data is validated and saved we send a success notice with the add_settings_error function. Yes, we send a success notice with the **_error function. It can be done with the final argument, by setting the type to ‘success’.

Handling multiple custom options.

In the project that sparked this post I need to create a post, but also list those posts with a delete button.
What I found useful was creating a second register_setting, put them in two separate <form> tags on the template and let one handle the creating and the other the deleting.

<?php
function register_settings() {
	register_setting(
		'fake-option-create',
		'fake-option-create',
		array( 'sanitize_callback' => 'save_post' )
	);
	register_setting(
		'fake-option-delete',
		'fake-option-delete',
		array( 'sanitize_callback' => 'delete_post' )
	);
}
function save_post()  {
    // handles saveing/creating.
}
function delete_post() {
    // handles deleting.
}
?>


<div class="wrap">
    <h1><?php esc_html_e( 'Settings page title' ); ?></h1>
    <form action="options.php" method="POST">
		<?php settings_fields( 'fake-option-create' ); ?>
        HTML FORM HERE
		<?php submit_button( __( 'Add log' ) ); ?>
    </form>
    <form action="options.php" method="POST">
		<?php settings_fields( 'fake-option-delete' ); ?>
        LIST POSTS HERE WITH CHECKBOXES
		<?php submit_button( __( 'Delete logs' ) ); ?>
    </form>
</div>

Other tweaks.

?php_version=7.4&platform=wordpress&platform_version=5.6&software=free&software_version=15.6.2&days_active=30plus&user_language=en_US

  • Create, delete and updates posts (and post meta)
  • The same for user(meta and tax(meta)
  • External API calls.
  • Mix in regular use of the settings API.
  • World peace.

Installing WordPress Coding standards Globally

When possible I like to code using the WordPress coding Standards. I always set my PHPstorm coding style to WordPress. And when I start a new plugin I implement the PHPCodeSniffer rules for WordPress.
In the most ideal scenario you can include these in your composer settings, but that’s not always possible so it’s handy to have them installed globally.

To install run these 2 commands:

composer global require --dev squizlabs/php_codesniffer wp-coding-standards/wpcs phpcompatibility/phpcompatibility-wp dealerdirect/phpcodesniffer-composer-installer wptrt/wpthemereview
composer global update --dev --with-dependencies

Now all you need is installed. In your global composer directory. You can get that running: composer config home -g
By default is should return `~/.config/composer`

To test the phpcs with the global dir I run: ~/.config/composer/vendor/bin/phpcs -i
The output should look like this:

Result of command is "The installed coding standards are PSR12, PEAR, PSR2, PSR1, Squiz, MySource, Zend, PHPCompatibility, PHPCompatibilityParagonieRandomCompat, PHPCompatibilityParagonieSodiumCompat, PHPCompatibilityWP, WordPress-Docs, WordPress, WordPress-Extra, WordPress-Core and WPThemeReview"
Installed phpcs rulesets

Running this whole command will get annoying and will cluter up the terminal. So we will put the bin directory in the global $PATH
The bin directory we already got. (but we can also get using composer global config bin-dir --absolute)

To your ~/.bashrc add:

export PATH="$HOME/.config/composer/vendor/bin:$PATH"

Open a new terminal and test the command phpcs -i it should now work globally and is ready to run on individual projects.

Sources:

Shadow Posttype

The case I was dealing with. There is a posttype “person” and it had the normal templates, a archive-person.php and single-person.php with a bunch of metadata.
Which where visible on example.com/person/john-doe and the archive example.com/persons.
So far nothing special.

Every “person” was a -well- person. Some persons where also a “Judge”. A judge was an extra metabox on the person edit page.
And it needed it’s own pages on the frontend. example.com/judge/john-doe and example.com/judges.

Adding the rewrites

<?php
function judge_rewrite_rule() {
	/**
	 * Single judge
	 * 
	 * set is_judge to 1
	 * set person to post slug
	 */
	add_judge_rewrite_rule( '^judge/([^/]+)/?', 'index.php?is_judge=1&person=$matches[1]', 'top' );

	/**
	 * Archive for judges
	 */
	add_rewrite_rule( '^judges/?', 'index.php?is_judge=1&post_type=person', 'top' );
}
add_action( 'init','rewrite_rule' );

function judge_rewrite_tag() {
	/**
	 * make the query_vars aware of `is_judge`
	 */
	add_rewrite_tag( '%is_judge%', '([0-9]+)' );
}
add_action( 'init', 'judge_rewrite_tag' );

The main 2 functions to look out for are add_rewrite_rule and add_rewrite_tag.
I register the query_var is_judge and set it when the url is /judges/ or /judge/john-doe

After adding this do flush the permalinks. Just go to the settings for permalinks and press save. No need to change anything.

Registering the templates

<?php
/**
 * Set the correct theme/template-file.php
 * the shadow single & archive.
 *
 * @param string $template
 *
 * @return string
 */
function assign_judge_template( $template ) {
	if ( 1 !== (int) get_query_var( 'is_judge' ) ) {
		return $template; // we only check for templates when the judge set.
	}

	// assign the correct template.
	if ( is_single() ) {
		return get_template_directory() . '/single-person-judge.php';
	}
	if ( is_post_type_archive( 'person' ) ) {
		return get_template_directory() . '/archive-person-judge.php';
	}


	return $template; // fallback.
}
add_filter( 'template_include', 'assign_judge_template', 10, 1 );

When the is_judge is set set a different template. In this case this these templates are in the theme folder. You could also set templates from a plugin. The naming is can be anything but I picked single-person-judge.php so the next person can easier find the relation to the post type.

Setting the correct posts for the judge archive

Now the single will work as you would expect. But the archive page will still include all persons, not just the judges. Lets fix that.

<?php
/**
 * @param WP_Query $query
 */
function judge_pre_get_posts( $query ) {
	if ( 1 !== (int) $query->get( 'is_judge' ) || $query->is_single() ) {
		// only do this check if `is_judge` is set.
		// for single's this check is not needed.
		return;
	}
	// the meta_query
	$meta_is_judge = [
	    'relation' => 'AND',
		[ 'key'     => 'is_judge', 'compare' => 'EXISTS',],
		[
            'key'     => 'is_judge',
            'value'   => '1',
            'compare' => '=',
		],
	];

	// This part is to make sure you don't override other possible existing meta_queries
	$existing_meta   = $query->get( 'meta_query' );
	if ( empty( $existing_meta ) ) {
		$query->set( 'meta_query', $meta_is_judge );
	} else {
		$query->set( 'meta_query', [ 'relation' => 'AND', $existing_meta, $meta_is_judge, ] );
	}

}

add_action( 'pre_get_posts', 'judge_pre_get_posts' , 10, 1 );

If you’re familiar with the pre_get_posts hook this should not be to hard.
We select all posts that have the post_meta is_judge set to 1.
The way I add the meta_query seems a bit excessive, but this makes sure you never override a possible existing meta_query which an other filter might have added.

Now every Judge should be listed on example.com/judge and every individual judge should be visible on example.com/judge/john-doe. Only thing left to do is link to the page.

Linking to the judge url

As we want a person to be visible on both example.com/person/john-doe and example.com/judge/john-doe we can’t override the default permalink. So the best next thing is to create a helper function for calling the judge link.
This function should be used where you need to link to example.com/judge/john-doe, like on the judge archive page.

<?php
/**
 * Helper function
 *
 * @param int|WP_Post|null $post
 *
 * @return string, if not a judge, return normal permalink
 */
function get_judge_permalink( $post = null ) {
	$post = get_post( $post );
	if ( is_null( $post ) ) {
		return null;
	}

	// if the meta `is_judge` is not set, return the default person permalink.
	// for your use cases you might want to return something different.
	if ( '1' !== get_post_meta($post->ID, 'is_judge', true ) ) {
		return get_the_permalink( $post );
	}

	return home_url( 'judge/' . $post->post_name );
}

The function can be used in the same way as the regular get_the_permalink. get_judge_permalink(); the post argument is optional, and can also be a post_id.

Closing thoughts

In the template files, I suggest adding a very clear comment at the top, which explains where the rewrite functions are. An other person will otherwise have a hard time to find where this is created.

The list of judges will now automatically appear on the /judge/ archive page. If you need to create this list on a custom WP_query just add the query_var is_judge, example:

<?php
$q_args = array(  
	'post_type' => 'person',
	'is_judge' => 1, // this will get picked up by the `judge_pre_get_posts` filter
	// other arguments
);
$custom_query = new WP_Query($q_args);
// do what you want

Gravity Forms give editors access

By default Gravity forms isn’t accessible by editors. There are ways to do this with plugins like members.
As some might guess I prefer wp-cli for this:

wp cap add editor gravityforms_create_form gravityforms_edit_forms gravityforms_view_entries gravityforms_export_entries gravityforms_delete_entries graviyforms_delete_forms gravityforms_edit_entries gravityforms_view_entry_notes gravityforms_edit_entry_notes --grant
Difference in rights after setting capabilities

Running this will pretty much give all rights except the settings of Gravifty Forms.
It will allow creatign, editing & deleting forms. But also view entries and export them.

Full list of Gravity Forms Capabilities

Nano Shortcuts

I’ve know about nano and some it’s shortcuts. Today I explored them a bit more deeply.
So a list of shortcuts I find useful:

  • ctrl+K Cut the current line and put it in the nano clipboard (it’s not the same as the general clipboard)
  • ctrl+U Paste the line
  • ctrl+W Open search, type and hit enter. For the next match press alt+W
  • ctrl+Q Search backwards. For the next backward match press alt+Q
  • alt+U Undo
  • alt-E Redo
  • alt+C show the line number
  • alt-G Go to line number

Sources:

  • https://www.nano-editor.org/dist/latest/cheatsheet.html
  • https://askubuntu.com/a/672091/208343

PHP namespaces grouped

Consider the following code:

<?php
namespace abc;
use function is_string;
use function get_class;

class main {
    protected $string;
    public function __construct( $string ) {
        if ( is_string( $string )) {
            $this->string = $string;
        } else {
            $this->string = get_class( $this );
        }
    }
}

Nothing fancy. Just a class in a namespace using two functions from the global namespace (is_string & get_class).
Those two functions are imported from the global namespace as that will give a small performance boost.

But if you have 20-30 build in PHP functions that list will get very long….

Luckily you can merge them:

use function is_string, get_class;

For now I’m not sure I’ll always import build in PHP functions, the boost is small. And it’s annoying to keep track of.