Blog

Tutorial: Column "Application" in Oxygen Templates List

This is only a tutorial!
There's a current version of this snippet.

Contents

Task

Oxygen is a powerful plugin for WordPress that deactivates the WordPress theme, supports the creation of own templates with a visual builder and thus enables a fully individual design for your website.

Within each Oxygen template you define the conditions under which it will be used, e.g. for the archive or blog, search results, single pages or posts, for all or only certain categories, authors, etc.

Unfortunately, these conditions are not visible in the list of templates.
When working with a larger number of templates, the overview is quickly lost.

Solution

So I developed a code snippet that displays the defined conditions for each template in a new column "Application" in the list of Oxygen templates.

Development

In this article I describe how I implemented the requirement in a code snippet.
With this I want to give an insight into how the development is done and how to achieve the end result in this case.
Some basic knowledge of WordPress development is a prerequisite for understanding this article.

Data source

To solve this task I first have to understand how the conditions for the conditions for using the templates are stored in the WordPress database. For this purpose I create a template "Demo", where I check or fill in all conditions.
In the table postmeta I inspect the corresponding meta data:

Entries for an Oxygen Template in the table postmeta

The structure and meaning of the data is clear to me quite fast.

Code Snippet

My favorite method to implement code is the plugin Code Snippets. Here I create a new snippet.
It only needs to be executed in the WordPress administration area. So I set the toggle "Only run in administration areas".

New Column in Template List

From previous projects I already know the procedure to add new columns to administrative list views in WordPress and to fill them with data.
Basically these are two steps for which WordPress provides filter hooks:

  1. Add a column with title using a filter hook to manage_xxx_posts_columns, where the xxx is replaced by the post type slug (post, page, …)
  2. Add column content using a filter hook to manage_xxx_posts_custom_column, where the xxx is replaced by the … see above.

Oxygen templates have the post type ct_template.
This results in the world changing formula xxx = ct_template.

With this knowledge I can create the basic framework in my Code Snippet as follows:

<?php 
// add column title
add_filter('manage_ct_template_posts_columns', 'maltmann_oxygen_template_table_head');
function maltmann_oxygen_template_table_head( $columns ) {
	$columns['maltmann_oxy_tpl_apply']  = 'Application';
	return $columns;
}

// add column content
add_action( 'manage_ct_template_posts_custom_column', 'maltmann_oxygen_template_table_content', 10, 2 );
function maltmann_oxygen_template_table_content( $column_name, $post_id ) {
	if ($column_name != 'maltmann_oxy_tpl_apply') return;
    echo 'my column content';
}

In line 2 I add a new filter to the hook manage_ct_template_posts_columns.
Here xxx is already replaced by the correct post type ct_template.
This hook calls my own function maltmann_oxygen_template_table_head().
In the parameter $columns an array with the previously registered table columns is passed to my function.
I add my new column named maltmann_oxy_tpl_apply to the array and return the new array $columns.

After that in line 9 follows my filter for the hook manage_ct_template_posts_custom_column, which I use to output the column contents.
At the very beginning I check in line 11 whether the hook was actually called for my new column. If not, get out!
Then I output the content of the column in line 12. Let's make a provisional "my column content" first.
Save it. Test it.
Looks already acceptable:

My new hooks work.

Determine metadata

To access the data stored in the table postmeta from my snippet in the first place, I have to fetch it.

This works quite well with the function get_post_meta().
Since this function requires a $post_id parameter, I have to get access to the current $post first.

<?php
function maltmann_oxygen_template_table_content( $column_name, $post_id ) {
	if ($column_name != 'maltmann_oxy_tpl_apply') return;
	
	global $post;
	$meta = get_post_meta($post->ID);
}

I'm a bit worried because the function get_post_meta() returns all meta data for this $post from the database without any restriction to certain meta data. This would also include all tens of revisions for each template. Of course I want to avoid this.
However, by debugging I quickly find out that the meta data was read in one go when the templates were listed and with the function get_post_meta() I access the data already in the cache. All fine.

Populate column: 1) Other

The easiest seems to be the conditions for the "Other" section.
Although the assignment is quite clear, I set and delete these conditions alternately to have the changes confirmed in the database.

Assignment of conditions "Other" in database

With this knowledge I can output the first conditions in my new column.

