Login   Register  
PHP Classes
elePHPant
Icontem

File: QueryTemplates.php

Recommend this page to a friend!
Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of  >  Query Templates  >  QueryTemplates.php  >  Download  
File: QueryTemplates.php
Role: Class source
Content type: text/plain
Description: QueryTemplates.php
Class: Query Templates
Template engine using load, traverse and modify
Author: By
Last change:
Date: 6 years ago
Size: 14,347 bytes
 

Contents

Class file image Download
<?php
/**
 * QueryTemplates is a PHP templating engine using pure HTML files in popular
 * web 2.0 pattern load-traverse-modify using jQuery-like chainable API.
 *
 * @version 1.0 Beta1
 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 * @link http://code.google.com/p/querytemplates/
 */

require_once(dirname(realpath(__FILE__))."/QueryTemplatesTemplate.php");
/**
 * Static methods namespace class.
 *
 * @static
 * @package QueryTemplates
 * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 * @link http://code.google.com/p/querytemplates/
 */
abstract class QueryTemplates {
	/**
	 * Turns on/off debug. Includes phpQuery's one.
	 * @param bool
	 * @see phpQuery::$debug
	 */
	public static $debug = false;
	/**
	 * Path to direcotry where cached templates will be stored.
	 * @var string
	 * @see self::$cacheTimeout
	 */
	public static $targetsPath = './cache/';
	/**
	 * Checks if source template file exists and if it has been modified.
	 * Can be turned off for performance reasons on production enviroment.
	 * @var bool
	 */
	public static $monitorTemplateModification = true;
	/**
	 * Checks if source code file has been modified.
	 * Use it only on developement enviroment as it runs debug_backtrace() every time.
	 * @var bool
	 * @TODO
	 */
	public static $monitorCodeModification = false;
	/**
	 * Path prepended to templates names.
	 * @var string
	 * TODO refactor to $sourcePath
	 */
	public static $sourcesPath = '';
	/**
	 * Timeout for cached templates in minutes.
	 * Generally uneeded because of checking templates modification time.
	 * False (default) means no timeout.
	 * -1 turns off cache.
	 * @var mixed
	 * @see self::$cache
	 * @see self::$targetsPath
	 */
	public static $cacheTimeout = null;
	/**
	 * Use HTML Tidy for pretty output (if avaible).
	 * Tidy is included with every PHP5 installation, but it has be manually activated in php.ini.
	 * Value == 1 means tidy only partial templates (with no <html> or <body> tags)
	 * 	This is supposed to work quite well.
	 * Value == 2 means tidy all, including files with <head>, and this can fail,
	 * 	if You have <?php tags inside <head> with nested <script> tags.
	 * @todo Strip out HTML/BODY if it's soudlnt be in the template (something what show-body-only should do).
	 * @todo tidy removes empty tags (shouldnt !!!!)
	 * @TODO create formatter API
	 * @var int
	 */
	public static $useTidy = false;
	/**
	 * Use tabs insted of spaces for tidy intendation.
	 * @var bool
	 * @TODO create formatter API
	 */
	public static $tidyIntendWithTabs = true;
	/**
	 * Config fo Tidy.
	 * @link http://tidy.sourceforge.net/docs/quickref.html
	 * @var array
	 * @TODO create formatter API
	 */
	public static $tidyConfig = array(
		'indent' => true,
		'indent-spaces' => 4,
		'wrap' => false,
		'show-body-only' => 'yes',
		'merge-divs' => false,
		'new-inline-tags' => 'php',
		'tab-size' => 4,
		'output-bom' => false,
		// TODO support other charset
		'input-encoding' => 'utf8',
		'output-encoding' => 'utf8',
		'char-encoding' => 'utf8',
	);
	/**
	 * PEAR XML_Beutifuler
	 * XML only. No PHP code support (at least working one). 
	 * @TODO create formatter API
	 * @var unknown_type
	 */
	public static $formatterEnable = false;
	/**
	 * @TODO create formatter API
	 * @var unknown_type
	 */
	public static $formatterOptions = null;
	protected static $formatterInstance = null;
	/**
	 * Fixes paths to CSS, JS and image files.
	 * @todo Dont update external links.
	 * @var string
	 */
	public static $fixWebroot = '';
	/**
	 * Returns cached template's path for inclusion.
	 * Returns false if cache isn't up-to-date.
	 *
	 * @return string|false
	 */
	public static function loadTemplate($templateName, $targetsPath = null) {
		list($cachePath, $cacheDeps) = self::getCachePaths($templateName, $targetsPath);
		$useCache = false;
		if (file_exists($cachePath) && self::$cacheTimeout >= 0) {
			$useCache = true;
			// check dependencies
			if (file_exists($cacheDeps)) {
				foreach(file($cacheDeps) as $line) {
					list($file, $time) = explode("\t", $line);
					if (! file_exists($file)) {
						$useCache = false;
						continue;
					}
					// check if template source have been modified
					if (filemtime($file) > $time ) {
						$useCache = false;
						continue;
					}
					// check timeout (stiff refresh)
					if (self::$cacheTimeout && time()-$time > self::$cacheTimeout ) {
						$useCache = false;
						continue;
			//		debug('cacheTimeout');
					}
				}
			}
			if ($useCache )
				return $cachePath;
		}
		return false;
	}
	/**
	 * Creates new template and returns it's path.
	 * If You want to use self::$fixWebRoot You have to pass phpQuery object directly.
	 * CAUTION: this method will call unload() on passed phpQuery object, so You cant use it futher. Use $unloadDocument to avoid this.
	 *
	 * @param phpQuery			$_				HTML from object will be fetched by htmlOuter(), so take care of proper stack.
	 * @param string				$templatePath
	 * @param string				$templateName
	 * @param array					$dependencies
	 * @param string				$targetsPath
	 * @param bool					$unloadDocument
	 *
	 * @return string|false
	 */
	public static function saveTemplate($pq, $dependencies = array(), 
		$templateName = null, $vars = null, $targetsPath = null, $unloadDocument = true ) {
		// for performance cache stuff is checked only when writing
		if (! self::validateCacheSettings() )
			return false;
		if (! self::$monitorTemplateModification)
			$dependencies = array();
		if (! $templateName )
			$templateName = md5(microtime());
		$html = self::postFilters($pq);
		// needed to avoid conflicts
		if ($unloadDocument)
			$pq->unloadDocument();
		list($cachePath, $cacheDeps) = self::getCachePaths($templateName, $targetsPath);
		$varsPHP = "";
		if ($vars) {
			$varsPHP = "<?php\n";
			foreach($vars as $var => $val) {
				$varsPHP .= "\$$var = ".var_export($val, true).";\n";
			}
			$varsPHP .= "?>";
		}
		file_put_contents(
		 	$cachePath,
		 	$varsPHP.$html
		);
		$dependencies = array_map(
			array('QueryTemplates', 'mapDepends'),
			$dependencies
		);
		if (self::$monitorCodeModification)
			$dependencies[] = self::mapDepends(self::srcFilePath());
		file_put_contents(
		 	$cacheDeps,
		 	implode("\n", $dependencies)
		);
		return $cachePath;
	}
	/**
	 * Fetches included templates content into parsed one.
	 *
	 * @param	phpQuery	$_
	 * @return	array			Array of dependencies.
	 * @TODO refresh
	 */
	public static function parseIncludes($_) {
//		$rootDir = dirname(self::$sourcesPath.$templatePath).'/';
		$rootDir = self::$sourcesPath;
		// collect included templates name
		$dependencies = array();
		// FIXME change codetype to type (attribute)
		$selector = 'object[type=text/template]';
		foreach( $_->find($selector) as $include ) {
			$include = pq($include, $_->getDocumentID());
			$includePath = $rootDir.$include->attr('data');
			if (strpos($includePath, '{') !== false )
				continue;
			if (file_exists($includePath) ) {
				$_nested = phpQuery::newDocumentFile($includePath);
				$dependencies[] = $includePath;
				$dependencies = array_merge(
					$dependencies,
					self::parseIncludes( $_nested )
				);
				$include
					->after($_nested)
					->remove();
			} else
				throw new Exception("File '{$includePath}' doesn't exists, couldn't include template");
		}
		// remove unincludable includes (with {n} args)
		self::removeIncludes($_);
		// dont loose main template
		phpQuery::selectDocument($_);
		return $dependencies;
	}
	/**
	 * Removes included templates from parsed one.
	 *
	 * @param	phpQuery	$_
	 * @TODO refresh
	 * @return	phpQuery
	 */
	public static function removeIncludes($_) {
		$_->find('object[type=text/template]')
			->remove();
	}
	/**
	 * Clears cache folder. Can be limited to files containing $search in names.
	 * Returns number of deleted files.
	 *
	 * @param	string	$search	Optional. Search pattern, accepts wildcard. @see http://php.net/glob
	 * @return	int
	 */
	public function clearCache($search = '*') {
		$i = 0;
		foreach( glob(self::$targetsPath.$search.'.code.php') as $file ) {
			unlink($file);
			// TODO get cache names from self::getCachePaths() and delete other files
			$i++;
		}
		return $i;
	}
	protected static function mapDepends($file) {
		return $file."\t".filemtime($file);
	}
	protected static function srcFilePath() {
		foreach(array_slice(debug_backtrace(), 2) as $r) {
			$phrase = 'QueryTemplates';
			$filename = substr($r['file'], strrpos($r['file'], '/')+1);
			if (substr($filename, 0, strlen($phrase)) != $phrase)
				return $r['file'];
		}
	}
	protected static function normalizeVarName($string) {
		return preg_replace('@[^\\w$]@i', '_', $string);
	}
	protected static function validateCacheSettings() {
		 if (! self::$targetsPath) {
		 	throw new Exception('QueryTemplates::$targetsPath not set');
		 }
		 if (! file_exists(self::$targetsPath)) {
		 	throw new Exception('Directory QueryTemplates::$targetsPath doesn\'t exist');
		 }
		 if (! is_writable(self::$targetsPath)) {
			 if (! @chmod(self::$targetsPath, 0666)) {
			 	throw new Exception('Directory QueryTemplates::$targetsPath isn\'t writtable');
			 }
		 }
		 $s = DIRECTORY_SEPARATOR;
		 self::$targetsPath = rtrim(self::$targetsPath, $s).$s;
		 return true;
	}
	/**
	 * Enter description here...
	 *
	 * @param unknown_type $templateName
	 * @param unknown_type $targetsPath
	 * @return unknown
	 * @TODO refactor to getTargetPaths
	 */
	protected static function getCachePaths( $templateName, $targetsPath = null ) {
		$replace = array('/', '\\');
		if (! $targetsPath )
			$targetsPath = self::$targetsPath;
		$clearCacheName = self::cleanCacheName($templateName);
		return array(
			$targetsPath.$clearCacheName.'.code.php',
//			$targetsPath.$clearCacheName.'.time.php',
			$targetsPath.$clearCacheName.'.deps.php',
//			$targetsPath.$clearCacheName.'.src_time.php',
		);
	}
	protected static function cleanCacheName( $name ) {
		return str_replace(array('/', '\\'), '_', $name);
	}
	/**
	 * Enter description here...
	 *
	 * @param phpQuery $_
	 * @return string
	 */
	protected static function postFilters($pq) {
		$partialDoc = $pq->documentFragment();
		if (self::$fixWebroot)
			$pq->plugin('Scripts')->script('fix_webroot', self::$fixWebroot);
//		$html = $dom->php();
		$markup = $pq->markupOuter();
		/* <php>...</php> to <?php...?> */
		/*
		$html = preg_replace_callback(
			'@<php>\s*<!--(.*?)-->\s*</php>@s',
			create_function('$m',
				'return "<?php\n ".htmlspecialchars_decode($m[1])." \n?>";'
			),
			$dom->htmlOuter()
		);
		*/
		// tidy for pretty output
		if (self::$useTidy && function_exists('tidy_parse_string')) {
			// TODO use output-html	output-xml output-xhtml
			// FIXME formatting attr with PHP code (input[value]) changes PHP tags (to normal)
			if ($partialDoc) {
				// if we doesnt want whole doc, but only a part, get <body> content
//				$config = array_merge(
//					self::$tidyConfig,
//					array('show-body-only' => true)
//				);
				$tidy = tidy_parse_string($markup, self::$tidyConfig);
//				$html = tidy_get_output($tidy);
				$markup = '';
				$body = tidy_get_body($tidy);
				if ($body->child)
					foreach($body->child as $node)
						// get outer html
						$markup .= $node->value;
			} else if (self::$useTidy === 2) {
				// adding <php> as new block element destroys tags inside <head>,
				// so we need to mask them for a while
				// this is DIRTY hack and You cant rely on it
			/*	$html = preg_replace("@<\\?php(.+?)(?:\\?>)@", "<script type='text/php'>\\1</script>", $html);*/
				$tidy = tidy_parse_string($markup, self::$tidyConfig);
				$markup = tidy_get_output($tidy);
				// and now backwards...
			/*	$html = preg_replace("@<script type='text/php'>(.+?)(?:</script>)@", "<?php\\1?>", $html);*/
			}
			if (self::$tidyIntendWithTabs) {
				$markup = preg_replace_callback("@(?<=\\n)(\\s+)(?=.)@",
					array('QueryTemplates', 'tidySpacesToTabs'),
					$markup
				);
			}
		}
		if (self::$formatterEnable) {
			// XXX doent support <?php tags inside attribute
			if (self::$formatterInstance)
				$fmt = self::$formatterInstance;
			else {
				require_once "XML/Beautifier.php";
				$fmt = new XML_Beautifier();
			}
			var_dump($markup);
			$markup = $fmt->formatString($markup);
			if (PEAR::isError($result)) {
				self::debug($result->getMessage());
			}
		}
		// it's important to convert markup to PHP AFTER using Tidy
		$markup = phpQuery::markupToPHP($markup);
		// FIXME just a quick fix, breaks nodes structure
		$markup = str_replace('?><?php', '', $markup);
		// safe tags
		/*$html = str_replace(
			array('<?php <!--', '--> ?>'),
			array('<?php', '?>'),
			$html
		);*/
		// fix PHP tags inside HTML attr
		// separate regex needed for ' and " delimiters (make it in one if You can...)
		// attrs without delimiters are NOT supported (but can be)
		$regexes = array(/*
			'@(<[^>]+?\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C|<)\\?(?:php)?(.*?)(?:\\?&gt;|\\?%3E|\\?>)([^\']*)\'@',
			'@(<[^>]+?\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C|<)\\?(?:php)?(.*?)(?:\\?&gt;|\\?%3E|\\?>)([^"]*)"@',
		*/);
		/*
		foreach($regexes as $regex)
			$html = preg_replace_callback(
				$regex,
	//			'@(<[^>]+?\\w\\s*=\\s*)([\'"])?&lt;\\?php(.*?)(\\?&gt;)\\2@',
	//			'@(<[^>]+?\\w\\s*=\\s*)([\'"])?(.*?)&lt;\\?php(.*?)(\\?&gt;)(.*?)\\2@',
				create_function('$m',
					'return $m[1].$m[2].$m[3]."<?php"
						.str_replace(array("%20", "%3E", "%09", "&#10;", "&#9;"), array(" ", ">", "	", " ", " "), htmlspecialchars_decode($m[4]))
						."?>".$m[5].$m[2];'
				),
				$markup
			);
		*/
		return $markup;
	}
	public static function tidySpacesToTabs($matches) {
		$spacesPerTab = self::$tidyConfig['indent-spaces']
			? self::$tidyConfig['indent-spaces']
			: 4;
		return str_repeat("\t", strlen($matches[1])/$spacesPerTab);
	}
}