PHP Classes
Icontem

File: QueryTemplates.php


  Search   All class groups All class groups   Latest entries Latest entries   Top 10 charts Top 10 charts   Newsletter Newsletter   Blog Blog   Forums Forums   Help FAQ Help FAQ  
  Login   Register  
Recommend this page to a friend! ReTweet ReTweet Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Tobiasz Cudnik  >  Query Templates  >  QueryTemplates.php  
File: QueryTemplates.php
Role: Class source
Content type: text/plain
Description: QueryTemplates.php
Class: Query Templates
Template engine using load, traverse and modify
 

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);
	}
}

 
  Advertise on this site Advertise on this site   Site map Site map   Statistics Statistics   Site tips Site tips   Privacy policy Privacy policy   Contact Contact  

For more information send a message to :
info at phpclasses dot org.
Copyright (c) Icontem 1999-2009 PHP Classes - PHP Class Scripts
  PHP Book Reviews - Reviews of books and other products