GaryJones/Gamajo-Template-Loader Template, giống woocommerce (ok)

https://github.com/GaryJones/Gamajo-Template-Loader

Ví dụ 1:

C:\xampp\htdocs\wordpress5\wp-content\plugins\pw-sample-template-loader-plugin\pw-sample-template-loader.php

<?php
/*
 Plugin Name: PW Sample Template Loader
 Description: Illustrates how to build a template file loaded into a plugin using the Gamajo Template Loader class
 Author: Pippin Williamson
 Version: 1.0
*/

define( 'PW_SAMPLE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
if( ! class_exists( 'Gamajo_Template_Loader' ) ) {
	require PW_SAMPLE_PLUGIN_DIR . 'class-gamajo-template-loader.php';
}
require PW_SAMPLE_PLUGIN_DIR . 'class-pw-template-loader.php';
function pw_sample_shortcode() {
	$templates = new PW_Template_Loader;
	ob_start();
	$templates->get_template_part( 'content', 'header' );
	$templates->get_template_part( 'content', 'middle' );
	$templates->get_template_part( 'content', 'footer' );
	return ob_get_clean();

}
add_shortcode( 'pw_sample', 'pw_sample_shortcode' );

C:\xampp\htdocs\wordpress5\wp-content\plugins\pw-sample-template-loader-plugin\class-gamajo-template-loader.php

<?php
/**
 * Template Loader for Plugins.
 *
 * @package   Gamajo_Template_Loader
 * @author    Gary Jones
 * @link      http://github.com/GaryJones/Gamajo-Template-Loader
 * @copyright 2013 Gary Jones
 * @license   GPL-2.0+
 */

/**
 * Template loader.
 *
 * Originally based on functions in Easy Digital Downloads (thanks Pippin!).
 *
 * When using in a plugin, create a new class that extends this one and just overrides the properties.
 *
 * @package Gamajo_Template_Loader
 * @author  Gary Jones
 */
class Gamajo_Template_Loader {

	/**
	 * Prefix for filter names.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $filter_prefix = 'your_plugin';

	/**
	 * Directory name where custom templates for this plugin should be found in the theme.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $theme_template_directory = 'your-plugin'; // or 'your-plugin-templates' etc.

	/**
	 * Reference to the root directory path of this plugin.
	 *
	 * Can either be a defined constant, or a relative reference from where the subclass lives.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $plugin_directory = "YOUR_PLUGIN_DIR"; // or plugin_dir_path( dirname( __FILE__ ) ); etc.

	/**
	 * Retrieve a template part.
	 *
	 * @since 1.0.0
	 *
	 * @uses Gamajo_Template_Loader::get_template_possble_parts() Create file names of templates.
	 * @uses Gamajo_Template_Loader::locate_template() Retrieve the name of the highest priority template
	 *     file that exists.
	 *
	 * @param string  $slug
	 * @param string  $name Optional. Default null.
	 * @param bool    $load Optional. Default true.
	 *
	 * @return string
	 */
	public function get_template_part( $slug, $name = null, $load = true ) {
		// Execute code for this part
		do_action( 'get_template_part_' . $slug, $slug, $name );

		// Get files names of templates, for given slug and name.
		$templates = $this->get_template_file_names( $slug, $name );

		// Return the part that is found
		return $this->locate_template( $templates, $load, false );
	}

	/**
	 * Given a slug and optional name, create the file names of templates.
	 *
	 * @since 1.0.0
	 *
	 * @param string  $slug
	 * @param string  $name
	 *
	 * @return array
	 */
	protected function get_template_file_names( $slug, $name ) {
		if ( isset( $name ) ) {
			$templates[] = $slug . '-' . $name . '.php';
		}
		$templates[] = $slug . '.php';

		/**
		 * Allow template choices to be filtered.
		 *
		 * The resulting array should be in the order of most specific first, to least specific last.
		 * e.g. 0 => recipe-instructions.php, 1 => recipe.php
		 *
		 * @since 1.0.0
		 *
		 * @param array $templates Names of template files that should be looked for, for given slug and name.
		 * @param string $slug Template slug.
		 * @param string $name Template name.
		 */
		return apply_filters( $this->filter_prefix . '_get_template_part', $templates, $slug, $name );
	}

	/**
	 * Retrieve the name of the highest priority template file that exists.
	 *
	 * Searches in the STYLESHEETPATH before TEMPLATEPATH so that themes which
	 * inherit from a parent theme can just overload one file. If the template is
	 * not found in either of those, it looks in the theme-compat folder last.
	 *
	 * @since 1.0.0
	 *
	 * @uses Gamajo_Tech_Loader::get_template_paths() Return a list of paths to check for template locations.
	 *
	 * @param string|array $template_names Template file(s) to search for, in order.
	 * @param bool         $load           If true the template file will be loaded if it is found.
	 * @param bool         $require_once   Whether to require_once or require. Default true.
	 *   Has no effect if $load is false.
	 *
	 * @return string The template filename if one is located.
	 */
	protected function locate_template( $template_names, $load = false, $require_once = true ) {
		// No file found yet
		$located = false;

		// Remove empty entries
		$template_names = array_filter( (array) $template_names );
		// Try to find a template file
		foreach ( $template_names as $template_name ) {
			// Trim off any slashes from the template name
			$template_name = ltrim( $template_name, '/' );

			// Try locating this template file by looping through the template paths
			foreach ( $this->get_template_paths() as $template_path ) {
				if ( file_exists( $template_path . $template_name ) ) {
					$located = $template_path . $template_name;
					break;
				}
			}
		}

		if ( $load && $located ) {
			load_template( $located, $require_once );
		}

		return $located;
	}

	/**
	 * Return a list of paths to check for template locations.
	 *
	 * Default is to check in a child theme (if relevant) before a parent theme, so that themes which inherit from a
	 * parent theme can just overload one file. If the template is not found in either of those, it looks in the
	 * theme-compat folder last.
	 *
	 * @since 1.0.0
	 *
	 * @return mixed|void
	 */
	protected function get_template_paths() {
		$theme_directory = trailingslashit( $this->theme_template_directory );

		$file_paths = array(
			10  => trailingslashit( get_template_directory() ) . $theme_directory,
			100 => $this->get_templates_dir()
		);

		// Only add this conditionally, so non-child themes don't redundantly check active theme twice.
		if ( is_child_theme() ) {
			$file_paths[1] = trailingslashit( get_stylesheet_directory() ) . $theme_directory;
		}

		/**
		 * Allow ordered list of template paths to be amended.
		 *
		 * @since 1.0.0
		 *
		 * @param array $var Default is directory in child theme at index 1, parent theme at 10, and plugin at 100.
		 */
		$file_paths = apply_filters( $this->filter_prefix . '_template_paths', $file_paths );

		// sort the file paths based on priority
		ksort( $file_paths, SORT_NUMERIC );

		return array_map( 'trailingslashit', $file_paths );
	}

	/**
	 * Return the path to the templates directory in this plugin.
	 *
	 * May be overridden in subclass.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	protected function get_templates_dir() {
		return $this->plugin_directory . 'templates';
	}

}

C:\xampp\htdocs\wordpress5\wp-content\plugins\pw-sample-template-loader-plugin\class-pw-template-loader.php

<?php

/**
 * Template loader for PW Sample Plugin.
 *
 * Only need to specify class properties here.
 *
 */
class PW_Template_Loader extends Gamajo_Template_Loader {
	
	/**
	 * Prefix for filter names.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $filter_prefix = 'pw';

	/**
	 * Directory name where custom templates for this plugin should be found in the theme.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $theme_template_directory = 'pw-templates';

	/**
	 * Reference to the root directory path of this plugin.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $plugin_directory = PW_SAMPLE_PLUGIN_DIR;

}

C:\xampp\htdocs\wordpress5\wp-content\plugins\pw-sample-template-loader-plugin\templates\content-header.php

<div id="pw-header">
   <p>This is the header area.</p>
</div>

C:\xampp\htdocs\wordpress5\wp-content\plugins\pw-sample-template-loader-plugin\templates\content-middle.php

<div id="pw-content">
	<p>This is the main content area.</p>
</div>

C:\xampp\htdocs\wordpress5\wp-content\plugins\pw-sample-template-loader-plugin\templates\content-footer.php

<div id="pw-footer">
	<p>This is the footer area.</p>
</div>

C:\xampp\htdocs\wordpress5\wp-content\themes\twentytwentyone-child\pw-templates\content-header.php

<div id="pw-header">
	<p>This is the header area theme child.</p>
</div>

Kết quả:

Ví dụ 2.1: Nhúng trực tiếp trong file hd-quiz\index.php khi update vẫn bị xóa 😂 và tiếp sau đây chúng ta sẽ nhúng nó vào child theme :(

C:\xampp\htdocs\wordpress5\wp-content\plugins\hd-quiz\index.php

// Lionel
define( 'PW_SAMPLE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
if( ! class_exists( 'Gamajo_Template_Loader' ) ) {
    require PW_SAMPLE_PLUGIN_DIR . 'class-gamajo-template-loader.php';
}
require PW_SAMPLE_PLUGIN_DIR . 'class-pw-template-loader.php';
function hdq_add_shortcode_edit($atts)
{
    // Attributes
    extract(
        shortcode_atts(
            array(
                'quiz' => '',
            ),
            $atts
        )
    );

    // Code
    ob_start();
    include plugin_dir_path(__FILE__) . './includes/template-edit.php';
    return ob_get_clean();
}
add_shortcode('HDquizs', 'hdq_add_shortcode_edit',30);
// Lionel

C:\xampp\htdocs\wordpress5\wp-content\plugins\hd-quiz\includes\template-edit.php

<?php
function hdq_multiple_choice_text_edit($question_ID, $question_number, $question, $quiz)
{   die($question_ID);
    // require(dirname(__FILE__) . '/templates/default.php');
    $templates = new PW_Template_Loader;
    include $templates->get_template_part( 'default' );
}
function hdq_multiple_choice_image_edit($question_ID, $question_number, $question, $quiz)
{
    // require(dirname(__FILE__) . '/templates/image.php');
    $templates = new PW_Template_Loader;
    include $templates->get_template_part( 'image' );
}
function hdq_text_based_edit($question_ID, $question_number, $question, $quiz)
{
    // require(dirname(__FILE__) . '/templates/text.php');
    $templates = new PW_Template_Loader;
    include $templates->get_template_part( 'text' );
}
function hdq_title_edit($question_ID, $question_number, $question, $quiz)
{
    // require(dirname(__FILE__) . '/templates/title.php');
    $templates = new PW_Template_Loader;
    include $templates->get_template_part( 'title' );
}
function hdq_select_all_apply_text_edit($question_ID, $question_number, $question, $quiz)
{
    // require(dirname(__FILE__) . '/templates/select-all-text.php');
    $templates = new PW_Template_Loader;
    include $templates->get_template_part( 'select-all-text' );
}
// enqueue style and script
wp_enqueue_style(
    'hdq_admin_style',
    plugin_dir_url(__FILE__) . './css/hdq_style.css',
    array(),
    HDQ_PLUGIN_VERSION
);
wp_enqueue_script(
    'hdq_admin_script',
    plugins_url('./js/hdq_script.js?', __FILE__),
    array('jquery'),
    HDQ_PLUGIN_VERSION,
    true
);

$buildQuiz = true;

if (!defined('HDQ_REDIRECT')) {
    define('HDQ_REDIRECT', true);
}

if (!is_singular() && HDQ_REDIRECT) {
    // if we are on a category, search, or home blog page
    // replace quiz with direct link to post or page
    hdq_print_quiz_in_loop();
    $buildQuiz = false;
} else {
    if (function_exists("is_amp_endpoint")) {
        if (is_amp_endpoint()) {
            hdq_print_quiz_in_loop();
            $buildQuiz = false;
        }
    }
}


if ($buildQuiz === true) {
    $quiz_ID = intval($quiz); // quiz ID from shortcode

    // get quiz name
    $quiz_name = get_term($quiz_ID, "quiz");
    if ($quiz_name == null) {
        echo 'This quiz no longer exists';
        return;
    }
    $quiz_name = $quiz_name->name;

    $quiz_settings = get_hdq_quiz($quiz_ID);

    // get question order for query
    $question_order = "menu_order"; // default
    if (
        $quiz_settings["randomize_questions"]["value"][0] === "yes" ||
        $quiz_settings["pool_of_questions"]["value"] > 0
    ) {
        $question_order = "rand";
    }

    $per_page = -1; // show all questions by default
    $paginate = false;
    if ($quiz_settings["wp_paginate"]["value"] > 0) {
        if ($quiz_settings["pool_of_questions"]["value"] > 0) {
            return;
        } else {
            $paginate = true;
            $question_order = "menu_order";
            $per_page = $quiz_settings["wp_paginate"]["value"];
        }
    }

    if ($quiz_settings["pool_of_questions"]["value"] > 0) {
        $per_page = $quiz_settings["pool_of_questions"]["value"];
    }

    $hdq_settings = hdq_get_settings();

    // if we should display ads
    $use_adcode = false;
    $hdq_adcode = hdq_decode(hdq_decode($hdq_settings["hd_qu_adcode"]["value"]));
    if ($hdq_adcode != "" && $hdq_adcode != null) {
        $hdq_adcode = stripcslashes(urldecode($hdq_adcode));
        $use_adcode = true;
    }

    $legacy_scroll = false;
    if (isset($hdq_settings["hd_qu_legacy_scroll"]["value"]) && $hdq_settings["hd_qu_legacy_scroll"]["value"][0] == "yes") {
        $legacy_scroll = true;
    }


    // Get the page or post featured image
    // (try to send to facebook for sharing results)
    $hdq_featured_image = "";
    if (has_post_thumbnail()) {
        $hdq_featured_image = wp_get_attachment_url(get_post_thumbnail_id(get_the_ID()), 'full');
    }

    $hdq_twitter_handle = $hdq_settings["hd_qu_tw"]["value"];
    if ($hdq_twitter_handle == "" || $hdq_twitter_handle == null) {
        $hdq_twitter_handle = "harmonic_design";
    }
	
	$hide_questions = "";
	if(isset($quiz_settings["hide_questions"]["value"][0])){
		$hide_questions = $quiz_settings["hide_questions"]["value"][0];
	}
	
	$finish = "Finish";
	if(!isset($hdq_settings["hd_qu_finish"]) || $hdq_settings["hd_qu_finish"]["value"] !== ""){
		$finish = $hdq_settings["hd_qu_finish"]["value"];
	}
	
	$next = "Next";
	if(!isset($hdq_settings["hd_qu_next"]) || $hdq_settings["hd_qu_next"]["value"] !== ""){
		$next = $hdq_settings["hd_qu_next"]["value"];
	}
		
	$results = "Results";
	if(!isset($hdq_settings["hd_results"]) || $hdq_settings["hd_results"]["value"] !== ""){
		$results = $hdq_settings["hd_results"]["value"];
	}	
	
	$translations = array(
		"finish" => $finish,
		"next" => $next,
		"results" => $results,		
	);

    $jPaginate = false;
    // create object for localized script
    $hdq_local_vars = new \stdClass();
    $hdq_local_vars->hdq_quiz_id = $quiz_ID;
    $hdq_local_vars->hdq_timer = $quiz_settings["quiz_timer"]["value"];
    $hdq_local_vars->hdq_timer_question = $quiz_settings["quiz_timer_question"]["value"][0];
    $hdq_local_vars->hdq_show_results = $quiz_settings["show_results"]["value"][0];
    $hdq_local_vars->hdq_results_correct = $quiz_settings["show_results_correct"]["value"][0];
    $hdq_local_vars->hdq_show_extra_text = $quiz_settings["show_extra_text"]["value"][0];
    $hdq_local_vars->hdq_show_results_now = $quiz_settings["show_results_now"]["value"][0];
    $hdq_local_vars->hdq_stop_answer_reselect = $quiz_settings["stop_answer_reselect"]["value"][0];
    $hdq_local_vars->hdq_pass_percent = $quiz_settings["quiz_pass_percentage"]["value"];
    $hdq_local_vars->hdq_share_results = $quiz_settings["share_results"]["value"][0];
	$hdq_local_vars->hdq_hide_questions = $hide_questions;
    $hdq_local_vars->hdq_legacy_scroll = $legacy_scroll;
    $hdq_local_vars->hdq_quiz_permalink = get_the_permalink();
    $hdq_local_vars->hdq_twitter_handle = $hdq_twitter_handle;
    $hdq_local_vars->hdq_quiz_name = $quiz_name;
    $hdq_local_vars->hdq_ajax = admin_url('admin-ajax.php');
    $hdq_local_vars->hdq_featured_image = $hdq_featured_image;
    $hdq_local_vars->hdq_use_ads = $use_adcode;
    $hdq_local_vars->hdq_submit = array();
    $hdq_local_vars->hdq_init = array();
	$hdq_local_vars->hdq_translations = $translations;
    do_action("hdq_submit", $hdq_local_vars); // add functions to quiz complete
    do_action("hdq_init", $hdq_local_vars); // add functions to quiz init
    $hdq_local_vars = json_encode($hdq_local_vars);
    wp_localize_script('hdq_admin_script', 'hdq_local_vars', array($hdq_local_vars));
?>

    <div class="hdq_quiz_wrapper" id="hdq_<?php echo $quiz_ID; ?>">
        <div class="hdq_before">
            <?php do_action("hdq_before", $quiz_ID); ?>
        </div>

        <?php
        hdq_print_quiz_start($quiz_settings["quiz_timer"]["value"], $use_adcode); ?>
        <div class="hdq_quiz" <?php if ($quiz_settings["quiz_timer"]["value"] > 3 && $use_adcode !== true) {
                                    echo 'style = "display:none;"';
                                } ?>>
            <?php
            if ($quiz_settings["results_position"]["value"] != "below") {
                hdq_get_results($quiz_settings);
            }

            // Query through questions
            wp_reset_postdata();
            wp_reset_query();
            global $post;
            $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

            // WP_Query arguments
            $args = array(
                'post_type' => array('post_type_questionna'),
                'tax_query' => array(
                    array(
                        'taxonomy' => 'quiz',
                        'terms' => $quiz_ID,
                    ),
                ),
                'pagination' => $paginate, // true or false
                'posts_per_page' => $per_page, // also used for the pool of questions
                'paged' => $paged,
                'orderby' => $question_order, // defaults to menu_order
                'order' => 'ASC',
            );

            $query = new WP_Query($args);
            $i = 0; // question counter;

            // figure out the starting question number (for WP Pagination)
            $questionNumber = 0;
            if ($per_page >= 1 && $paged > 1) {
                $questionNumber = ($paged * $per_page) - $per_page + 1;
            }

            // The Loop
            if ($query->have_posts()) {
                while ($query->have_posts()) {
                    $query->the_post();
                    $i++;
                    $question_ID = get_the_ID();
                    $question = get_hdq_question($question_ID);

                    if ($question["paginate"]["value"][0] === "yes") {
                        $jPaginate = true;
                        hdq_print_jPaginate($quiz_ID);
                    }
					
					// used to add custom data attributes to questions					
					$extra = apply_filters('hdq_extra_question_data', array(), $question, $quiz_ID);
					$extra_data = "";
					foreach($extra as $k => $d){
						$extra_data = "data-".$k.'="'.$d.'" ';
					}
					$extra_data = sanitize_text_field($extra_data);
					
                    echo '<div class = "hdq_question" '.$extra_data.' data-type = "' . $question["question_type"]["value"] . '" id = "hdq_question_' . $question_ID . '">';

                    hdq_print_question_featured_image($question);

					do_action("hdq_after_featured_image", $question);
					
					
                    // deal with randomized answer order here,
                    // so that you don't have to in your custom question type functions
                    $ans_cor = hdq_get_question_answers($question["answers"]["value"], $question["selected"]["value"], $quiz_settings["randomize_answers"]["value"][0]);
                    $question["answers"]["value"] = $ans_cor;
                    if ($question["question_type"]["value"] === "multiple_choice_text") {
                        hdq_multiple_choice_text_edit($question_ID, $i, $question, $quiz_settings);
                    } elseif ($question["question_type"]["value"] === "multiple_choice_image") {
                        hdq_multiple_choice_image_edit($question_ID, $i, $question, $quiz_settings);
                    } elseif ($question["question_type"]["value"] === "text_based") {
                        hdq_text_based_edit($question_ID, $i, $question, $quiz_settings);
                    } elseif ($question["question_type"]["value"] === "title") {
                        $i = $i - 1; // don't count this as a question
                        hdq_title($question_ID, $i, $question, $quiz_settings);
                    } elseif ($question["question_type"]["value"] === "select_all_apply_text") {
                        hdq_select_all_apply_text_edit($question_ID, $i, $question, $quiz_settings);
                    } else {
                        // TODO: Allow custom question types to be hookable
                        echo "Question type not found";
                    }
                    hdq_print_question_extra_text($question);
                    echo '</div>';

                    if ($use_adcode) {
                        if ($i % 5 == 0 && $i != 0) {
                            echo '<div class = "hdq_adset_container">';
                            echo $hdq_adcode;
                            echo '</div>';
                        }
                    }
                }
            }

            wp_reset_postdata();


            if ($query->max_num_pages > 1 || $per_page != "-1") {
                if (isset($_GET['currentScore'])) {
                    echo '<input type = "hidden" id = "hdq_current_score" value = "' . intval($_GET['currentScore']) . '"/>';
                }
                if (isset($_GET['totalQuestions'])) {
                    echo '<input type = "hidden" id = "hdq_total_questions" value = "' . intval($_GET['totalQuestions']) . '"/>';
                }

                if ($quiz_settings["pool_of_questions"]["value"] == 0 || $quiz_settings["pool_of_questions"]["value"] == "") {
                    if ($query->max_num_pages != $paged) {
                        hdq_print_next($quiz_ID, $paged);
                    }

                    if ($query->max_num_pages == $paged) {
                        hdq_print_finish($quiz_ID, $jPaginate);
                    }
                } else {
                    hdq_print_finish($quiz_ID, $jPaginate);
                }
            } else {
                hdq_print_finish($quiz_ID, $jPaginate);
            }

            if ($quiz_settings["results_position"]["value"] == "below") {
                hdq_get_results($quiz_settings);
            } ?>
        </div>
        <div class="hdq_after">
            <?php do_action("hdq_after", $quiz_ID); ?>
        </div>
        <div class="hdq_loading_bar"></div>
    </div>
<?php
}
?>

C:\xampp\htdocs\wordpress5\wp-content\plugins\hd-quiz\class-gamajo-template-loader.php

<?php
/**
 * Template Loader for Plugins.
 *
 * @package   Gamajo_Template_Loader
 * @author    Gary Jones
 * @link      http://github.com/GaryJones/Gamajo-Template-Loader
 * @copyright 2013 Gary Jones
 * @license   GPL-2.0+
 */

/**
 * Template loader.
 *
 * Originally based on functions in Easy Digital Downloads (thanks Pippin!).
 *
 * When using in a plugin, create a new class that extends this one and just overrides the properties.
 *
 * @package Gamajo_Template_Loader
 * @author  Gary Jones
 */
class Gamajo_Template_Loader {

	/**
	 * Prefix for filter names.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $filter_prefix = 'your_plugin';

	/**
	 * Directory name where custom templates for this plugin should be found in the theme.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $theme_template_directory = 'your-plugin'; // or 'your-plugin-templates' etc.

	/**
	 * Reference to the root directory path of this plugin.
	 *
	 * Can either be a defined constant, or a relative reference from where the subclass lives.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $plugin_directory = "YOUR_PLUGIN_DIR"; // or plugin_dir_path( dirname( __FILE__ ) ); etc.

	/**
	 * Retrieve a template part.
	 *
	 * @since 1.0.0
	 *
	 * @uses Gamajo_Template_Loader::get_template_possble_parts() Create file names of templates.
	 * @uses Gamajo_Template_Loader::locate_template() Retrieve the name of the highest priority template
	 *     file that exists.
	 *
	 * @param string  $slug
	 * @param string  $name Optional. Default null.
	 * @param bool    $load Optional. Default true.
	 *
	 * @return string
	 */
	public function get_template_part( $slug, $name = null, $load = true ) {
		// Execute code for this part
		do_action( 'get_template_part_' . $slug, $slug, $name );

		// Get files names of templates, for given slug and name.
		$templates = $this->get_template_file_names( $slug, $name );

		// Return the part that is found
		return $this->locate_template( $templates, $load, false );
	}

	/**
	 * Given a slug and optional name, create the file names of templates.
	 *
	 * @since 1.0.0
	 *
	 * @param string  $slug
	 * @param string  $name
	 *
	 * @return array
	 */
	protected function get_template_file_names( $slug, $name ) {
		if ( isset( $name ) ) {
			$templates[] = $slug . '-' . $name . '.php';
		}
		$templates[] = $slug . '.php';

		/**
		 * Allow template choices to be filtered.
		 *
		 * The resulting array should be in the order of most specific first, to least specific last.
		 * e.g. 0 => recipe-instructions.php, 1 => recipe.php
		 *
		 * @since 1.0.0
		 *
		 * @param array $templates Names of template files that should be looked for, for given slug and name.
		 * @param string $slug Template slug.
		 * @param string $name Template name.
		 */
		return apply_filters( $this->filter_prefix . '_get_template_part', $templates, $slug, $name );
	}

	/**
	 * Retrieve the name of the highest priority template file that exists.
	 *
	 * Searches in the STYLESHEETPATH before TEMPLATEPATH so that themes which
	 * inherit from a parent theme can just overload one file. If the template is
	 * not found in either of those, it looks in the theme-compat folder last.
	 *
	 * @since 1.0.0
	 *
	 * @uses Gamajo_Tech_Loader::get_template_paths() Return a list of paths to check for template locations.
	 *
	 * @param string|array $template_names Template file(s) to search for, in order.
	 * @param bool         $load           If true the template file will be loaded if it is found.
	 * @param bool         $require_once   Whether to require_once or require. Default true.
	 *   Has no effect if $load is false.
	 *
	 * @return string The template filename if one is located.
	 */
	protected function locate_template( $template_names, $load = false, $require_once = true ) {
		// No file found yet
		$located = false;

		// Remove empty entries
		$template_names = array_filter( (array) $template_names );
		// Try to find a template file
		foreach ( $template_names as $template_name ) {
			// Trim off any slashes from the template name
			$template_name = ltrim( $template_name, '/' );

			// Try locating this template file by looping through the template paths
			foreach ( $this->get_template_paths() as $template_path ) {
				if ( file_exists( $template_path . $template_name ) ) {
					$located = $template_path . $template_name;
					break;
				}
			}
		}

		if ( $load && $located ) {
			load_template( $located, $require_once );
		}

		return $located;
	}

	/**
	 * Return a list of paths to check for template locations.
	 *
	 * Default is to check in a child theme (if relevant) before a parent theme, so that themes which inherit from a
	 * parent theme can just overload one file. If the template is not found in either of those, it looks in the
	 * theme-compat folder last.
	 *
	 * @since 1.0.0
	 *
	 * @return mixed|void
	 */
	protected function get_template_paths() {
		$theme_directory = trailingslashit( $this->theme_template_directory );

		$file_paths = array(
			10  => trailingslashit( get_template_directory() ) . $theme_directory,
			100 => $this->get_templates_dir()
		);

		// Only add this conditionally, so non-child themes don't redundantly check active theme twice.
		if ( is_child_theme() ) {
			$file_paths[1] = trailingslashit( get_stylesheet_directory() ) . $theme_directory;
		}

		/**
		 * Allow ordered list of template paths to be amended.
		 *
		 * @since 1.0.0
		 *
		 * @param array $var Default is directory in child theme at index 1, parent theme at 10, and plugin at 100.
		 */
		$file_paths = apply_filters( $this->filter_prefix . '_template_paths', $file_paths );

		// sort the file paths based on priority
		ksort( $file_paths, SORT_NUMERIC );

		return array_map( 'trailingslashit', $file_paths );
	}

	/**
	 * Return the path to the templates directory in this plugin.
	 *
	 * May be overridden in subclass.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	protected function get_templates_dir() {
		return $this->plugin_directory . 'templates';
	}

}

C:\xampp\htdocs\wordpress5\wp-content\plugins\hd-quiz\class-pw-template-loader.php

<?php

/**
 * Template loader for PW Sample Plugin.
 *
 * Only need to specify class properties here.
 *
 */
class PW_Template_Loader extends Gamajo_Template_Loader {
	
	/**
	 * Prefix for filter names.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $filter_prefix = 'pw';

	/**
	 * Directory name where custom templates for this plugin should be found in the theme.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $theme_template_directory = 'pw-templates';

	/**
	 * Reference to the root directory path of this plugin.
	 *
	 * @since 1.0.0
	 * @type string
	 */
	protected $plugin_directory = PW_SAMPLE_PLUGIN_DIR;

}

C:\xampp\htdocs\wordpress5\wp-content\themes\twentytwentyone-child\pw-templates\image.php

<?php
    // question type template
    // multiple choice image

    // print question title
    hdq_print_question_title($question_number, $question);
    // print out answers
    $answers = $question["answers"]["value"];
	echo '<div class = "hdq_answers">';	
    echo '<div class = "hdq_question_answers_images ggggggg4">';
    for ($i = 0; $i < @count($answers); $i++) {
        if ($answers[$i]["answer"] != "" && $answers[$i]["answer"] != null) {
            $selected = 0;
            if ($answers[$i]["correct"]) {
                $selected = 1;
            } 
            if($i === 0) {
            ?>
			<div class = "hdq_row hdq_row_image one">
				<label role = "button" aria-labelledby = "hda_label_<?php echo $i . '_' . $question_ID; ?>"  id = "hda_label_<?php echo $i . '_' . $question_ID; ?>" class="hdq_label_answer" data-type = "image" data-id = "hdq_question_<?php echo $question_ID; ?>" for="hdq_option_<?php echo $i . '_' . $question_ID; ?>">
				<?php
                $image = "";
            if ($answers[$i]["image"] != "" && $answers[$i]["image"] != 0) {
                $image = hdq_get_answer_image_url($answers[$i]["image"]);
            }
            if ($image != "" && $image != null) {
                echo '<img src = "'.$image.'" alt = "'.htmlentities($answers[$i]["answer"]).'"/>';
            } ?>
					<div>					
						<div class="hdq-options-check">
							<input type="checkbox" autocomplete="off" data-id = "<?php echo $question_ID; ?>" class="hdq_option hdq_check_input" data-type = "image" value="<?php echo $selected; ?>" name="hdq_option_<?php echo $i . '_' . $question_ID; ?>" id="hdq_option_<?php echo $i . '_' . $question_ID; ?>">
							<span class = "hdq_toggle"><span class = "hdq_aria_label"><?php echo $answers[$i]["answer"]; ?></span></span>
						</div>
					<?php echo $answers[$i]["answer"]; ?>
					</div>
				</label>
			</div>
	<?php

            }else {
                ?>
                <div class = "hdq_row hdq_row_image">
                <label role = "button" aria-labelledby = "hda_label_<?php echo $i . '_' . $question_ID; ?>"  id = "hda_label_<?php echo $i . '_' . $question_ID; ?>" class="hdq_label_answer" data-type = "image" data-id = "hdq_question_<?php echo $question_ID; ?>" for="hdq_option_<?php echo $i . '_' . $question_ID; ?>">
                    <?php
                        $image = "";
                        if ($answers[$i]["image"] != "" && $answers[$i]["image"] != 0) {
                            $image = hdq_get_answer_image_url($answers[$i]["image"]);
                        }
                        if ($image != "" && $image != null) {
                            echo '<img src = "'.$image.'" alt = "'.htmlentities($answers[$i]["answer"]).'"/>';
                        } 
                    ?>
                    <div>                   
                        <div class="hdq-options-check">
                            <input type="checkbox" autocomplete="off" data-id = "<?php echo $question_ID; ?>" class="hdq_option hdq_check_input" data-type = "image" value="<?php echo $selected; ?>" name="hdq_option_<?php echo $i . '_' . $question_ID; ?>" id="hdq_option_<?php echo $i . '_' . $question_ID; ?>">
                            <span class = "hdq_toggle"><span class = "hdq_aria_label"><?php echo $answers[$i]["answer"]; ?></span></span>
                        </div>
                    <?php echo $answers[$i]["answer"]; ?>
                    </div>
                </label>
            </div>
                <?php
            }
        }
    }
    echo '</div>';
	echo '</div>';

Ví dụ 2.2:

C:\xampp\htdocs\wordpress5\wp-content\themes\twentytwentyone-child\functions.php

<?php
// Lionel
define('PW_SAMPLE_PLUGIN_DIR', WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . "hd-quiz". DIRECTORY_SEPARATOR);
if (!class_exists('Gamajo_Template_Loader')) {
  require PW_SAMPLE_PLUGIN_DIR . 'class-gamajo-template-loader.php';
}
require PW_SAMPLE_PLUGIN_DIR . 'class-pw-template-loader.php';
function hdq_add_shortcode_edit($atts) {
  // Attributes
  extract(
    shortcode_atts(
      array(
        'quiz' => '',
      ),
      $atts
    )
  );
  // Code
  ob_start();
  include PW_SAMPLE_PLUGIN_DIR . 'includes/template-edit.php';
  return ob_get_clean();
}
add_shortcode('HDquizs', 'hdq_add_shortcode_edit', 30);
// Lionel
?>

Gamajo Template Loader

A class to copy into your WordPress plugin, to allow loading template parts with fallback through the child theme > parent theme > plugin.

Description

Easy Digital Downloads, WooCommerce, and Events Calendar plugins, amongst others, allow you to add files to your theme to override the default templates that come with the plugin. As a developer, adding this convenience in to your own plugin can be a little tricky.

The get_template_part() function in WordPress was never really designed with plugins in mind, since it relies on locate_template() which only checks child and parent themes. So we can add in a final fallback that uses the templates in the plugin, we have to use a custom locate_template() function, and a custom get_template_part() function. The solution here just wraps them up as a class for convenience.

Installation

This isn't a WordPress plugin on its own, so the usual instructions don't apply. Instead:

Manually install class

  1. Copy class-gamajo-template-loader.php into your plugin. It can be into a file in the plugin root, or better, an includes directory.

or:

Install class via Composer

  1. Tell Composer to install this class as a dependency: composer require gamajo/template-loader

  2. Recommended: Install the Mozart package: composer require coenjacobs/mozart --dev and configure it.

  3. The class is now renamed to use your own prefix, to prevent collisions with other plugins bundling this class.

Implement class

  1. Create a new file, such as class-your-plugin-template-loader.php, in the same directory.

  2. Create a class in that file that extends Gamajo_Template_Loader (or the new prefixed name, if you installed via Composer/Mozart). You can see the Meal Planner Template Loader example class below as a starting point if it helps.

  3. Override the class properties to suit your plugin. You could also override the get_templates_dir() method if it isn't right for you.

  4. You can now instantiate your custom template loader class, and use it to call the get_template_part() method. This could be within a shortcode callback, or something you want theme developers to include in their files.

// Template loader instantiated elsewhere, such as the main plugin file.
$meal_planner_template_loader = new Meal_Planner_Template_Loader;
  • Use it to call the get_template_part() method. This could be within a shortcode callback, or something you want theme developers to include in their files.

    $meal_planner_template_loader->get_template_part( 'recipe' );
  • If you want to pass data to the template, call the set_template_data() method with an array before calling get_template_part(). set_template_data() returns the loader object to allow for method chaining.

    $data = array( 'foo' => 'bar', 'baz' => 'boom' );
    $meal_planner_template_loader
        ->set_template_data( $data );
        ->get_template_part( 'recipe' );

    The value of bar is now available inside the recipe template as $data->foo.

    If you wish to use a different variable name, add a second parameter to set_template_data():

    $data = array( 'foo' => 'bar', 'baz' => 'boom' );
    $meal_planner_template_loader
        ->set_template_data( $data, 'context' )
        ->get_template_part( 'recipe', 'ingredients' );

    The value of bar is now available inside the recipe template as $context->foo.

    This will try to load up wp-content/themes/my-theme/meal-planner/recipe-ingredients.php, or wp-content/themes/my-theme/meal-planner/recipe.php, then fallback to wp-content/plugins/meal-planner/templates/recipe-ingredients.php or wp-content/plugins/meal-planner/templates/recipe.php.

Meal Planner Example Class

<?php
/**
 * Meal Planner
 *
 * @package   Meal_Planner
 * @author    Gary Jones
 * @link      http://example.com/meal-planner
 * @copyright 2013 Gary Jones
 * @license   GPL-2.0+
 */

if ( ! class_exists( 'Gamajo_Template_Loader' ) ) {
  require plugin_dir_path( __FILE__ ) . 'class-gamajo-template-loader.php';
}

/**
 * Template loader for Meal Planner.
 *
 * Only need to specify class properties here.
 *
 * @package Meal_Planner
 * @author  Gary Jones
 */
class Meal_Planner_Template_Loader extends Gamajo_Template_Loader {
  /**
   * Prefix for filter names.
   *
   * @since 1.0.0
   *
   * @var string
   */
  protected $filter_prefix = 'meal_planner';

  /**
   * Directory name where custom templates for this plugin should be found in the theme.
   *
   * @since 1.0.0
   *
   * @var string
   */
  protected $theme_template_directory = 'meal-planner';

  /**
   * Reference to the root directory path of this plugin.
   *
   * Can either be a defined constant, or a relative reference from where the subclass lives.
   *
   * In this case, `MEAL_PLANNER_PLUGIN_DIR` would be defined in the root plugin file as:
   *
   * ~~~
   * define( 'MEAL_PLANNER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
   * ~~~
   *
   * @since 1.0.0
   *
   * @var string
   */
  protected $plugin_directory = MEAL_PLANNER_PLUGIN_DIR;

  /**
   * Directory name where templates are found in this plugin.
   *
   * Can either be a defined constant, or a relative reference from where the subclass lives.
   *
   * e.g. 'templates' or 'includes/templates', etc.
   *
   * @since 1.1.0
   *
   * @var string
   */
  protected $plugin_template_directory = 'templates';
}

Usage Example

The Cue plugin from AudioTheme uses this class. Starting at https://github.com/AudioTheme/cue/tree/develop/includes, it has this class in the vendor directory, then the required subclass of my class in the class-cue-template-loader.php file, which sets a few basic properties. It also has a template in https://github.com/AudioTheme/cue/tree/develop/templates.

If you wanted the playlist to have different markup for your theme, you'd copy templates/playlist.php to wp-content/themes/{your-active-theme}/cue/playlist.php and do whatever changes you wanted. WordPress will look for that file first, before then checking a parent theme location (if your active theme is a child theme), before falling back to the default template that comes with the Cue plugin.

Change Log

See the change log.

License

GPL 2.0 or later.

Contributions

Contributions are welcome - fork, fix and send pull requests against the develop branch please.

Credits

Built by Gary Jones Copyright 2013 Gary Jones

Last updated