Front to Back, take 2, step 04

Editing a post meta in the Theme Customizer.

Where is this going?

I’ve resumed work on a plugin idea I had worked on some time ago.
The pressing need at the time was to be able to quickly set up a WordPress site that would be made mainly of pages and some custom post types used for archive purposes only.
The need behind a site that’s built around less than ten pages is to be able to have an almost 1 to 1 relation between pages and templates and WordPress template hierarchy allows for such a thing to happen using template files in the page-{page-name}.php format. Each page a universe of its own in terms of custom fields, Posts to Posts relations and other customizations means using custom fields or property in the page template file and defining those custom fields or properties some place like a theme functions.php file or a plugin.
Being able to define and use custom fields and properties in one place only, the template file, would cut the work in half in a way not dissimilar to what happens in the Perch CMS.
While the first attempt was a failure this second attempt of mine does not revolutionize the concept but will leverage the Theme Customizer to be able to edit each page properties there in place of relying on meta boxes populating each page edit screen.

Done

I’m still testing the waters for technical possibilities and pitfalls and so far being being able to hide and show Theme Customizer components on specific pages or being able to edit and save a post title and contents is already a great progress.
Furthermore the Kirky library has been a pleasant and rich finding.

Custom fields in the page template file

Also known as “post meta” a custom field is any record stored in the the postmeta table and that extends the properties, or fields, of a post beyond the default ones defined in the posts table.
A notable custom field example is the _thumbnail_id one: nothing else but the post featured image ID.
To test the concept I’ve modified the template for the “About us” page, page-about-us.php to use the page featured image and a caption.
Both fields would not make sense as theme customizations and custom fields are the right place to store that information.
The relevant part of the page template is the one below

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
    <header class="entry-header">
        <h1 class="entry-title">
            <?php the_title('<h1 class="entry-title">', '</h1>'); ?>
        </h1>
    </header><!-- .entry-header -->

    <div style="padding-bottom: 1em;margin-bottom: 1em;border-bottom: solid lightgrey 1px;">
        <div style="padding: 12px;">
            <img src="<?php echo get_the_post_thumbnail_url(get_the_ID(), 'large') ?>"
                 alt="<?php echo get_post_meta(get_the_ID(), '_featured_image_caption', true) ?>">
        </div>
        <div>
            <p style="text-align: center; font-size: small;">
                <?php echo get_post_meta(get_the_ID(), '_featured_image_caption', true) ?>
            </p>
        </div>
    </div>

    <div class="entry-content">
        <?php
        the_content();

        wp_link_pages(array(
            'before' => '<div class="page-links"><span class="page-links-title">' . __('Pages:', 'twentysixteen') . '</span>',
            'after' => '</div>',
            'link_before' => '<span>',
            'link_after' => '</span>',
            'pagelink' => '<span class="screen-reader-text">' . __('Page', 'twentysixteen') . ' </span>%',
            'separator' => '<span class="screen-reader-text">, </span>',
        ));
        ?>
    </div><!-- .entry-content -->

    <?php
    edit_post_link(
        sprintf(
        /* translators: %s: Name of current post */
            __('Edit<span class="screen-reader-text"> "%s"</span>', 'twentysixteen'),
            get_the_title()
        ),
        '<footer class="entry-footer"><span class="edit-link">',
        '</span></footer><!-- .entry-footer -->'
    );
    ?>

</article><!-- #post-## -->

Filtering the post meta

In a way similar to what I’ve done for the post title and content I will filter the post meta when rendering the template in the Theme Customizer and take care of saving the values set for the custom fields when the user hits the “Save and Publish” button.
I’m still experimenting here and I’ve modified the front-to-back.php plugin file as little as possible

/**
 * Plugin Name: Front to Back
 * Plugin URI: http://theAverageDev.com
 * Description: Easy page templating for developers.
 * Version: 1.0
 * Author: theAverageDev
 * Author URI: http://theAverageDev.com
 * License: GPL 2.0
 */

include "vendor/autoload_52.php";

$page_locator = new FTB_Locators_Page();
$about_us_page = new FTB_Pages_AboutUs(new FTB_Locators_Page());

$config_id = 'front-to-back-example';

Kirki::add_config($config_id, array(
    'capability' => 'edit_theme_options',
    'option_type' => 'theme_mod',
));

Kirki::add_panel('ftb-page-about_us-panel-customizations', array(
    'title' => 'Page customization',
    'active_callback' => array($page_locator, 'is_about_us'),
    'priority' => 150,
));

Kirki::add_section('ftb-page-about_us-section-content', array(
    'title' => 'Content',
    'panel' => 'ftb-page-about_us-panel-customizations',
));

