Filters

The plugin exposes WordPress filters to let the developer alter specific data.

wordless_pug_configuration

wordless/helpers/pug/wordless_pug_options.php
<?php

class WordlessPugOptions {
    public static function get_options() {
        $wp_debug = defined('WP_DEBUG') ? WP_DEBUG : false;
        return apply_filters( 'wordless_pug_configuration', [
            'expressionLanguage' => 'php',
            'extension' => '.pug',
            'cache' => Wordless::theme_temp_path(),
            'strict' => true,
            'debug' => $wp_debug,
            'enable_profiler' => false,
            'error_reporting' => E_ERROR | E_USER_ERROR,
            'keep_base_name' => true,
            'paths' => [Wordless::theme_views_path()],
            'mixin_keyword' => ['mixin','component'],
        ]);
    }
}

Usage example

<?php
add_filter('wordless_pug_configuration', 'custom_pug_options', 10, 1);

function custom_pug_options(array $options): array {
    $options['expressionLanguage'] = 'js';

    return $options;
}

wordless_acf_gutenberg_blocks_views_path

wordless/helpers/acf_gutenberg_block_helper.php
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
  function _acf_block_render_callback( $block ) {
    $slug = str_replace('acf/', '', $block['name']);

    // The filter must return a string, representing a folder relative to `views/`
    $blocks_folder = apply_filters('wordless_acf_gutenberg_blocks_views_path', 'blocks/');

    $admin_partial_filename = Wordless::theme_views_path() . "/{$blocks_folder}/admin/_{$slug}";

    if (
      file_exists( "{$admin_partial_filename}.html.pug" ) ||
      file_exists( "{$admin_partial_filename}.pug" ) ||
      file_exists( "{$admin_partial_filename}.html.php" ) ||
      file_exists( "{$admin_partial_filename}.php" )
    ) {
        $admin_partial = "{$blocks_folder}/admin/{$slug}";
    } else {
        $admin_partial = "{$blocks_folder}/{$slug}";
    }

Usage example

<?php
add_filter('wordless_acf_gutenberg_blocks_views_path', 'custom_blocks_path', 10, 1);

function custom_blocks_path(string $path): string {
    return 'custom_path';
}

This way Wordless will search for blocks’ partials in views/custom_path/block_name.html.pug so you can use render_partial('custom_path/block_name') to render them in your template.

The default path is blocks/.

Note

The path will be always relative to views/ folder

wordless_tmp_dir_exists

wordless/helpers/render_helper.php
<?php
/**
* Handles rendering of views, templates, partials
*
* @ingroup helperclass
*/
class RenderHelper {
    /**
    * Renders a preformatted error display view than dies
    *
    * @param  string $title       A title for the error
    * @param  string $description An explanation about the error
    */
    function render_error($title, $description) {
        ob_end_clean();
        require "templates/error_template.php";
        die();
    }

    /**
     * Retrive the actual template file path (searched in the views folder). It's possible to filter for an extension
     * passing in the argument $force_extension_match the nane ex. "pug"
     *
     * @param  string $name                     The template filenames
     * @param  array  $force_extension_match    The extension by which to filter the template file search
     */
    private function template_info($name, $force_extension_match = '') {
        $template = new stdClass;
        $template->path = null;
        $template->format = null;

        $valid_filenames = array(
            "$name.html.pug", // TODO: Plan to deprecate the double extension
            "$name.pug",
            "$name.html.php", // TODO: Plan to deprecate the double extension
            "$name.php",
        );

        if (!empty($force_extension_match)) {
            foreach($valid_filenames as $key => $filename) {
                $splitted_filename = explode('.', $filename);
                if (end($splitted_filename) != $force_extension_match) {
                    unset($valid_filenames[$key]);
                }
            }
        }

        foreach ($valid_filenames as $filename) {
            $path = Wordless::join_paths(Wordless::theme_views_path(), $filename);

            if (is_file($path)) {
                $template->path = $path;
                $arr = explode('.', $path);
                $template->format = array_pop($arr);
                break;
            }
        }
        return $template;
    }

    /**
    * Renders a template and its contained plartials. Accepts
    * a list of locals variables which will be available inside
    * the code of the template
    *
    * @param  string $name   The template filenames
    *
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the template
    *
    * @param boolean $static If `true` static rendering of PUG templates will
    *                        be activated.
    *
    */
    function render_template($name, $locals = array(), $static = false) {
        $template_found = $this->template_info($name);
        $template_path = $template_found->path;
        $format = $template_found->format;

        if (!isset($template_path)) {
          render_error("Template missing", "<strong>Ouch!!</strong> It seems that <code>$name.pug</code> or <code>$name.php</code> doesn't exist!");
        }

        $tmp_dir = Wordless::theme_temp_path();

        switch ($format) {
            case 'pug':
                require_once('pug/wordless_pug_options.php');

                if ($this->ensure_tmp_dir() ) {
                    // Read the environment from various sources. Note that .env file has precedence
                    if ( getenv('ENVIRONMENT') ) {
                        $env = getenv('ENVIRONMENT');
                    } elseif ( defined('ENVIRONMENT') ) {
                        $env = ENVIRONMENT;
                    } else {
                        $env = 'development';
                    }

                    // Read the option to bypass static cache from various sources. Note that .env file has precedence
                    if ( getenv('BYPASS_STATIC') ) {
                        $bypass_static = getenv('BYPASS_STATIC'); // getenv() returns a string
                    } elseif ( defined('BYPASS_STATIC') ) {
                        $bypass_static = var_export(BYPASS_STATIC, true); // constant could be a boolean so we uniform to a string representation
                    } else {
                        $bypass_static = 'false'; // default value
                    }

                    $env = apply_filters( 'wordless_environment', $env );

                    if ( in_array( $env, array('staging', 'production') ) ) {
                        if (true === $static && 'false' == strtolower($bypass_static)) {
                            $staticPath = $this->static_path($name, $locals);

                            if (file_exists($staticPath)) {
                                include $staticPath;
                            } else {
                                \Pug\Facade::setOptions(WordlessPugOptions::get_options());
                                \Pug\Facade::renderAndWriteFile($template_path, $staticPath, $locals);
                                include $staticPath;
                            }
                        } else {
                            \Pug\Optimizer::call(
                                'displayFile', [$template_path, $locals], WordlessPugOptions::get_options()
                            );
                        }
                    } else {
                        \Pug\Facade::setOptions(WordlessPugOptions::get_options());
                        if (true === $static && 'false' == $bypass_static) {
                            $staticPath = $this->static_path($name, $locals);

                            if (file_exists($staticPath)) {
                                include $staticPath;
                            } else {
                                \Pug\Facade::renderAndWriteFile($template_path, $staticPath, $locals);
                                include $staticPath;
                            }
                        } else {
                            \Pug\Facade::displayFile($template_path, $locals);
                        }
                    }
                } else {
                    render_error("Temp dir not writable", "<strong>Ouch!!</strong> It seems that the <code>$tmp_dir</code> directory is not writable by the server! Go fix it!");
                }

                break;

            case 'php':
                include $template_path;
                break;

            default:
                render_error("Template missing", "<strong>Ouch!!</strong> It seems that <code>$name.pug</code> or <code>$name.php</code> doesn't exist!");
        }
    }

    /**
     * Wraps render_template() function activating the static rendering strategy
     *
     * @param string $name Template path relative to +views+ directory
     * @param array $locals Associative array of variable that will be scoped into the template
     * @return void
     */
    function render_static($name, $locals = array()) {
        $template_found = $this->template_info($name, 'pug');
        if (isset($template_found->path)) {
            $fileInfo = new SplFileInfo($template_found->path);
            $extension = $fileInfo->getExtension();
        }
        if (!isset($extension) || 'pug' !== $extension) {
            render_error("Static rendering only available for PUG templates", "<strong>Ouch!!</strong> It seems you required a <code>render_static</code> for a PHP template, but this render method is supported only for PUG. Use <code>render_partial</code> or <code>render_template</code> instead.");
        }

        render_template($name, $locals, $static = true);
    }

    /**
    * Retrievs contents of partial without printing theme
    * @param string $name The template filenames (those not starting
    *                        with an underscore by convention)
    *
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the partial
    */
    function get_partial_content($name, $locals = array()) {
        ob_start();
        render_partial($name, $locals);
        $partial_content = ob_get_contents();
        ob_end_clean();
        return $partial_content;
    }

    /**
    * Renders a partial: those views followed by an underscore
    *   by convention. Partials are inside theme/views.
    *
    * @param  string $name   The partial filenames (those starting
    *                        with an underscore by convention)
    *
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the partial
    */
    function render_partial($name, $locals = array(), $static = false) {
        $parts = preg_split("/\//", $name);
        if (!preg_match("/^_/", $parts[sizeof($parts)-1])) {
            $parts[sizeof($parts)-1] = "_" . $parts[sizeof($parts)-1];
        }
        render_template(implode("/", $parts), $locals, $static);
    }

    /**
    * Renders a view. Views are rendered based on the routing.
    *   They will show a template and a yielded content based
    *   on the user requested page.
    *
    * @param  string $name   Filename with path relative to theme/views
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the view
    *
    * @deprecated 5.0
    */
    function render_view($name, $layout = 'default', $locals = array()) {
        ob_start();
        global $current_view, $current_locals;

        $current_view = $name;
        $current_locals = $locals;

        render_template("layouts/$layout", $locals);
        ob_flush();
    }

    /**
    * Yield is almost inside every good templates. Based on the
    *   rendering view yield() will insert inside the template the
    *   specific required content (usually called partials)
    *
    * @see render_view()
    * @see render_template()
    *
    *
    */
    function wl_yield() {
        global $current_view, $current_locals;
        render_template($current_view, $current_locals);
    }

    private function ensure_tmp_dir() : bool {
        $tmpDir = Wordless::theme_temp_path();
        $tmp_dir_exists_and_writable = $this->ensure_dir( $tmpDir );

        return apply_filters('wordless_tmp_dir_exists', $tmpDir, $tmp_dir_exists_and_writable );
    }

    private function ensure_dir( $dir ) : bool {

        $dir_exists_and_writable = false;

        if (!file_exists($dir)) {
            mkdir($dir, 0770);
        }

        if (!is_writable($dir)) {
            chmod($dir, 0770);
        }

        if (is_writable($dir)) {
            $dir_exists_and_writable = true;
        }

        return $dir_exists_and_writable;
    }

    // REALLY IMPORTANT NOTE: the cache policy of static generated views is based on the
    // view's name + the SHA1 of serialized $locals. As it stands the best way
    // to introduce business logic in the expiration logic is to pass ad hoc extra variables
    // into the $locals array. For example having
    //     render_template('pages/photos', $locals = [ 'cache_key' => customAlgorithm() ], $static = true)
    // when `customAlgorithm()` will change, it will automatically invalidate the static cache for this
    // template
    private function static_path(string $name, array $locals): string {
        $tmp_dir = Wordless::theme_temp_path();

        return Wordless::join_paths(
            $tmp_dir,
            basename($name) . '.' . sha1(serialize($locals)) . '.html'
        );
    }

}

Wordless::register_helper("RenderHelper");

Usage example

<?php
function validateWordlessTmpDir( $tmpdir, $coreCheckResult ){

    if (false == $coreCheckResult) { // Just giving examples
        sendAlertToSysadmin();
    }

    $file_counts = 0;

    if ( file_exists( $tmpdir ) ) {
        $file_counts = preg_grep('/(.*).(php|txt)$/', scandir( $tmpdir ) );
    }

    return count( $file_counts ) > 0;
}

add_filter('wordless_tmp_dir_exists', 'validateWordlessTmpDir', 10, 2);

Sometimes tmp folder in theme directory, may not have write permission in dedicated server, Hence failure to load pug template from tmp. In tmp directory, if there is compiled files listed following hook can be used to check file counts and override ensure_tmp_dir function to return true. In some cases files can be compiled via command line to generate files in tmp dir. here the filter code is added in themes/example-theme/config/initializers/hooks.php

wordless_environment

wordless/helpers/render_helper.php
<?php
/**
* Handles rendering of views, templates, partials
*
* @ingroup helperclass
*/
class RenderHelper {
    /**
    * Renders a preformatted error display view than dies
    *
    * @param  string $title       A title for the error
    * @param  string $description An explanation about the error
    */
    function render_error($title, $description) {
        ob_end_clean();
        require "templates/error_template.php";
        die();
    }

    /**
     * Retrive the actual template file path (searched in the views folder). It's possible to filter for an extension
     * passing in the argument $force_extension_match the nane ex. "pug"
     *
     * @param  string $name                     The template filenames
     * @param  array  $force_extension_match    The extension by which to filter the template file search
     */
    private function template_info($name, $force_extension_match = '') {
        $template = new stdClass;
        $template->path = null;
        $template->format = null;

        $valid_filenames = array(
            "$name.html.pug", // TODO: Plan to deprecate the double extension
            "$name.pug",
            "$name.html.php", // TODO: Plan to deprecate the double extension
            "$name.php",
        );

        if (!empty($force_extension_match)) {
            foreach($valid_filenames as $key => $filename) {
                $splitted_filename = explode('.', $filename);
                if (end($splitted_filename) != $force_extension_match) {
                    unset($valid_filenames[$key]);
                }
            }
        }

        foreach ($valid_filenames as $filename) {
            $path = Wordless::join_paths(Wordless::theme_views_path(), $filename);

            if (is_file($path)) {
                $template->path = $path;
                $arr = explode('.', $path);
                $template->format = array_pop($arr);
                break;
            }
        }
        return $template;
    }

    /**
    * Renders a template and its contained plartials. Accepts
    * a list of locals variables which will be available inside
    * the code of the template
    *
    * @param  string $name   The template filenames
    *
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the template
    *
    * @param boolean $static If `true` static rendering of PUG templates will
    *                        be activated.
    *
    */
    function render_template($name, $locals = array(), $static = false) {
        $template_found = $this->template_info($name);
        $template_path = $template_found->path;
        $format = $template_found->format;

        if (!isset($template_path)) {
          render_error("Template missing", "<strong>Ouch!!</strong> It seems that <code>$name.pug</code> or <code>$name.php</code> doesn't exist!");
        }

        $tmp_dir = Wordless::theme_temp_path();

        switch ($format) {
            case 'pug':
                require_once('pug/wordless_pug_options.php');

                if ($this->ensure_tmp_dir() ) {
                    // Read the environment from various sources. Note that .env file has precedence
                    if ( getenv('ENVIRONMENT') ) {
                        $env = getenv('ENVIRONMENT');
                    } elseif ( defined('ENVIRONMENT') ) {
                        $env = ENVIRONMENT;
                    } else {
                        $env = 'development';
                    }

                    // Read the option to bypass static cache from various sources. Note that .env file has precedence
                    if ( getenv('BYPASS_STATIC') ) {
                        $bypass_static = getenv('BYPASS_STATIC'); // getenv() returns a string
                    } elseif ( defined('BYPASS_STATIC') ) {
                        $bypass_static = var_export(BYPASS_STATIC, true); // constant could be a boolean so we uniform to a string representation
                    } else {
                        $bypass_static = 'false'; // default value
                    }

                    $env = apply_filters( 'wordless_environment', $env );

                    if ( in_array( $env, array('staging', 'production') ) ) {
                        if (true === $static && 'false' == strtolower($bypass_static)) {
                            $staticPath = $this->static_path($name, $locals);

                            if (file_exists($staticPath)) {
                                include $staticPath;
                            } else {
                                \Pug\Facade::setOptions(WordlessPugOptions::get_options());
                                \Pug\Facade::renderAndWriteFile($template_path, $staticPath, $locals);
                                include $staticPath;
                            }
                        } else {
                            \Pug\Optimizer::call(
                                'displayFile', [$template_path, $locals], WordlessPugOptions::get_options()
                            );
                        }
                    } else {
                        \Pug\Facade::setOptions(WordlessPugOptions::get_options());
                        if (true === $static && 'false' == $bypass_static) {
                            $staticPath = $this->static_path($name, $locals);

                            if (file_exists($staticPath)) {
                                include $staticPath;
                            } else {
                                \Pug\Facade::renderAndWriteFile($template_path, $staticPath, $locals);
                                include $staticPath;
                            }
                        } else {
                            \Pug\Facade::displayFile($template_path, $locals);
                        }
                    }
                } else {
                    render_error("Temp dir not writable", "<strong>Ouch!!</strong> It seems that the <code>$tmp_dir</code> directory is not writable by the server! Go fix it!");
                }

                break;

            case 'php':
                include $template_path;
                break;

            default:
                render_error("Template missing", "<strong>Ouch!!</strong> It seems that <code>$name.pug</code> or <code>$name.php</code> doesn't exist!");
        }
    }

    /**
     * Wraps render_template() function activating the static rendering strategy
     *
     * @param string $name Template path relative to +views+ directory
     * @param array $locals Associative array of variable that will be scoped into the template
     * @return void
     */
    function render_static($name, $locals = array()) {
        $template_found = $this->template_info($name, 'pug');
        if (isset($template_found->path)) {
            $fileInfo = new SplFileInfo($template_found->path);
            $extension = $fileInfo->getExtension();
        }
        if (!isset($extension) || 'pug' !== $extension) {
            render_error("Static rendering only available for PUG templates", "<strong>Ouch!!</strong> It seems you required a <code>render_static</code> for a PHP template, but this render method is supported only for PUG. Use <code>render_partial</code> or <code>render_template</code> instead.");
        }

        render_template($name, $locals, $static = true);
    }

    /**
    * Retrievs contents of partial without printing theme
    * @param string $name The template filenames (those not starting
    *                        with an underscore by convention)
    *
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the partial
    */
    function get_partial_content($name, $locals = array()) {
        ob_start();
        render_partial($name, $locals);
        $partial_content = ob_get_contents();
        ob_end_clean();
        return $partial_content;
    }

    /**
    * Renders a partial: those views followed by an underscore
    *   by convention. Partials are inside theme/views.
    *
    * @param  string $name   The partial filenames (those starting
    *                        with an underscore by convention)
    *
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the partial
    */
    function render_partial($name, $locals = array(), $static = false) {
        $parts = preg_split("/\//", $name);
        if (!preg_match("/^_/", $parts[sizeof($parts)-1])) {
            $parts[sizeof($parts)-1] = "_" . $parts[sizeof($parts)-1];
        }
        render_template(implode("/", $parts), $locals, $static);
    }

    /**
    * Renders a view. Views are rendered based on the routing.
    *   They will show a template and a yielded content based
    *   on the user requested page.
    *
    * @param  string $name   Filename with path relative to theme/views
    * @param  array  $locals An associative array. Keys will be variables'
    *                        names and values will be variable values inside
    *                        the view
    *
    * @deprecated 5.0
    */
    function render_view($name, $layout = 'default', $locals = array()) {
        ob_start();
        global $current_view, $current_locals;

        $current_view = $name;
        $current_locals = $locals;

        render_template("layouts/$layout", $locals);
        ob_flush();
    }

    /**
    * Yield is almost inside every good templates. Based on the
    *   rendering view yield() will insert inside the template the
    *   specific required content (usually called partials)
    *
    * @see render_view()
    * @see render_template()
    *
    *
    */
    function wl_yield() {
        global $current_view, $current_locals;
        render_template($current_view, $current_locals);
    }

    private function ensure_tmp_dir() : bool {
        $tmpDir = Wordless::theme_temp_path();
        $tmp_dir_exists_and_writable = $this->ensure_dir( $tmpDir );

        return apply_filters('wordless_tmp_dir_exists', $tmpDir, $tmp_dir_exists_and_writable );
    }

    private function ensure_dir( $dir ) : bool {

        $dir_exists_and_writable = false;

        if (!file_exists($dir)) {
            mkdir($dir, 0770);
        }

        if (!is_writable($dir)) {
            chmod($dir, 0770);
        }

        if (is_writable($dir)) {
            $dir_exists_and_writable = true;
        }

        return $dir_exists_and_writable;
    }

    // REALLY IMPORTANT NOTE: the cache policy of static generated views is based on the
    // view's name + the SHA1 of serialized $locals. As it stands the best way
    // to introduce business logic in the expiration logic is to pass ad hoc extra variables
    // into the $locals array. For example having
    //     render_template('pages/photos', $locals = [ 'cache_key' => customAlgorithm() ], $static = true)
    // when `customAlgorithm()` will change, it will automatically invalidate the static cache for this
    // template
    private function static_path(string $name, array $locals): string {
        $tmp_dir = Wordless::theme_temp_path();

        return Wordless::join_paths(
            $tmp_dir,
            basename($name) . '.' . sha1(serialize($locals)) . '.html'
        );
    }

}

Wordless::register_helper("RenderHelper");

Usage example

<?php
    add_filter( 'wordless_environment' , function( $pug_environment ) {
        $env = getCustomEnv(); // example a custom define global function
        if ( $env->is_local() ) {
                return $pug_environment;
        }

            return 'production';
    } );

Here the filter code is added in themes/example-theme/config/initializers/hooks.php

If there is different or custom environment name defined this hook can override it, rather than defaulting to development always. For example if environment is called UAT or SIT.

Actions

wordless_component_validation_exception

wordless/helpers/component_helper.php
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
        try {
            $this->setProperties();
            $this->validate();
        } catch (ComponentValidationException $e) {
            if ( 'production' === ENVIRONMENT ) {
                do_action('wordless_component_validation_exception', $e);
                // Would be nice to have an exception collector in your callback, e.g. Sentry:
                //
                // function yourhandler(\Wordless\ComponentValidationException $e) {
                //      if ( function_exists( 'wp_sentry_safe' ) ) {
                //          wp_sentry_safe( function ( \Sentry\State\HubInterface $client ) use ( $e ) {
                //                         $client->captureException( $e );
                //                     } );
                //      }
                // }
                // add_action('wordless_component_validation_exception', 'yourhandler', 10, 1)
            } else {
                render_error('Component validation error', $e->getMessage());
            }

When an object of class Wordless\Component fails its validation, it will throw an exception only if ENVIRONMENT is not production. When in production nothing will happen, in order to be unobtrusive and not breaking the site to your users. The developer will still see specific exception happening.

You can customize the behavior by adding your action as documented in the code.

What we like to do is to add here a notification to our Sentry account (thanks to https://github.com/stayallive/wp-sentry/ plugin)