<?php
function maltmann_oxygen_template_table_content( $column_name, $post_id ) {
	if ($column_name != 'maltmann_oxy_tpl_apply') return;
	
	global $post;
	// get_post_meta does not fire a db query since post meta is still cached from listing
	$meta = get_post_meta($post->ID);

	// --- Others ----------------------------------------------------------
	// collect settings for Oxygen's Other section
	$apply = [];
	if (@$meta['ct_template_front_page'][0] == 'true') 		{$apply[] = 'Front Page';}
	if (@$meta['ct_template_blog_posts'][0] == 'true') 		{$apply[] = 'Blog Posts Index';}
	if (@$meta['ct_template_search_page'][0] == 'true') 	{$apply[] = 'Search Page';}
	if (@$meta['ct_template_404_page'][0] == 'true') 		{$apply[] = '404';}
	if (@$meta['ct_template_inner_content'][0] == 'true') 	{$apply[] = 'Inner Content';}
	if (@$meta['ct_template_index'][0] == 'true') 			{$apply[] = '<i>Catch All</i>';}
	// Others Final Output
	if (count($apply)) {echo '<b>Others:</b><br/>' . join('<br/>',$apply).'<br/>';}
}

In line 10 I first define an array $apply, where I will store all conditions.
Then I can read all previously identified conditions from the metadata and store them in an understandable form in my array.
WordPress delivers meta data as array. Therefore I have to access the first element of the array with [0]. And since it is not guaranteed that I get back valid data at all, I access the elements with the prefix @. This suppresses warnings in the debug log.
Afterwards I check in line 18 if I have collected any conditions in the section "Other" and output them a bit prettified.
Save. Test.
I am satisfied:

My hooks show first data from the "Others" section.

Populate column: 2) Singular

Next level of difficulty are the conditions in the section "Singular". Here we have the definition of single post types, taxonomies and parent IDs. This will be more exciting.
In the database I identify the following links:

Assignment of conditions "Singular" in database

Beside the simple boolean fields I see here serialized stored data ("a:2:{i:0;s:11…"). I have to convert them back into data structures first. In PHP this is normally done with unserialize(). But WordPress offers an even more practical function maybe_unserialize(), which doesn't fail if the data cannot be deserialized.

The additional code looks like this:

<?php
    // --- Singular --------------------------------------------------------
    // collect settings for Oxygen's Sigular section
    $apply= [];
    // Singular Post Types
    $post_types = [];
    if (@$meta['ct_template_single_all'][0] == 'true') 		{$post_types[] = '<i>All</i>';}
    $specific_post_types = maybe_unserialize(@$meta['ct_template_post_types'][0]);
    if (is_array($specific_post_types) && count($specific_post_types)) {
    	$post_types = array_merge($post_types,$specific_post_types);
    }
    if (is_array($post_types) && count($post_types)) {
    	$apply[] = 'Post Types: ' . join(', ',$post_types);
    }
    // Singular Taxonomies
    if (@$meta['ct_use_template_taxonomies'][0]=='true') {
    	$taxonomies = maybe_unserialize(@$meta['ct_template_taxonomies'][0]);
    	if (is_array($taxonomies) && count($taxonomies)) {
    		$apply[] = 'Taxonomies: ' . join(', ',$taxonomies);
    	}
    }
    // Singular Parents
    if (@$meta['ct_template_apply_if_post_of_parents'][0]=='true') {
    	$parents = maybe_unserialize(@$meta['ct_template_post_of_parents'][0]);
    	$apply[] = 'Parent IDs: '.join(', ',$parents);
    }
    // Singular Final Output
    if (count($apply)) {echo '<b>Singular:</b><br/>'.join('<br/>',$apply).'<br/>';}

Save. Test.
Not quite how I imagine it yet:

Hooks show data from the "Singular" section

Then I probably need some helper functions to display post types localized and to convert taxonomies (categories, keywords) from ID (or array) to text.

<?php
//-----------------------------------------------------------------------------
// Helper function to translate post type slugs (page, post, ...) to localized names (Seiten, Beiträge, ...)
function _maltmann_parse_oxy_post_types($post_types) {
	$retval = [];
	if (is_array($post_types) && count($post_types)) {
		foreach($post_types as $post_type) {
			$post_type_object = get_post_type_object($post_type);
			if ($post_type_object) {$retval[] = $post_type_object->label;}
		}
	}
	return $retval;
}
//------------------------------------------------------------------------------
// Helper function to retrieve term names from term IDs list or Oxygen names,values array 
function _maltmann_parse_oxy_taxonomies($taxonomies) {
	$retval = [];
	if (is_array($taxonomies) && count($taxonomies)) {
		if (array_key_exists('values',$taxonomies)) {$taxonomies = $taxonomies['values'];}
		foreach ($taxonomies as $term_id) {
			$term = get_term($term_id);
			if ($term) {$retval[] = $term->name;}
		}
	}
	return $retval;
}

The function _maltmann_parse_oxy_post_types() determines the localized label for post types.