Kirki::add_field($config_id, array(
    'settings' => 'ftb-page-about_us-featured_image',
    'label' => 'Featured image',
    'section' => 'ftb-page-about_us-section-content',
    'type' => 'image',
));

Kirki::add_field($config_id, array(
    'settings' => 'ftb-page-about_us-featured_image_caption',
    'label' => 'Featured image caption',
    'section' => 'ftb-page-about_us-section-content',
    'type' => 'text',
));

Kirki::add_field($config_id, array(
    'settings' => 'ftb-page-about_us-title',
    'label' => 'Title',
    'section' => 'ftb-page-about_us-section-content',
    'type' => 'text',
    'default' => 'About us',
));

Kirki::add_field($config_id, array(
    'settings' => 'ftb-page-about_us-content',
    'label' => 'Content',
    'section' => 'ftb-page-about_us-section-content',
    'type' => 'textarea',
    'default' => 'We are skilled',
));

add_action('customize_preview_init', 'ftb_add_about_us_page_filters');
function ftb_add_about_us_page_filters()
{
    $about_us_page = new FTB_Pages_AboutUs(new FTB_Locators_Page());

    add_filter('the_title', array($about_us_page, 'filter_the_title'), 0, 2);
    add_filter('the_content', array($about_us_page, 'filter_the_content'), 0, 2);
    add_filter('get_post_metadata', array($about_us_page, 'filter_get_post_metadata'), 0, 3);
}

add_action('customize_save_after', array($about_us_page, 'on_customize_save_after'), 10, 1);

The relevant code modification is the filter on the get_post_metadata function

add_filter('get_post_metadata', array($about_us_page, 'filter_get_post_metadata'), 0, 3);

and the changes to the FTB_Pages_AboutUs class to accommodate for the new required functionalities.

Meta inception and update

The FTB_Pages_AboutUs class is responsible for filtering and updating the custom fields; in this particular application this means adding the handling of the featured image and its caption.
The featured image is a custom field proper storing an attachment post type ID and the way WordPress uses it is not different from a call to the get_post_meta function; as such hijacking it for Theme Customizer purposes is not difficult.
I’ve added the filter_get_post_metadata method and updated the on_customize_save_after to filter the featured image and caption while previewing and save the meta when the previewed changes are saved.

class FTB_Pages_AboutUs
{
    /**
     * @var FTB_Locators_Page
     */
    private $page_locator;

    public function __construct(FTB_Locators_Page $page_locator)
    {
        $this->page_locator = $page_locator;
    }


    public function filter_the_title($title, $post_id)
    {
        if ($post_id == $this->page_locator->get_about_us()->ID) {
            return get_theme_mod('ftb-page-about_us-title', 'About us');
        }

        return $title;
    }

    public function filter_the_content($content, $post_id = null)
    {
        global $post;
        if ($post && $post->ID == $this->page_locator->get_about_us()->ID) {
            return get_theme_mod('ftb-page-about_us-content', 'We are skilled');
        }

        return $content;
    }

    public function filter_get_post_metadata($value, $object_id, $meta_key)
    {
        $post = $this->page_locator->get_about_us();
        if ($post && $object_id == $post->ID) {
            switch ($meta_key) {
                case '_featured_image_caption':
                    $value = get_theme_mod('ftb-page-about_us-featured_image_caption', '');
                    break;
                case '_thumbnail_id':
                    $featured_image_url = get_theme_mod('ftb-page-about_us-featured_image', '');
                    $value = $this->get_attachment_id_from_url($featured_image_url);
                    break;
                default:
                    break;
            }
        }

        return $value;
    }

    public function on_customize_save_after(WP_Customize_Manager $wp_customize)
    {
        $title = $wp_customize->get_setting('ftb-page-about_us-title');
        $content = $wp_customize->get_setting('ftb-page-about_us-content');
        $featured_image = $wp_customize->get_setting('ftb-page-about_us-featured_image');
        $featured_image_caption = $wp_customize->get_setting('ftb-page-about_us-featured_image_caption');

        $featured_image_ID = $this->get_attachment_id_from_url($featured_image->value());

        $post_id = $this->page_locator->update_about_us(array(
            'post_title' => $title->value(),
            'post_content' => $content->value(),
            'meta_input' => array(
                '_thumbnail_id' => $featured_image_ID,
                '_featured_image_caption' => $featured_image_caption->value(),
            )
        ));
    }

    private function get_attachment_id_from_url($featured_image)
    {
        /** @var \wpdb $wpdb */
        global $wpdb;

        $id = $wpdb->get_var($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid = %s", $featured_image));

        return empty($id) ? false : $id;
    }
}

Next

Now that the idea seems to work on paper it’s time to put some code generation in place to have the Theme Customizer components registered “automagically” from the template file.