360 lines
13 KiB
PHP
360 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* core/template.php
|
|
*
|
|
* This class is responsible for all visual output for the application.
|
|
* This class also contains all the functions for parsing data outputs
|
|
* into HTML, including: bbcodes, the data replacement structure, the
|
|
* filters, and other variables used to display application content.
|
|
*
|
|
* @version 3.0
|
|
* @author Joey Kimsey <Joey@thetempusproject.com>
|
|
* @link https://TheTempusProject.com/Core
|
|
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
|
|
*/
|
|
namespace TheTempusProject\Houdini\Classes;
|
|
|
|
use TheTempusProject\Canary\Canary as Debug;
|
|
use TheTempusProject\Hermes\Functions\Route as Routes;
|
|
use TheTempusProject\Bedrock\Functions\Token;
|
|
use TheTempusProject\Bedrock\Functions\Date;
|
|
use TheTempusProject\Bedrock\Classes\Config;
|
|
use TheTempusProject\Bedrock\Classes\CustomException;
|
|
use TheTempusProject\Houdini\Classes\Components;
|
|
use TheTempusProject\Houdini\Classes\Forms;
|
|
use TheTempusProject\Houdini\Classes\Filters;
|
|
use TheTempusProject\Houdini\Classes\Issues;
|
|
use TheTempusProject\Houdini\Classes\Pagination;
|
|
|
|
class Template {
|
|
private static $follow = true;
|
|
private static $index = true;
|
|
private static $headers = [];
|
|
private static $additionalLocations = [];
|
|
private static $templateLocation = null;
|
|
protected static $content = null;
|
|
|
|
/**
|
|
* The constructor automatically sets a few $values and variables
|
|
* the template will need.
|
|
*/
|
|
public function __construct() {
|
|
Debug::group( 'Template Constructor', 1 );
|
|
Components::set( 'SITENAME', 'Houdini Site' );
|
|
Components::set( 'ROOT_URL', Routes::getRoot() );
|
|
Components::set( 'ROOT_ADDRESS', Routes::getAddress() );
|
|
Components::set( 'TITLE', '' );
|
|
Components::set( 'PAGE_DESCRIPTION', '' );
|
|
Components::set( 'TOKEN', Token::generate() );
|
|
Components::set( 'BASE', Routes::getAddress() );
|
|
Debug::gend();
|
|
}
|
|
|
|
private static function loadIssues() {
|
|
Filters::add( 'issues', '#{ISSUES}(.*?){/ISSUES}#is', ( Issues::hasIssues() ? '$1' : '' ), true );
|
|
$notices = implode( '<br>', Issues::getNoticeMessages() );
|
|
if ( !empty( $notices ) ) {
|
|
$notices = '<div class="alert alert-warning" role="alert">' . $notices . '</div>';
|
|
}
|
|
Components::set( 'NOTICE', $notices );
|
|
|
|
$successes = implode( '<br>', Issues::getSuccessMessages() );
|
|
if ( !empty( $successes ) ) {
|
|
$successes = '<div class="alert alert-success" role="alert">' . $successes . '</div>';
|
|
}
|
|
Components::set( 'SUCCESS', $successes );
|
|
|
|
$errors = implode( '<br>', Issues::getErrorMessages() );
|
|
if ( !empty( $errors ) ) {
|
|
$errors = '<div class="alert alert-danger" role="alert">' . $errors . '</div>';
|
|
}
|
|
Components::set( 'ERROR', $errors );
|
|
|
|
$infos = implode( '<br>', Issues::getInfoMessages() );
|
|
if ( !empty( $infos ) ) {
|
|
$infos = '<div class="alert alert-info" role="alert">' . $infos . '</div>';
|
|
}
|
|
Components::set( 'INFO', $infos );
|
|
}
|
|
|
|
public static function addTemplateLocation( $location ) {
|
|
self::$additionalLocations[] = $location;
|
|
return;
|
|
}
|
|
|
|
public static function getLocation() {
|
|
return self::$templateLocation;
|
|
}
|
|
|
|
/**
|
|
* This function sets the '<template>.tpl' to be used for the rendering of the
|
|
* application. It also calls the template include file via the Template::loadTemplate
|
|
* function and stores the keys to the $values array for the template to use later.
|
|
*
|
|
* @param string $name - The name of the template you are trying to use.
|
|
* ('.', and '_' are valid delimiters and the
|
|
* '.tpl' or '.inc.php' are not required.)
|
|
* @todo - Add a check for proper filename.
|
|
*/
|
|
public static function setTemplate( $name ) {
|
|
Debug::info( "Setting template: $name" );
|
|
$name = strtolower( str_replace( '.', '_', $name ) );
|
|
$location = TEMPLATE_DIRECTORY . $name . DIRECTORY_SEPARATOR;
|
|
$docLocation = $location . $name . '.tpl';
|
|
if ( file_exists( $docLocation ) ) {
|
|
self::$templateLocation = $docLocation;
|
|
return self::loadTemplate( $location, $name );
|
|
}
|
|
foreach ( self::$additionalLocations as $key => $location ) {
|
|
$docLocation = $location . $name . '.tpl';
|
|
if ( file_exists( $docLocation ) ) {
|
|
self::$templateLocation = $docLocation;
|
|
return self::loadTemplate( $location, $name );
|
|
}
|
|
}
|
|
new CustomException( 'template', $docLocation );
|
|
}
|
|
|
|
/**
|
|
* Checks for, requires, and instantiates the template include file
|
|
* and constructor for the specified template. Uses the class templateName
|
|
* if none is provided.
|
|
*
|
|
* @param string $name - A custom template name to load the include for.
|
|
* @return array - Returns the values object from the loader file,
|
|
* or an empty array.
|
|
* @todo - Add a check for proper filename.
|
|
*/
|
|
private static function loadTemplate( $path, $name ) {
|
|
Debug::group( 'Template Loader', 1 );
|
|
$fullPath = $path . $name . '.inc.php';
|
|
$className = APP_SPACE . '\\Templates\\' . ucfirst( $name ) . 'Loader';
|
|
if ( !file_exists( $fullPath ) ) {
|
|
new CustomException( 'templateLoader', $fullPath );
|
|
} else {
|
|
Debug::log( 'Requiring template loader: ' . $name );
|
|
require_once $fullPath;
|
|
$loaderNameFull = $className;
|
|
Debug::log( 'Calling loader: ' . $className );
|
|
new $className;
|
|
}
|
|
Debug::gend();
|
|
}
|
|
|
|
/**
|
|
* Sets the current page as noFollow and rebuilds the robots meta tag.
|
|
*
|
|
* @param boolean $status - The desired state for noFollow.
|
|
*/
|
|
public static function noFollow( $status = false ) {
|
|
self::$follow = (bool) $status;
|
|
self::buildRobot();
|
|
}
|
|
|
|
/**
|
|
* Sets the current page as noIndex and rebuilds the robots meta tag.
|
|
*
|
|
* @param boolean $status - The desired state for noIndex.
|
|
*/
|
|
public static function noIndex( $status = false ) {
|
|
self::$index = (bool) $status;
|
|
self::buildRobot();
|
|
}
|
|
|
|
public static function addHeader( $header ) {
|
|
self::$headers[] = $header;
|
|
}
|
|
|
|
public static function removeHeader( $header ) {
|
|
if ( isset( self::$headers[$header] ) ) {
|
|
unset( self::$headers[$header] );
|
|
}
|
|
}
|
|
|
|
public static function buildHeaders() {
|
|
if ( empty( self::$headers ) ) {
|
|
return;
|
|
}
|
|
foreach ( self::$headers as $key => $header) {
|
|
header( $header );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the component for ROBOT.
|
|
*/
|
|
public static function buildRobot() {
|
|
if ( !self::$index && !self::$follow ) {
|
|
Components::set( 'ROBOT', '<meta name="robots" content="noindex,nofollow">' );
|
|
} elseif ( !self::$index ) {
|
|
Components::set( 'ROBOT', '<meta name="robots" content="noindex">' );
|
|
} elseif ( !self::$follow ) {
|
|
Components::set( 'ROBOT', '<meta name="robots" content="nofollow">' );
|
|
} else {
|
|
Components::set( 'ROBOT', '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints the parsed and fully rendered page using the specified template from
|
|
* templateLocation.
|
|
* NOTE: This should be the only echo in the system.
|
|
*/
|
|
public static function render() {
|
|
self::loadIssues();
|
|
Components::set( 'CONTENT', self::$content );
|
|
self::buildRobot();
|
|
self::buildHeaders();
|
|
if ( empty( self::$templateLocation ) ) {
|
|
self::setTemplate( Config::getValue( 'main/template' ) );
|
|
}
|
|
if ( empty( self::$templateLocation ) ) {
|
|
self::setTemplate( Config::getValue( 'main/template' ) );
|
|
return;
|
|
}
|
|
if ( !Debug::status( 'render' ) ) {
|
|
return;
|
|
}
|
|
echo self::parse( file_get_contents( self::$templateLocation ) );
|
|
}
|
|
|
|
/**
|
|
* The loop function for the template engine's {loop}{/loop} tag.
|
|
*
|
|
* @param string $template The string being checked for a loop
|
|
* @param array $data the data being looped through
|
|
* @return string the filtered and completed LOOP
|
|
*/
|
|
public static function buildLoop( $template, $data = null ) {
|
|
$header = null;
|
|
$footer = null;
|
|
$final = null;
|
|
$loopAlternative = null;
|
|
|
|
$loop = '#.*{LOOP}(.*?){/LOOP}.*#is';
|
|
$loopTemplate = preg_replace( $loop, '$1', $template );
|
|
if ( $loopTemplate != $template ) {
|
|
//Separate off the header if it exists.
|
|
$header = trim( preg_replace( '#^(.*)?{LOOP}.*$#is', '$1', $template ) );
|
|
if ( $header === $template ) {
|
|
$header = null;
|
|
}
|
|
|
|
//Separate off the footer if it exists.
|
|
$footer = trim( preg_replace( '#^.*?{/LOOP}(.*)$#is', '$1', $template ) );
|
|
if ( $footer === $template ) {
|
|
$footer = null;
|
|
}
|
|
|
|
if ( !empty( $footer ) ) {
|
|
//Separate off the alternative to the loop if it exists.
|
|
$alt = '#{ALT}(.*?){/ALT}#is';
|
|
$loopAlternative = trim( preg_replace( $alt, '$1', $footer ) );
|
|
if ( $loopAlternative === $footer ) {
|
|
$loopAlternative = null;
|
|
} else {
|
|
$footer = trim( preg_replace( '#^.*?{/ALT}(.*)$#is', '$1', $footer ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !empty( $data ) ) {
|
|
//iterate through the data as instances.
|
|
foreach ( $data as $instance ) {
|
|
$x = 0;
|
|
//reset the template for every iteration of $data.
|
|
$modifiedTemplate = $loopTemplate;
|
|
|
|
if ( !is_object( $instance ) ) {
|
|
$instance = $data;
|
|
$end = 1;
|
|
}
|
|
//loop the template as many times as we have data for.
|
|
foreach ( $instance as $key => $value ) {
|
|
if ( !is_object( $value ) ) {
|
|
$tagPattern = "~{($key)}~i";
|
|
if ( is_array( $value ) || null === $value ) {
|
|
$value = '';
|
|
}
|
|
$modifiedTemplate = preg_replace( $tagPattern, $value, $modifiedTemplate );
|
|
}
|
|
}
|
|
|
|
//since this loop may have a header, and/or footer, we have to define the final output of the loop.
|
|
$final .= $modifiedTemplate;
|
|
|
|
if ( $x === 0 ) {
|
|
$singlePattern = '#{SINGLE}(.*?){/SINGLE}#is';
|
|
//If there is a {SINGLE}{/SINGLE} tag, we will replace it on the first iteration.
|
|
$final = preg_replace( $singlePattern, '$1', $final );
|
|
|
|
//Same practice, but for the entry template.
|
|
$loopTemplate = preg_replace( $singlePattern, '', $loopTemplate );
|
|
$x++;
|
|
}
|
|
|
|
//Since $data is only for a single data set, we break the loop.
|
|
if ( isset( $end ) ) {
|
|
unset( $end );
|
|
$output = $header . $final . $footer;
|
|
break;
|
|
}
|
|
}
|
|
$output = $header . $final . $footer;
|
|
} else {
|
|
if ( !empty( $loopAlternative ) ) {
|
|
$output = $header . $loopAlternative;
|
|
} else {
|
|
$output = $header . $loopTemplate . $footer;
|
|
}
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* This is the main function of the template engine.
|
|
* this function parses the given view and replaces
|
|
* all of the necessary components with their processed
|
|
* counterparts.
|
|
*
|
|
* @param string $template - The html that needs to be parsed.
|
|
* @param array|object $data - An associative array or object that will
|
|
* be used as components for the provided html.
|
|
* @return string - The fully parsed html output.
|
|
*/
|
|
public static function parse( $template, $data = null, $flags = null ) {
|
|
if ( empty( $template ) ) {
|
|
return $template;
|
|
}
|
|
//Check for a {LOOP}{/LOOP} tag.
|
|
$template = self::buildLoop( $template, $data );
|
|
$template = Components::parse( $template );
|
|
if ( strpos( $template, '{OPTION=' ) !== false ) {
|
|
foreach ( Forms::$options as $key => $value ) {
|
|
$template = preg_replace( $key, $value, $template, 1 );
|
|
}
|
|
$template = preg_replace( '#\{OPTION\=(.*?)\}#is', '', $template );
|
|
}
|
|
|
|
//Convert any dates into preferred Date/Time format. User preference will be applied her in the future.
|
|
// @todo - there must be something to migrate this ability from the top to the bottom when a user has a pref
|
|
$dtc = '#{DTC(.*?)}(.*?){/DTC}#is';
|
|
|
|
$template = preg_replace_callback(
|
|
$dtc,
|
|
function ( $data ) {
|
|
if ( empty( $data[2] ) ) {
|
|
return '';
|
|
}
|
|
return Date::formatTimestamp( $data[1], $data[2] );
|
|
},
|
|
$template
|
|
);
|
|
|
|
//Run through our full list of generated filters.
|
|
$template = Filters::apply( $template );
|
|
|
|
return $template;
|
|
}
|
|
}
|