Location: PHPKode > scripts > PAX > pax/core/classes/Tags.class.php
<?php


/**
 * Page DocBlock definition
 * @package org.zadara.marius.pax
 */

/**
 * Tags definition file.
 * This class will be responsbile with loading the tags from the definition file,
 * also for filtering (tags, tag's content).
 * 
 * @author Marius Zadara <hide@address.com>
 * @category Classes
 * @copyright (C) 2008-2009 Marius Zadara
 * @license Free for non-comercial use
 * @package org.zadara.marius.pax
 * @final 
 * @see File
 * @see ITags
 * @version 6.0
 * @since 5.0
 */
final class Tags extends File implements ITags  
{
	/**
	 * The tags definition file content.
	 * 
	 * @access private
	 * @var string
	 */
	private $tagsFileContent;
	
	
	/**
	 * The array with all the tags from the definition file.
	 * It is used to keep track of a tag and not search it again in the file
	 *
	 * @access private
	 * @var array
	 */
	private $tags;	
	
	/**
	 * Class constructor.
	 * 
	 * @access public
	 */
	public function __construct()
	{
		// call the parent constructor
		parent::__construct();
		
		// init the tags file content
		$this->tagsFileContent = null;
		
		// init the tags array
		$this->tags = null;
	}

	/**
	 * Method to check if a tag is allowed.
	 *
	 * @access public
	 * @param string <b>$rootTag</b> The root tag from the definition file
	 * @param string <b>$tag</b> The searched tag
	 * @return boolean
	 */
	public function isAllowedTag($rootTag, $tag)
	{
		// if the tag has been searched before,
		// return the previous result
		if (isset($this->tags[$tag]))
			return $this->tags[$tag];

		// check the tags file content
		// try to load the tag definition file for the first tme
		if (!is_object($this->tagsFileContent))
		{			
			$this->tagsFileContent = @simplexml_load_file($this->path);
			
			// in case of failure, 
			// throw exception
			if ($this->tagsFileContent === false)
				throw new PAXException(sprintf(Messages::$MSG_033, $this->path), 33);				
		}
	
		// search the tag using an XPath expression
		$result = @$this->tagsFileContent->xPath(sprintf("/%s/%s", $rootTag, $tag));
		
		// init the tags array the first time		
		if (is_null($this->tags))
			$this->tags = array();
		
		// set the acceptance for this tag
		// if the XPath expression failed, is the same as the tag is not accepted
		$this->tags[$tag] = is_array($result) ? (sizeof($result) > 0) : false;

		// return the result
		return $this->tags[$tag];
	}
	
	
	/**
	 * Method to filter the tags according to the definition file.
	 * Throws exception if error encountered
	 *
	 * @access public
	 * @static 
	 * @param config <b>$config</b> The PAX config object
	 * @param config <b>$elements</b> The PAX elements object
	 * @param config <b>$directories</b> The PAX directories object
	 * @param config <b>$filenames</b> The PAX filenames object
	 * @param array <b>$nodes</b> The array with the current nodes 
	 * @param array <b>$namespaces</b> The array with the current namespaces
	 * @param string <b>$paxNsSeparator</b> The separator used in tag namespace definition
	 * @return void
	 */
	public static function filterTags(&$config, &$elements, $directories, $filenames, &$nodes, &$namespaces, $paxNsSeparator)
	{	
		// catch any exceptions	
		try 
		{
			// get the filter tags flag from the config object
			// set the default value to true
			$filterTags = $config->get("filterTags", true);
			
			// get the attribute name 
			// using this attribute, this filtering can be overwritten from the source itself
			$filterTagsAttributeName = $config->get("filterTagsAttributeName", "filterTags");
									
			// get the final status of the filtering 
			// either from the config or from the definition
			if (isset($nodes[0]['attributes'][$filterTagsAttributeName]))
			{
				// check to see if the attribute value is in the 'true' dictionary			
				$filterTags = Model::isInList($nodes[0]['attributes'][$filterTagsAttributeName], $elements->get("trueTexts")); 
			}
						
			
			// continue with the filtering
			// only if the status is true
			if ($filterTags)
			{		
				$allowedTags = array();
				$allowNoTags = $config->get("allowNoTags", false);
				
				// allow the source without any tag definition?
				if (!$allowNoTags)
				{
					$tags = new Tags();
					
					// get the tags path from the configuration
					$tagsPath = $directories->get("definitions", "definitions");
					$tagsPath .= $filenames->get("tags", "tags.xml");
	
					$tags->setPath($tagsPath);
										
					// validate the path
					// if any error, throw exception
					if (!$tags->exists())
						throw new PAXException(sprintf(Messages::$MSG_030, $tagsPath), 30);

					if (!$tags->isOrdinary())
						throw new PAXException(sprintf(Messages::$MSG_031, $tagsPath), 31);
						
					if (!$tags->isReadable())
						throw new PAXException(sprintf(Messages::$MSG_031, $tagsPath), 32);
				}
				
				// get the tags definition file root
				$tagsRoot = $elements->get("tagsRoot", "tags");

				// create a file object
				// this object will be used to check the node definition file
				$file = new File();				
				
				
				// parse each node from the list
				foreach ($nodes as $index => $nodeData)
				{
				    // get the namespace and tag name based on the node data
				    $names = self::extractNamesFromTag($nodeData['tag'], ":");
				    
				    // if failed ...
				    if ($names === false)
				    {
				        // mark the node as invalid and continue
				        $nodes[$index] = false;
				        continue;
				    }
				    
				    // namespace found?
				    if ($names['namespace'] !== false)
				    {
				        // declared?
    					if (!isset($namespaces[$names['namespace']]))
    						throw new PAXException(sprintf(Messages::$MSG_023, $names['namespace']), 23);

    					// update the directory using the namespace
    					$namespaceDirectory = $namespaces[$names['namespace']];						
    					$namespaceClass = $names['tagName'];
    					
						// get the file format from using the namespace
						$namespaceFileFormat = $filenames->get("namespaceClassFileFormat", "[CLASS].class.php");						
						$namespaceFile = str_ireplace("[CLASS]", $namespaceClass, $namespaceFileFormat);
    					
						// update the namespace file path
						$namespaceFile = $namespaceDirectory . $namespaceFile;

						// update the file path
						$file->setPath($namespaceFile);
						
						// check the file
						// in case of error, throw exception
						if (!$file->exists())
							throw new PAXException(sprintf(Messages::$MSG_024, $namespaceFile), 24);
						
						if (!$file->isOrdinary())
							throw new PAXException(sprintf(Messages::$MSG_025, $namespaceFile), 25);
							
						if (!$file->isReadable())
							throw new PAXException(sprintf(Messages::$MSG_026, $namespaceFile), 26);

						// load the file
						require_once $namespaceFile;
						
						// update the tag name 
						// so we eliminate the namespace (is not needed anymore, class previously loaded)
						$nodes[$index]['tag'] = $names['tagName'];
						
						unset($usedNamespace, $namespaceDirectory, $namespaceClass, $namespaceFileFormat);
				    }    		
				    else 
				    {
						// do the same validations for the non-namespace node
												
						$modelClassFileFormat = $filenames->get("modelClassFileFormat", "[CLASS].class.php");
						$modelClassFile = str_ireplace("[CLASS]", $nodeData['tag'], $modelClassFileFormat);						
						$modelClassFile = $directories->get("implementations") . $modelClassFile;

						// set the path of the file
						$file->setPath($modelClassFile);
						
						// check the file
						if (!$file->exists())
							throw new PAXException(sprintf(Messages::$MSG_027, $modelClassFile), 27);
						
						if (!$file->isOrdinary())
							throw new PAXException(sprintf(Messages::$MSG_028, $modelClassFile), 28);
							
						if (!$file->isReadable())
							throw new PAXException(sprintf(Messages::$MSG_029, $modelClassFile), 29);
						
						// load the file
						require_once $modelClassFile;	
						
						unset($modelClassFileFormat, $modelClassFile);
												
						if (!$tags->isAllowedTag($tagsRoot, $nodeData['tag']))
							$nodes[$index] = false;
				    }
				}
			}
		}
		catch (PAXException $pe)
		{
			// in case of exception, send it further
			throw $pe;
		}	
		catch (Exception $e)
		{
			// in case of exception, send it further
			throw $e;
		}
	}
	