With the function _maltmann_parse_oxy_taxonomies() I determine the actual names of the taxonomies, i.e. categories and keywords, based on IDs. You can pass a simple array with IDs to the function as well as an array with the fields names and values.
These are two variants of how Oxygen stores taxonomies for different conditions. The interested reader is welcome to debug this in detail by himself. For 5 Euro I add the detailed explanations here.

In my opinion I have implemented sufficient error handling in both help functions to be prepared for all cases.

The function maltmann_oxygen_template_table_content() must now of course be adjusted a bit to implement the new functions.

<?php
	// --- Singular --------------------------------------------------------
	// collect settings for Oxygen's Sigular section
	$apply= [];
	// Singular Post Types
	$post_types = [];
	if (@$meta['ct_template_single_all'][0] == 'true') 		{$post_types[] = '<i>All</i>';}
	$specific_post_types = maybe_unserialize(@$meta['ct_template_post_types'][0]);
	if (is_array($specific_post_types) && count($specific_post_types)) {
		$post_types = array_merge($post_types,_maltmann_parse_oxy_post_types($specific_post_types));
	}
	if (is_array($post_types) && count($post_types)) {
		$apply[] = 'Post Types: ' . join(', ',$post_types);
	}
	// Singular Taxonomies
	if (@$meta['ct_use_template_taxonomies'][0]=='true') {
		$taxonomies = maybe_unserialize(@$meta['ct_template_taxonomies'][0]);
		$taxonomies =  _maltmann_parse_oxy_taxonomies($taxonomies);
		if (is_array($taxonomies) && count($taxonomies)) {
			$apply[] = 'Taxonomies: ' . join(', ',$taxonomies);
		}
	}
	// Singular Parents
	if (@$meta['ct_template_apply_if_post_of_parents'][0]=='true') {
		$parents = maybe_unserialize(@$meta['ct_template_post_of_parents'][0]);
		$apply[] = 'Parent IDs: '.join(', ',$parents);
	}
	// Singular Final Output
	if (count($apply)) {echo '<b>Singular:</b><br/>'.join('<br/>',$apply).'<br/>';}

Save. Test.

Singular conditions with localized post types and taxonomies in plain text

Populate column: 3) Archives

Now it will get funny. The "Archives" section consists mostly of references to something.
I search the references in the database again:

Assignment of conditions "Archive" in database

I already have helper functions for post types and taxonomies. For authors (users) I will need a new helper function:

<?php
//------------------------------------------------------------------------------
// retrieves author IDs to names
function _maltmann_parse_oxy_authors($authors) {
	$retval = [];
	if (is_array($authors) && count($authors)) {
		foreach ($authors as $user_id) {
			if ($user_id == 'all_authors') {$retval[] = '<i>All</i>'; continue;}
			$user = get_userdata($user_id);
			if ($user) {$retval[] = $user->user_login;}
		}
	}
	return $retval;
}

This helper function can also handle errors well.

After that I can prepare the conditions for the section "Archives" with the already existing resources.

<?php
	// --- Archive ---------------------------------------------------------
	// collect settings for Oxygen's Archive section
	$apply = [];
	// All Archives
	if (@$meta['ct_template_all_archives'][0]=='true') 		{$apply[] = '<i>All</i>';}
	// Archive Taxonomies
	if (@$meta['ct_template_apply_if_archive_among_taxonomies'][0]=='true') {
		$taxonomies = maybe_unserialize(@$meta['ct_template_archive_among_taxonomies'][0]);
		$taxonomies =  _maltmann_parse_oxy_taxonomies($taxonomies);
		if (is_array($taxonomies) && count($taxonomies)) {
			$apply[] = 'Taxonomies: ' . join(', ',$taxonomies);
		}
	}
	// Archive Post Types
	if (@$meta['ct_template_apply_if_archive_among_cpt'][0]=='true') {
		$post_types = maybe_unserialize(@$meta['ct_template_archive_post_types'][0]);
		$post_types = _maltmann_parse_oxy_post_types($post_types);
		if (is_array($post_types) && count($post_types)) {
			$apply[] = 'Post Types: ' . join(', ',$post_types);
		}
	}
	// Archive Authors
	if (@$meta['ct_template_apply_if_archive_among_authors'][0]=='true') {
		$authors = maybe_unserialize(@$meta['ct_template_authors_archives'][0]);
		$authors = _maltmann_parse_oxy_authors($authors);
		if (is_array($authors) && count($authors)) {
			$apply[] = 'Authors: ' . join(', ',$authors);
		}
	}
	// Archive Date
	if (@$meta['ct_template_date_archive'][0] == 'true') 	{$apply[] = 'Date Archive';}
	// Archive Final Output
	if (count($apply)) {echo '<b>Archive:</b><br/>'.join('<br/>',$apply).'<br/>';}

Save. Test.
THIS looks good!

Final solution.
magnifier