Front to Back, take 2, step 03

Editing a page title and content in the Theme Customizer.

Not reinventing the wheel

My first code excursion with the renewed “Front to Back” idea had nothing revolutionary in its use of the Theme Customizer API and was but an assessment of the possibilities offered by the active_callbackparameter on the API elementary components: panels, sections and controls.
While I’ve come up with a PHP magic method based utilty class I will tap into the Kirky library to add and manage Theme Customizer components: it offers a variety of ready to use components while wrapping some methods and functions.

Adding some flexibility to the page locator object

The first node of my experiment has been trying out how flexible the active_callback parameter could be in the scope of a component visibility.
Willing to delegate control of all content, and not just graphic and theme mods proper, to the Theme Customizer being able to hide and show controls (and panels and sections) in a relevant way is paramount.
While the first iteration of the code would allow for simple query methods to know if the previewed page is a specific one I’ve added some functionalities to it to allow for some page post fetching and updating

class FTB_Locators_Page
{
    protected $cache = array();

    public function __call($name, array $args = array())
    {
        // let's cache previously obtained results for the request
        if (isset($this->cache[$name])) {
            return $this->cache[$name];
        }

        // get the page snake_case post name and the request type and args
        $matches = array();
        preg_match('/(is|get|update)_([_A-Za-z09]*)/', $name, $matches);

        // if we have a match on one of the supported operations
        if (isset($matches[1]) && isset($matches[2])) {
            $request = $matches[1];
            $page = $matches[2];

            // make the post_name a URL friendly post-name
            $page_slug = str_replace('_', '-', $page);

            // look for the page in the DB
            $found = get_page_by_path($page_slug, OBJECT, 'page');

            // not found... false
            if (empty($found)) {
                return false;
            }

            $out = false;
            switch ($request) {
                case 'is':
                    // found and same page we are looking at?
                    $queried_post = $this->get_queried_post();
                    $out = $queried_post && $found->ID == $queried_post->ID;
                    break;
                case 'get':
                    $out = $found;
                    break;
                case'update':
                    return wp_update_post(array_merge(array('ID' => $found->ID), $args[0]));
                default:
                    $out = false;
                    break;
            }
        }

        $this->cache[$name] = $out;
        return $out;
    }

    private function get_queried_post()
    {
        global $wp_query;
        $posts = $wp_query->get_posts();

        // nothing to match against, bail
        if (empty($posts)) {
            return false;
        }

        // more than one post? Not a page for sure.
        if (count($posts) !== 1) {
            return false;
        }

        return $posts[0];
    }
}

Still nothing incredible but, sticking to the “About us” page example:

  • is_about_us() will return true if the previewed page is the “About us” one
  • get_about_us() will return the page post object (a WP_Post instance)
  • update_about_us($args) will updated the “About us” post with the specified entry

Setting title and content using theme modifications

Getting back to the experiment the first thing I’d like to do is allowing the user (alas “the person customizing the theme”) to set and edit a post title and content from the Theme Customizer.
This is very easy to do setting up a pair of page specific controls and the accessory panels and sections.

/**
 * 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();

$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-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',
));

The page template I’ve modified to use the template mods to show the page title and content.

// file 'page-about-us.php'
/**
 * The template for displaying the About us page.
 */

get_header(); ?>

<div id="primary" class="content-area">
    <main id="main" class="site-main" role="main">
        <?php
        // Start the loop.
        while (have_posts()) : the_post();
            ?>

            <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                <header class="entry-header">
                    <h1 class="entry-title">
                        <?php echo apply_filters('the_title', get_theme_mod('ftb-page-about_us-title','About us')) ?>
                    </h1>
                </header><!-- .entry-header -->

                <div class="entry-content">
                    <?php
                    echo apply_filters('the_content', get_theme_mod('ftb-page-about_us-content','We are skilled.'));

                    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-## -->

            <?php
            // If comments are open or we have at least one comment, load up the comment template.
            if (comments_open() || get_comments_number()) {
                comments_template();
            }

            // End of the loop.
        endwhile;
        ?>

    </main><!-- .site-main -->

    <?php get_sidebar('content-bottom'); ?>

</div><!-- .content-area -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

A less hacky approach maybe?

Using theme modifications to populate a page title and content is probably as bad practice as one can get though.
It would be better to be able to edit and modify them in the Theme Customizer, with its preview and all, to have it saved in the real post title and content when changes are committed by the user saving the modifications.
Being WordPress flexible as it is this required very little code to make it work.
I’ve reverted the relevant parts of the template to its original markup

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

<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 -->

and added some filtering in the plugin

<?php

/**
 * 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();

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

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

// ...same as above...

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());

    // use the theme mods early to populate values
    add_filter('the_title', array($about_us_page, 'filter_the_title'), 1, 2);
    add_filter('the_content', array($about_us_page, 'filter_the_content'), 1, 2);

    // when saving update the post
    add_action('customize_save_after', array($about_us_page, 'on_customize_after_save'), 10, 10);
}

The FTB_Pages_AboutUs class will handle the filtering taking care of real time preview (still using refresh transport though) and post updating.

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 on_customize_after_save(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');

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

Next

Since some main post attributes are manageable this way why not go all the way and cover custom fields (meta) too?