	/**
	 * Method to filter the tag content.
	 * Throws exception in case of error.
	 *
	 * @access public
	 * @static 
	 * @param config <b>$config</b> The PAX config object
	 * @param config <b>$elements</b> The PAX elements object
	 * @param array <b>$nodes</b> The PAX nodes array
	 * @return void
	 */
	public static function filterTagContent(&$config, $elements, &$nodes)
	{
		// catch any exceptions
		try 
		{
			// load the filtering status from the config
			$filterTagContent = $config->get("filterTagContent", true);			
			
			// load the filtering status from the file
			$filterTagContentAttributeName = $config->get("filterTagContentAttributeName", "filterTagContent");
											
			if (isset($nodes[0]['attributes'][$filterTagContentAttributeName]))
			{
				// if the attribute exists, 
				// update the config value with the value from attribute
				// in order to continue filtering, the value must be in the 'true texts' dictionary				
				$filterTagContent = Model::isInList($nodes[0]['attributes'][$filterTagContentAttributeName], $elements->get("trueTexts"));
			}			
			
			// continue with the filtering
			if ($filterTagContent)
			{
				// get the separator used in tag namespace definition
				$paxNsSeparator = $elements->get("paxNsSeparator", ":");
				
				// get the validation method name
				// this method, if foun in the tag class, will be called to filter the content
				$validationMethodName = $elements->get("tagContentValidationMethodName", "validateContent");				
				
				// parse the nodes list
				foreach ($nodes as $index => $nodeData)
				{
					// if invalid node, skip
					if ($nodeData === false)
						continue;
					
					// if node is closed, continue 
					// (closed nodes don't have content attached)
					if ($nodeData['type'] == 'close')
						continue;
						
					// skip the nodes without a content
					if (!isset($nodeData['value']))
						continue;

					// get the position of the separator in the tag name
					// in order to get the class name
					$paxNsSepPos = strpos($nodeData['tag'], $paxNsSeparator);												
						
					if ($paxNsSepPos !== false)
						$currentNodeName = substr($nodeData['tag'], $paxNsSepPos + 1);
					else
						$currentNodeName = $nodeData['tag']; 
					
					// beautify the class name
					$currentNodeName = ucfirst($currentNodeName);

					// at this point, the class should already exists (loaded at filtering nodes method)
					// if not found, throw exception
					if (!class_exists($currentNodeName))
						throw new PAXException(sprintf(Messages::$MSG_038, $currentNodeName), 38);						
					
					// if the method does not exists in the class definition, 
					// skip filtering
					if (!method_exists($currentNodeName, $validationMethodName))
						continue;
						
					// create the filtering content evaluation string	
					$evalString = sprintf("\$newValue = %s::%s('%s');", $currentNodeName, $validationMethodName, $nodeData['value']);

					// if could not evaluate the string, 
					// throw exception
					if (@eval($evalString) === false)
						throw new PAXException(sprintf(Messages::$MSG_040, $currentNodeName, $nodeData['value']), 40);	
					
					// update the tag content value with the new value
					// the function should return null in case of invalid content
					$nodes[$index]['value'] = is_null($newValue) ? false : $newValue; 
				}
			}
		}
		catch (PAXException $pe)
		{
			// in case of exception, throw it further
			throw $pe;		
		}				
		catch (Exception $e)
		{
			// in case of exception, throw it futher
			throw $e;
		}
	}
	
	
	/**
	 * Method to compile the instructions.
	 *
	 * @access public
	 * @param config <b>$instructions</b> The PAX instructions
	 * @param array <b>$nodes</b> The nodes list
	 * @return void
	 */
	public function compileInstructions(&$instructions, &$nodes)
	{
		if (is_null($instructions))
			return;

		// create the pattern
		$pattern = new Pattern();
		$instructionExtractor = new InstructionExtractor();
		$instructionEvaluator = new InstructionEvaluator();
		$currentInstructions = array();	
			
		$startDelimiter = $instructions->get("startDelimiter", "!#");
		$endDelimiter = $instructions->get("endDelimiter", "#!");
		
		// parse each node
		foreach ($nodes as $nodeIndex => $nodeData)
		{
			if ($nodeData === false)
				continue;
				
			if (!isset($nodeData['value']))
				continue;
			
			try
			{
				// try to extract the instructions from the content of the node
				$currentInstructions = $instructionExtractor->extract($nodeData['value'], $startDelimiter, $endDelimiter);
			}
			catch (PAXException $pe)
			{
				// if exception, throw it further
				throw $pe;
			}
			catch (Exception $e)
			{
				// if exception, throw it further
				throw $e;
			}
			
			// validate the instructions
			if ($currentInstructions === false)
				continue;
				
			// compile each instruction
			// and replace it by its value
			foreach ($currentInstructions as $instruction)
			{
				$replacePattern = $pattern->makeInstructionReplacePattern($instruction, $startDelimiter, $endDelimiter);
				$nodes[$nodeIndex]['value'] = InstructionReplacer::replace($replacePattern, $instructionEvaluator->evaluate($instruction), $nodes[$nodeIndex]['value']);
			}
		}	
	}
	

