<?php
/*
	DevSaver Web Framework
	Copyright (c) 2002-2018 DevSaver. 
	All rights reserved.
		web:  www.devsaver.com
		mail: support@devsaver.com
*/

if (!defined("STPBase")) {
	die("This file can't be accessed directly!");
}
/**
* description
*
* @library	
* @author	
* @since	
*/
class Shortcodes {
	var $shortcodes;
	var $content;

	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	function setContent($content) {
		$this->content = $content;
	}

	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	function getContent() {
		return $this->content;
	}
	
	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	function getShortcodes() {
	}
	
	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	function isValidShortcodeTag($code) {
		return is_array($this->shortcodes[$code]);
	}
	
	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	function processTag($m) {
		$code = $m["2"];

		if ($code == "icon") {
			//debug($m);
		}
		

		$attr = $this->parseAttrs($m["3"]);

		//add the inside data as special value called content
		if ($m[4]) {
			$attr["content"] = $m[4];
		}

		
		
		return $this->parseShortcode($code , $attr, $m["1"] , $m["5"]);
	}


	function parseAttrs($text) {

		//remove end / if exists
		$text = rtrim($text , " /");
		$text = str_replace("&nbsp;" , " " , $text);


		$attributes = array();
		$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
		$pattern = '/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/';
		$text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text);
		$text = html_entity_decode($text);
		if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
		  foreach ($match as $m) {
			if (!empty($m[1])) {
			  $attributes[strtolower($m[1])] = stripcslashes($m[2]);
			}
			elseif (!empty($m[3])) {
			  $attributes[strtolower($m[3])] = stripcslashes($m[4]);
			}
			elseif (!empty($m[5])) {
			  $attributes[strtolower($m[5])] = stripcslashes($m[6]);
			}
			elseif (isset($m[7]) and strlen($m[7])) {
			  $attributes[] = stripcslashes($m[7]);
			}
			elseif (isset($m[8])) {
			  $attributes[] = stripcslashes($m[8]);
			}
		  }
		}
		else {
		  $attributes = ltrim($text);
		}
		return $attributes;
	}


	
	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	function Parse($process = "process") {

		$shortcodes = $this->getShortcodes();

		// Processing recursively, now embedding tags within other tags is supported!
		$chunks = preg_split('!(\[{1,2}.*?\]{1,2})!', $this->content, -1, PREG_SPLIT_DELIM_CAPTURE);

		$heap = array();
		$heap_index = array();

		foreach ($chunks as $c) {

		  if (!$c) {
			continue;
		  }

		  $escaped = FALSE;

		  if ((substr($c, 0, 2) == '[[') && (substr($c, -2, 2) == ']]')) {
			$escaped = TRUE;
			// Checks media tags, eg: [[{ }]].
			if ((substr($c, 0, 3) != '{') && (substr($c, -3, 1) != '}')) {
			  // Removes the outer [].
			  $c = substr($c, 1, -1);
			}
		  }
		  // Decide this is a Shortcode tag or not.
		  if (!$escaped && ($c[0] == '[') && (substr($c, -1, 1) == ']')) {
			// The $c maybe contains Shortcode macro.

			// This is maybe a self-closing tag.
			// Removes outer [].
			$original_text = $c;
			$c = substr($c, 1, -1);
			$c = trim($c);

			$ts = explode(' ', $c);
			$tag = array_shift($ts);
			$tag = trim($tag, '/');

			//small fix for [icomoon="test-iicon"], the attribute will endup in {ATTR}
			if (stristr($tag , "=")) {


				$tmp = explode("=" , $tag);
				$tag = $tmp[0];
				//$ts = array("attr=" . $tmp[1]);

				//changed to allow a single parameter with spaces inside
				$tmp  = explode("=" , $c);
				$ts = array(
					"attr=" . $tmp[1]
				);
			}
			


			if (!$this->isValidShortcodeTag($tag)) {
			  // This is not a valid shortcode tag, or the tag is not enabled.
			  array_unshift($heap_index, '_string_');
			  array_unshift($heap, $original_text);
			}
			// This is a valid shortcode tag, and self-closing.
			elseif (substr($c, -1, 1) == '/') {
			  // Processes a self closing tag, - it has "/" at the end-
			  /*
			   * The exploded array elements meaning:
			   * 0 - the full tag text?
			   * 1/5 - An extra [] to allow for escaping Shortcodes with double [[]].
			   * 2 - The Shortcode name.
			   * 3 - The Shortcode argument list.
			   * 4 - The content of a Shortcode when it wraps some content.
			   */

			  $m = array(
				$c,
				'',
				$tag,
				implode(' ', $ts),
				NULL,
				'',
			  );

			  array_unshift($heap_index, '_string_');
			  array_unshift($heap, $this->processTag($m));

			}
			// A closing tag, we can process the heap.
			elseif ($c[0] == '/') {
			  $closing_tag = substr($c, 1);

			  $process_heap = array();
			  $process_heap_index = array();
			  $found = FALSE;

			  // Get elements from heap and process.
			  do {
				$tag = array_shift($heap_index);
				$heap_text = array_shift($heap);

				if ($closing_tag == $tag) {
				  // Process the whole tag.
				  $m = array(
					$tag . ' ' . $heap_text,
					'',
					$tag,
					$heap_text,
					implode('', $process_heap),
					'',
				  );
				  $str = $this->processTag($m);
				  array_unshift($heap_index, '_string_');
				  array_unshift($heap, $str);
				  $found = TRUE;
				}
				else {
				  array_unshift($process_heap, $heap_text);
				  array_unshift($process_heap_index, $tag);
				}
			  } while (!$found && $heap);

			  if (!$found) {
				foreach ($process_heap as $val) {
				  array_unshift($heap, $val);
				}
				foreach ($process_heap_index as $val) {
				  array_unshift($heap_index, $val);
				}
			  }

			}
			// A starting tag. Add into the heap.
			else {
			  //modify here to support non closing tags							  
			  array_unshift($heap_index, $tag);
			  array_unshift($heap, implode(' ', $ts));
			}
		  }
		  else {
			// Maybe not found a pair?
			array_unshift($heap_index, '_string_');
			array_unshift($heap, $c);
		  }
		  // End of foreach.
		}

		$this->content = (implode('', array_reverse($heap)));
	}



	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	function Add($code , $type = "func", $extra = null , $nested = false , $weight = 50) {
		$this->shortcodes[$code] = array(
			"code"		=> $code,
			"type"		=> $type , 
			"extra"		=> $extra,
			"nested"	=> $nested,
			"weight"	=> $weight
		);
	}


	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	private function parseShortcode($code , $attr , $before , $after) {

		$this->cleanupVars($attr);

		switch ($this->shortcodes[$code]["type"]) {
			case "html":
				return $this->processSimpleShortcode($code , $attr , $before , $after);
			break;

			case "func":
				return $this->processFunctionShortcode($code , $attr , $before , $after);
			break;	
		}			

	}

	
	private function processFunctionShortcode($code , $attr , $before , $after) {

		$data = call_user_func( $this->shortcodes[$code]["extra"], array("vars" => $attr , "shortcode" => $code , "before" => $before , "after" => $after) );

		
		if (is_array($data)) {
			return $data["before"] . $data["content"] . $data["after"];
		} else {
			return $before . $data . $madter;
		}
	}
	
	/**
	* description
	*
	* @param
	*
	* @return
	*
	* @access
	*/
	private function processSimpleShortcode($code , $attr , $before , $after) {
				
		$content = $this->shortcodes[$code]["extra"];

		if (is_array($content)) {
			

			if (empty($attr) || (count($attr) == 1 && $attr["attr"]) || (count($attr)== 2 && $attr["attr"] && $attr["content"])) {
				$content = $content["single"];
			} else {
				$content = $content["multi"];
			}
		}

		$this->cleanupVars($attr);

		return $before . CTemplateStatic::EmptyVars($content , $attr) .$after;
	}
	

	private function cleanupVars(&$vars) {

		if (is_array($vars) && count($vars)) {
			foreach ($vars as $key => &$val) {
				if ($key != "content") {

					$val = html_entity_decode($val);

					$val = str_replace('"' , '' , $val);
				}				
			}			
		}
	}
}

?>
