<?php
/*-------------------------------------------------------+
| PHPFusion Content Management System
| Copyright (C) PHP Fusion Inc
| https://phpfusion.com/
+--------------------------------------------------------+
| Filename: Template.php
| Author: Core Development Team
+--------------------------------------------------------+
| This program is released as free software under the
| Affero GPL license. You can redistribute it and/or
| modify it under the terms of this license which you
| can read by viewing the included agpl.txt or online
| at www.gnu.org/licenses/agpl.html. Removal of this
| copyright header is strictly prohibited without
| written permission from the original author(s).
+--------------------------------------------------------*/
namespace PHPFusion;
/**
* PHPFusion Template
*
* @package PHPFusion
*/
class Template {
const DEFAULT_ID = 'Default';
private static $instance = NULL;
private $template = '';
private $locale = [];
private $block = [];
private $block_render = [];
private $block_source = [];
private $tag = [];
private static $key = '';
private static $locale_list = [];
private $raw_block = [];
private $raw_tag = [];
private static $block_count = [];
private function __construct() {
}
private function __clone() {
}
/**
* @param string $key
*
* @return static
*/
public static function getInstance($key = self::DEFAULT_ID) {
if (!isset(self::$instance[$key])) {
self::$instance[$key] = new static();
}
self::$key = $key;
return self::$instance[$key];
}
/**
* @param string $key
*/
public static function set_key($key) {
self::$key = $key;
}
/**
* Set all files to be used in a template.
* There is a caveat that file names cannot be same or else it will be overridden
*
* @param array $folder_arr
*/
public function set_file(array $folder_arr = []) {
if (!empty($folder_arr)) {
foreach ($folder_arr as $folder_path) {
if (is_dir($folder_path)) {
$files = makefilelist($folder_path, '.|..|._DS_Store');
$folder = makefilelist($folder_path, '.|..|._DS_Store', TRUE, 'folders');
if (!empty($files)) {
foreach ($files as $filename) {
$this->set_tag($filename, $folder_path.$filename);
}
}
if (!empty($folder)) {
$folderdir = [];
foreach ($folder as $foldername) {
$folderdir[] = $folder_path.$foldername.'/';
}
$this->set_file($folderdir);
}
}
}
}
}
/**
* @return array
*/
public function get_block() {
return $this->block;
}
private static $registered_templates = [];
/**
* @param string $template_file_path
*/
public function register_template($template_file_path) {
self::$registered_templates[self::$key] = $template_file_path;
}
/**
* Define the instance to read a specific HTML file
*
* @param string $template_file_path Template File Source
*/
public function set_template($template_file_path) {
if (isset(self::$registered_templates[self::$key])) {
$template_file_path = self::$registered_templates[self::$key];
}
ob_start();
@include $template_file_path;
$this->template = ob_get_clean();
}
/**
* Defines the instance to read a specific text
*
* @param string $text
*/
public function set_text($text) {
$this->template = $text;
}
/**
* Defines the instance to replace locales
*
* @param array $locales
*/
public function set_locale(array $locales = []) {
$this->locale = $locales;
}
/**
* Sets to repeat on a subitem defined in the HTML markup - {block_id.{<subitem_html>}}
* This function adds a subitem for every set_block used
*
* @param string $block_id The name of the unique block id
* @param array $value The replacements set
*/
public function set_block($block_id, array $value = []) {
$filtered_value = [];
foreach ($value as $tag => $val) {
$filtered_value['{%'.$tag.'%}'] = $val;
}
$this->block[$block_id][] = $filtered_value;
$this->raw_block[$block_id][] = $value;
}
/**
* Fetches an entire block of tags
*
* Every time this function is used with a block_id specified, it will traverse to the second count to mimic the set_block.
*
* @param int $block_id
* @param string $html_tag
*
* @return array|mixed|null
*/
public function fetch_block($block_id = NULL, $html_tag = NULL) {
$block_count = '';
if ($block_id) {
self::$block_count[$block_id] = 0;
$block_count = self::$block_count[$block_id];
self::$block_count[$block_id]++;
}
if ($block_id != NULL) {
// block id is not null.
if (isset($this->raw_block[$block_id][$block_count])) {
if ($html_tag != NULL) {
if (isset($this->raw_block[$block_id][$block_count][$html_tag])) {
return $this->raw_block[$block_id][$block_count][$html_tag];
}
} else {
if (isset($this->raw_block[$block_id][$block_count])) {
return $this->raw_block[$block_id][$block_count];
}
}
return NULL;
}
} else {
return $this->raw_block;
}
return NULL;
}
/**
* Sets a tag to string conversion
*
* @param string $html_tag {%tag%} in html file is 'tag'
* @param string $value the value of the string
*/
public function set_tag($html_tag, $value) {
$this->tag['{%'.$html_tag.'%}'] = $value;
$this->raw_tag[$html_tag] = $value;
}
/**
* Fetches a tag
*
* @param string $html_tag
*
* @return array|mixed|null
*/
public function fetch_tag($html_tag = NULL) {
return $html_tag === NULL ? $this->raw_tag : (isset($this->raw_tag[$html_tag]) ? $this->raw_tag[$html_tag] : NULL);
}
/**
* Locale Replacement with Template Macro
* Pattern - {[locale_keys]}
* Recursively parse array into an array
*
* @param array $array
*
* @return array
*/
private function assign_template_locales($array) {
if (!empty($array)) {
foreach ($array as $key => $value) {
if (is_array($value)) {
self::$locale_list = array_merge(self::$locale_list, $this->assign_template_locales($value));
} else {
self::$locale_list["{[$key]}"] = $value ? nl2br($value) : "";
}
}
}
return self::$locale_list;
}
/**
* Renders the output
* Any unused blocks will not be parsed and deleted. This is useful to remove a div wrapper if condition fails.
*
* @return string The final HTML markup
*/
public function get_output() {
$this->template = trim($this->template);
// Locale replacements
if (!empty($this->locale)) {
if ($this->assign_template_locales($this->locale)) {
$this->template = strtr($this->template, self::$locale_list);
}
}
$block_pattern = '/\{([0-9a-zA-Z_]+)\.\{(.*?)\}\}/s'; // group 1 is id, group 2 is the child template
preg_match_all($block_pattern, $this->template, $cache, PREG_SET_ORDER);
// set to ID and Template
if (!empty($cache)) {
foreach ($cache as $preg_results) {
if (!empty($preg_results[1]) && !empty($preg_results[2])) {
$this->block_render[$preg_results[1]] = $preg_results[2];
$this->block_source[$preg_results[1]] = $preg_results[0];
}
}
}
if (!empty($this->block)) {
foreach ($this->block as $block_id => $blocks) {
$block_results = '';
if (isset($this->block_render[$block_id]) && isset($this->block_source[$block_id])) {
$block_tpl = $this->block_render[$block_id]; // found the corresponding block id.
foreach ($blocks as $block_values) {
$block_results .= strtr($block_tpl, $block_values)."\n";
}
// Mutate template by calculated block output
$this->template = strtr($this->template, [$this->block_source[$block_id] => $block_results]);
unset($this->block_source[$block_id]); // erase the block out of the array for cleaning up
}
}
}
// Look for any remaining matches, and clean up the template if this block id is unused or is blanked value.
if (!empty($this->block_source)) {
foreach ($this->block_source as $block_id => $block_results) {
$this->template = strtr($this->template, [$this->block_source[$block_id] => '']);
unset($this->block_source[$block_id]);
}
}
// Direct replacement that doesn't require any conditional checks. This is 1 to 1 string to tag replacements.
if (!empty($this->tag)) {
foreach ($this->tag as $tag => $value) {
if (is_string($tag) && (!is_array($value))) {
$this->template = strtr($this->template, [$tag => $value]);
}
}
}
unset($this->tag);
unset($this->block);
return trim($this->template);
}
}