	/**
	 * Method to extract the namespace and tag name based on the node data.
	 * 
	 * @access public
	 * @static 
	 * @param string <b>$tagName</b> The name of the tag as appears in the node data
	 * @param string <b>$namespaceSep</b> The separator used for delimiting the namespace and the tag name
	 * @return mixed False if invalid separator or array('namespace' => namespace, 'tagName' => name of the tag)
	 */
	public static function extractNamesFromTag($tagName, $namespaceSep)
	{
	    // assume the worst
	    $names = false;
	    
        $tagName = trim($tagName);
        
        if ($tagName == "")
            return $names;
        
        // init the return object as array         
        $names = array();                
            
        // get the separator position
        $namespaceSepPos = stripos($tagName, $namespaceSep);
        
        // if the separator has not been found,
        // create the return array as containing only the tag name
        if ($namespaceSepPos === false)
        {
            $names['namespace'] = false;
            $names['tagName'] = $tagName;            
        }
        else 
        {
            // else get both the namespace and the tag name
            // using substrings and separator position
            $names['namespace'] = substr($tagName, 0, $namespaceSepPos);
            $names['tagName'] = substr($tagName, $namespaceSepPos + 1);                       
        }
            
        // return the data collected so far
        return $names;
	}
	
	
	
	/**
	 * Class destructor.
	 * 
	 * @access private
	 */
	function __destruct()
	{
	}
}


?>
Return current item: PAX