<?php if (!defined('HTTP_SERVER')) die('You can not access this file directly!');
/**
  @licence GPL 2005-2010  The osCSS developers - osCSS Open Source E-commerce
  @portion code Copyright (c) 2002 osCommerce
  @link http://www.oscommerce-freelancers.com/ osCommerce-Freelancers
  @package osCSS-2 <www http://www.oscss.org>
  @version 2.1.0
  @date  31/10/10, 18:17
  @author oscim <mail aurelien@oscim.fr> <www http://www.oscim.fr>
  @encode UTF-8
  @class seo_url

  Base sur la version 2.1 de Ultimate-SEO-URLs
  Ultimate SEO URLs offers search engine optimized URLS for osCommerce
  based applications. Other features include optimized performance and
  automatic redirect script.
 */
class seo_url{

  /**
    Constant de class debugg
  */
  const USE_SEO_REDIRECT_DEBUG = false;

  /**
  * $cache is the per page data array that contains all of the previously stripped titles
  @var array
  */
  var $cache;
  /**
  * $languages_id contains the language_id for this instance
  @var integer
  */
  var $languages_id;
  /**
  * $attributes array contains all the required settings for class
  @var array
  */
  var $attributes;
  /**
  * $base_url is the NONSSL URL for site
  @var string
  */
  protected static $base_url;
  /**
  * $base_url_ssl is the secure URL for the site
  @var string
  */
  protected static $base_url_ssl;
  /**
  * $performance array contains evaluation metric data
  @var array
  */
  var $performance;
  /**
  * $timestamp simply holds the temp variable for time calculations
  @var float
  */
  var $timestamp;
  /**
  * $reg_anchors holds the anchors used by the .htaccess rewrites
  @var array
  */
  var $reg_anchors;
  /**
  * $cache_query is the resource_id used for database cache logic
  @var resource
  */
  var $cache_query;
  /**
  * $cache_file is the basename of the cache database entry
  @var string
  */
  var $cache_file;
  /**
  * $data array contains all records retrieved from database cache
  @var array
  */
  var $data;
  /**
  * $need_redirect determines whether the URL needs to be redirected
  @var boolean
  */
  var $need_redirect;
  /**
  * $is_seopage holds value as to whether page is in allowed SEO pages
  @var boolean
  */
  var $is_seopage;
  /**
  * $uri contains the $_SERVER['REQUEST_URI'] value
  @var string
  */
  var $uri;
  /**
  * $real_uri contains the $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING'] value
  @var string
  */
  var $real_uri;
  /**
  * $uri_parsed contains the parsed uri value array
  @var array
  */
  var $uri_parsed;
  /**
  * $path_info contains the getenv('PATH_INFO') value
  @var string
  */
  var $path_info;
  /**
  * $DB is the database object
  @var object
  */
  var $DB;

  /**
  * $header_tag element for page
  @var object
  */
  var $header_tag;


  /**
    Interne static function
  */
  protected static $extention;
  protected static $origUrl;



  /**
    Var conf
  */
  protected static $SEO_REWRITE_TYPE ;
  protected static $USE_SEO_REDIRECT;

  protected static $USE_SEO_CACHE_GLOBAL;
  protected static $USE_SEO_CACHE_LINKS;
  protected static $USE_SEO_CACHE_PRODUCTS;
  protected static $USE_SEO_CACHE_CATEGORIES;
  protected static $USE_SEO_CACHE_TOPICS;

  protected static $_instance;

  /**
   *
   * \brief SEO_URL class constructor
   * @author Bobby Easland
   * @version 1.1
   * @param $languages_id integer
  */
  private function __construct($languages_id){
    global $session_started, $SID;


    $this->DB= Database::getInstance();

    $this->languages_id = (int)$languages_id;

    $this->data = array();

    /// liaison header_tag
    $this->header_tag=array();

//ojp FILENAME_LINKS
    $seo_pages=array(FILENAME_DEFAULT,FILENAME_ACCOUNT,FILENAME_POPUP_IMAGE,FILENAME_PRODUCT_INFO,FILENAME_CONTENT,FILENAME_CREATE_ACCOUNT,FILENAME_ADVANCED_SEARCH,FILENAME_PRODUCTS_NEW,FILENAME_SMALL_PRICE,FILENAME_SPECIALS,FILENAME_BEST_SELLERS,FILENAME_SITEMAP,FILENAME_TEMPLATE,FILENAME_CONTACT_US, FILENAME_SHOPPING_CART);
    if ( defined('FILENAME_LINKS') ) $seo_pages[] = FILENAME_LINKS;


    $this->attributes = array(
      'SESSION_STARTED' => $session_started,
      'SID' => $SID,

      'SEO_ADD_CPATH_TO_PRODUCT_URLS' => defined('SEO_ADD_CPATH_TO_PRODUCT_URLS') ? SEO_ADD_CPATH_TO_PRODUCT_URLS : 'false',
      'SEO_ADD_CAT_PARENT' => defined('SEO_ADD_CAT_PARENT') ? SEO_ADD_CAT_PARENT : 'true',
      'SEO_URLS_USE_W3C_VALID' => defined('SEO_URLS_USE_W3C_VALID') ? SEO_URLS_USE_W3C_VALID : 'true',
//       'USE_SEO_CACHE_MANUFACTURERS' => defined('USE_SEO_CACHE_MANUFACTURERS') ? USE_SEO_CACHE_MANUFACTURERS : 'false',

      'SEO_URLS_FILTER_SHORT_WORDS' => defined('SEO_URLS_FILTER_SHORT_WORDS') ? SEO_URLS_FILTER_SHORT_WORDS : 'false',
      'SEO_CHAR_CONVERT_SET' => defined('SEO_CHAR_CONVERT_SET') ? $this->expand(SEO_CHAR_CONVERT_SET) : 'false',
      'SEO_REMOVE_ALL_SPEC_CHARS' => defined('SEO_REMOVE_ALL_SPEC_CHARS') ? SEO_REMOVE_ALL_SPEC_CHARS : 'false',
      'SEO_PAGES' => $seo_pages,

      );

    /**
      Conf
    */
    self::$SEO_REWRITE_TYPE=defined('SEO_REWRITE_TYPE') ? SEO_REWRITE_TYPE : 'false';
    self::$USE_SEO_REDIRECT= defined('USE_SEO_REDIRECT') ? USE_SEO_REDIRECT : 'false';

    self::$USE_SEO_CACHE_GLOBAL=defined('USE_SEO_CACHE_GLOBAL') ? USE_SEO_CACHE_GLOBAL : 'false';
    self::$USE_SEO_CACHE_LINKS=defined('USE_SEO_CACHE_LINKS') ? USE_SEO_CACHE_LINKS : 'false';
    self::$USE_SEO_CACHE_PRODUCTS=defined('USE_SEO_CACHE_PRODUCTS') ? USE_SEO_CACHE_PRODUCTS : 'false';
    self::$USE_SEO_CACHE_CATEGORIES=defined('USE_SEO_CACHE_CATEGORIES') ? USE_SEO_CACHE_CATEGORIES : 'false';
    self::$USE_SEO_CACHE_TOPICS=defined('USE_SEO_CACHE_TOPICS') ? USE_SEO_CACHE_TOPICS : 'false';


    $this->cache = array();
    $this->timestamp = 0;

    $this->reg_anchors['no-rewrite'] = array('products_id' => 'products_id=',
				    'cPath' => 'cPath=',
				    'manufacturers_id' => 'manufacturers_id=',
				    'pID' => 'pID=',
				    'content' => 'content=',
				    'divers' => 'divers=',
				    'lID' => 'lID=',
				    'customers_id' => 'customers_id=',
				    );
    $this->reg_anchors['Rewrite'] = array('products_id' => '-p-',
				    'cPath' => '-c-',
				    'manufacturers_id' => '-m-',
				    'pID' => '-pi-',
				    'content' => '-t-',
				    'divers' => '-d-',
				    'lID' => '-l-',
				    'customers_id' => '-u-',
				    );
    $this->reg_anchors['Rewrite-mode2'] = array('products_id' => 'p/',
					'cPath' => 'c/',
					'manufacturers_id' => 'm/',
					'pID' => 'pi/',
					'content' => 't/',
					'divers' => 'd/',
					'lID' => 'l/',
					'customers_id' => 'u/',
					);

    $this->reg_ext['no-rewrite']='html';
    $this->reg_ext['Rewrite']='html';
    $this->reg_ext['Rewrite-mode2']='html';


    $this->performance = array('NUMBER_URLS_GENERATED' => 0,
				'NUMBER_QUERIES' => 0,
				'CACHE_QUERY_SAVINGS' => 0,
				'NUMBER_STANDARD_URLS_GENERATED' => 0,
				'TOTAL_CACHED_PER_PAGE_RECORDS' => 0,
				'TOTAL_TIME' => 0,
				'TIME_PER_URL' => 0,
				'QUERIES' => array()
				);
    //ojp generate_link_cache
    if (_test_bool(self::$USE_SEO_CACHE_GLOBAL)){
      $this->cache_file = 'seo_urls_v2_';
      $this->cache_gc();

      if ( _test_bool(self::$USE_SEO_CACHE_PRODUCTS )) $this->generate_products_cache();
      if ( _test_bool(self::$USE_SEO_CACHE_CATEGORIES)) $this->generate_categories_cache();
  //       if ( _test_bool($this->attributes['USE_SEO_CACHE_MANUFACTURERS'] ) ) $this->generate_manufacturers_cache();
  //       if ( _test_bool($this->attributes['USE_SEO_CACHE_ARTICLES'] ) && defined('TABLE_ARTICLES_DESCRIPTION'))
  //  $this->generate_articles_cache();
      if ( _test_bool(self::$USE_SEO_CACHE_TOPICS) && defined('TABLE_CONTENT_DESCRIPTION')) $this->generate_topics_cache();
      $this->generate_divers_cache();
      if ( _test_bool(self::$USE_SEO_CACHE_LINKS) ) $this->generate_links_cache();
    }

    if (_test_bool( self::$USE_SEO_REDIRECT)) $this->check_redirect();




    /**
     * \brief Prepa static var
    */
    self::$extention= (!empty($this->reg_ext[self::$SEO_REWRITE_TYPE]))? '.'.$this->reg_ext[self::$SEO_REWRITE_TYPE] : '';

    self::$base_url = HTTP_SERVER . DIR_WS_HTTP_CATALOG;
    self::$base_url_ssl = HTTPS_SERVER . DIR_WS_HTTPS_CATALOG;
    self::$origUrl = strip_tags($this->requested_page());


    /**
     *\brief Base tag defaut
    */
    $sql = "select head_title_tag , head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='generic' and  ft.page_id = 'generic' and ftd.language_id = '" . (int)$this->languages_id. "' Limit 1";
    $query=$this->DB->query( $sql );
    $res=$query->fetchAssoc();
    define('HEAD_TITLE_TAG_ALL', $res['head_title_tag']);
    define('HEAD_DESC_TAG_ALL', $res['head_desc_tag']);
    define('HEAD_KEY_TAG_ALL', $res['head_keywords_tag']);
  }

  public static function getInstance($languages_id='') {
    if(empty($languages_id)) global $languages_id;
    if(self::$_instance == null)  self::$_instance = new self($languages_id);
    return self::$_instance;
  }

  public static function resetInstance($languages_id='') {
    if(empty($languages_id)) global $languages_id;
    self::$_instance = null;
    return self::getInstance($languages_id);
  }

  /**
   * \brief Function to return SEO URL link SEO'd with stock generattion for error fallback
   * @author Bobby Easland
   * @param $page string  Base script for URL
   * @param $parameters string  URL parameters
   * @param $connection string $connection NONSSL/SSL
   * @param $add_session_id boolean $add_session_id Switch to add osCsid
   * @return string Formed href link
  */
  public function href_link($page = '', $parameters = '', $connection = 'SSL', $add_session_id = false){
    $this->start($this->timestamp);
    $this->performance['NUMBER_URLS_GENERATED']++;

    if ( !in_array($page, $this->attributes['SEO_PAGES']) )  return $this->stock_href_link($page, $parameters, $connection, $add_session_id);
    $link = ($connection == 'NONSSL' || !(getenv('HTTPS') == 'on') )? self::$base_url : self::$base_url_ssl;
    $separator = '?';

    if ($this->not_null($parameters))  $link .= $this->parse_parameters($page, $parameters ,$connection , $separator);
    else  $link .= $this->parse_parameters($page, (!empty($parameters)?$parameters.'&':'') .'divers='.substr($page,0,strlen($page)-4) ,$connection , $separator); //$page;

    $this->stop($this->timestamp, $time);
    $this->performance['TOTAL_TIME'] += $time;
    switch($this->attributes['SEO_URLS_USE_W3C_VALID']){
      case ('true'):
	if (!isset($_SESSION['customer_id']) && _cst_bool('ENABLE_PAGE_CACHE') && class_exists('page_cache')) return $link;
	  else {
	  return htmlentities($link);
	}
      break;
      case ('false'):
	return $link;
      break;
    }
  }

  /**
   * \brief Recuperation des element pour le header_tag
   * @param $page
   * @param $parameters
   * @param $connection
   * @param $add_session_id
   */
  public function header_tags($page = '', $parameters = '', $connection = 'NONSSL', $add_session_id = false){
    $link = $connection == 'NONSSL' ? self::$base_url : self::$base_url_ssl;
    $separator = '?';
    $this->parse_parameters($page, $parameters ,$connection , $separator, true);
    if(!isset($this->header_tag['title'])) $this->transph_header_tag(array());
  }

  /**
   * \brief constructor du header_tag
   * @param $result
   */
  private function transph_header_tag($result){
    $this->header_tag['title']=(isset($result['tName']) && !empty($result['tName']))? $result['tName'] : @constant('HEAD_TITLE_TAG_ALL') ;
    $this->header_tag['desc']=(isset($result['head_desc_tag']) && !empty($result['head_desc_tag']))? $result['head_desc_tag']:@constant('HEAD_DESC_TAG_ALL') ;
    $this->header_tag['keywords']=(isset($result['head_keywords_tag']) && !empty($result['head_keywords_tag']))? $result['head_keywords_tag']: @constant('HEAD_KEY_TAG_ALL') ;
  }

  /**
   * \brief Stock function, fallback use
   * @param $page
   * @param $parameters
   * @param $connection
   * @param $add_session_id
   * @param $search_engine_safe
   */
  private function stock_href_link($page = '', $parameters = '', $connection = 'NONSSL', $add_session_id = false, $search_engine_safe = true) {
    global $request_type, $session_started, $SID, $kill_sid; ///// SID-KILLER ( change) /// added $kill_sid.
//     if (!$this->not_null($page))  die('</td></tr></table></td></tr></table><br /><br /><font color="#ff0000"><strong>Error!</strong></font><br /><br /><strong>Unable to determine the page link!</strong><br /><br />');
    if ($page == '/') $page = '';

    if ($connection == 'NONSSL')  $link = HTTP_SERVER . DIR_WS_HTTP_CATALOG;
    elseif ($connection == 'SSL') {
      if (_cst_bool('ENABLE_SSL'))  $link = HTTPS_SERVER . DIR_WS_HTTPS_CATALOG;
      else  $link = HTTP_SERVER . DIR_WS_HTTP_CATALOG;
    }
    else  die('</td></tr></table></td></tr></table><br /><br /><font color="#ff0000"><strong>Error!</strong></font><br /><br /><strong>Unable to determine connection method on a link!<br /><br />Known methods: NONSSL SSL</strong><br /><br />');

    if ($this->not_null($parameters)) {
      $link .= $page . '?' . $this->output_string($parameters);
      $separator = '&amp;';
    } else {
      $link .= $page;
      $separator = '?';
    }
    while ( (substr($link, -5) == '&amp;') || (substr($link, -1) == '&') || (substr($link, -1) == '?') ) $link = substr($link, 0, -1);
    if ( ($add_session_id == true) && ($session_started == true) && _cst_bool('SESSION_FORCE_COOKIE_USE') ) {
      if ($this->not_null($SID)) {
        $_sid = $SID;
      } elseif ( ( ($request_type == 'NONSSL') && ($connection == 'SSL') && _cst_bool('ENABLE_SSL') ) || ( ($request_type == 'SSL') && ($connection == 'NONSSL') ) ) {
        if (HTTP_COOKIE_DOMAIN != HTTPS_COOKIE_DOMAIN) {
          $_sid = $this->SessionName() . '=' . $this->SessionID();
        }
      }
    }

    switch(true){
      case (!isset($_SESSION['customer_id']) && _cst_bool('ENABLE_PAGE_CACHE') && class_exists('page_cache')):
	$page_cache = true;
	$return = $link . $separator . '<osCsid>';
      break;
      case (isset($_sid) && ( !$kill_sid )): ///// SID-KILLER ( change ) /// ORG: case (isset($_sid)):
	$page_cache = false;
	$return = $link . $separator . tep_output_string($_sid);
      break;
      default:
	$page_cache = false;
	$return = $link;
      break;
    }
    $this->performance['NUMBER_STANDARD_URLS_GENERATED']++;
    $this->cache['STANDARD_URLS'][] = $link;
    $time = 0;
    $this->stop($this->timestamp, $time);
    $this->performance['TOTAL_TIME'] += $time;
    switch(true){
      case (_test_bool($this->attributes['SEO_URLS_USE_W3C_VALID']) && !$page_cache):
	return htmlentities(str_replace('&amp;','&',$return));
      break;
      default:
	return str_replace('&amp;','&',$return);
      break;
    }
  }

  /**
   * \brief Function to append session ID if needed
   * @author Bobby Easland
   * @param  $link string
   * @param $add_session_id boolean
   * @param $connection string
   * @param $separator string
   * @return string
  */
  private function add_sid( $link, $add_session_id, $connection, $separator ){
    global $request_type, $kill_sid;
    if ( ($add_session_id) && ($this->attributes['SESSION_STARTED']) && !_cst_bool('SESSION_FORCE_COOKIE_USE') ) {
      if ($this->not_null($this->attributes['SID']))  $_sid = $this->attributes['SID'];
      elseif ( ( ($request_type == 'NONSSL') && ($connection == 'SSL') && (ENABLE_SSL == true) ) || ( ($request_type == 'SSL') && ($connection == 'NONSSL') ) ) {
	if (HTTP_COOKIE_DOMAIN != HTTPS_COOKIE_DOMAIN)  $_sid = $this->SessionName() . '=' . $this->SessionID();
      }
      else $_sid=tep_session_name().'='.tep_session_id();
    } else $_sid=tep_session_name().'='.tep_session_id();

    switch(true){
      case (!isset($_SESSION['customer_id']) && _cst_bool('ENABLE_PAGE_CACHE')  && class_exists('page_cache')):
	$return = $link . $separator . '<osCsid>';
      break;
      case (isset($_sid) && $this->not_null($_sid) && ( !$kill_sid )):
	$return = $link . $separator . tep_output_string($_sid);
	break;
      default:
	$return = $link;
      break;
    }
    return $return;
  }

  /**
   * \brief Function to parse the parameters into an SEO URL
   * @author Bobby Easland
   * @param $page string
   * @param $params string
   * @param $connection string
   * @param $separator string  NOTE: passed by reference
   * @return string
  */
  private function parse_parameters($page, $params ,$connection , &$separator){
    $params=str_replace('&amp;','&',$params);
    $p = @explode('&', $params);
    krsort($p);
    $container = array();

    foreach ($p as $index => $valuepair){
      $p2 = @explode('=', $valuepair);

      switch ($p2[0]){
	case 'products_id':
	  switch(true){
	    default:
	    case ( !$this->is_attribute_string($p2[1]) && in_array($page,array(FILENAME_PRODUCT_INFO, FILENAME_DEFAULT) ) ):
	      $url = $this->make_url($page, $this->get_product_name($p2[1]), $p2[0], $p2[1],  $separator);
	      $this->ValidateName($url, "p", $p2[1], $connection, $separator);
	    break;
	    default:
	      $container[$p2[0]] = $p2[1];
	    break;
	  }
	break;
	case 'cPath':
	  switch(true){
	    case ($page == FILENAME_DEFAULT):
	    default:
	      $url = $this->make_url($page, $this->get_category_name($p2[1]), $p2[0], $p2[1],  $separator);
	      $this->ValidateName($url, "c", $p2[1], $connection, $separator);
	    break;
// 	    case ( !$this->is_product_string($params) ):
// 	      if ( _test_bool($this->attributes['SEO_ADD_CPATH_TO_PRODUCT_URLS'])){
// 		$container[$p2[0]] = $p2[1];
// 	      }
// 	    break;
// 	    default:
// 	      $container[$p2[0]] = $p2[1];
// 	    break;
	    }
	break;
	case 'manufacturers_id':
	  switch(true){
	    case ($page == FILENAME_DEFAULT && !$this->is_cPath_string($params) && !$this->is_product_string($params) ):
	      $url = $this->make_url($page, $this->get_manufacturer_name($p2[1]), $p2[0], $p2[1],  $separator);
	      $this->ValidateName($url, "m", $p2[1], $connection, $separator);
	    break;
	    case ($page == FILENAME_PRODUCT_INFO):
	    break;
	    default:
		    $container[$p2[0]] = $p2[1];
	    break;
	  }
	break;
	case 'pID':
	  switch(true){
	    case ($page == FILENAME_POPUP_IMAGE):
	      $url = $this->make_url($page, $this->get_product_name($p2[1]), $p2[0], $p2[1],  $separator);
	      $this->ValidateName($url, "pID", $p2[1], $connection, $separator);
	    break;
	    default:
	      $container[$p2[0]] = $p2[1];
	    break;
	  }
	break;
	case 'lID':
	  switch(true){
	    default:
	      $url = $this->make_url($page, $this->get_links_name($p2[1]), $p2[0], $p2[1],  $separator);
	      $this->ValidateName($url, "lID", $p2[1], $connection, $separator);
	    break;
	  }
	break;
	case 'content':
	  switch(true){
	    case ($page == FILENAME_CONTENT):
	      $url = $this->make_url($page, $this->get_topic_name($p2[1]), $p2[0], $p2[1],  $separator);
	      $this->ValidateName($url, "t", $p2[1], $connection, $separator);
	    break;
	    default:
	      $container[$p2[0]] = $p2[1];
	    break;
	  }
	break;
	case 'divers':
	  switch(true){
	    default:
	      $url = $this->make_url($page, $this->get_divers_name($p2[1]), $p2[0], $p2[1], $separator);
	      $this->ValidateName($url, "d", $p2[1], $connection, $separator);
	    break;
	  }
	break;
	case 'customers_id':
	  switch(true){
	    default:
	      $url = $this->make_url($page, $this->get_divers_name($p2[1]), $p2[0], $p2[1], $separator);
	      $this->ValidateName($url, "u", $p2[1], $connection, $separator);
	    break;
	  }
	break;
	default:
	  $container[$p2[0]] = @$p2[1];
	  switch(true){
	    case ($page == '' || $page ==FILENAME_DEFAULT):
	      $id=AbstractHeader_tags::get_prio_cms('home');
	    //! home by cms
	      if(is_int($id)) {
		$url = $this->make_url($page, $this->get_topic_name($id), "content", $id,  $separator);
		$this->ValidateName($url, "t", $id, $connection, $separator);
	      }
	    //! home  by page content
	      else {
		$url = ''; // $this->make_url('home', $this->get_divers_name('home'), 'divers', 'home',  $separator);
		//$this->ValidateName($url, "d", 'home', $connection, $separator);
	      }
	    break;
	    default:
	      if(is_array($p2)) $container[$p2[0]] = @$p2[1];
	    break;
	  }
      }
    }
    $url = isset($url) ? $url : $page;
    if ( sizeof($container) > 0 ){
      if ( $imploded_params = $this->implode_assoc($container) ){
	$url .= $separator . $this->output_string( $imploded_params );
	$separator = '&';
      }
    }
    return $url;
  }

  /**
    * Function to return the generated SEO URL
    @author Bobby Easland
    @version 1.0
    @param $page string
    @param $string string  Stripped, formed anchor
    @param $anchor_type string  Parameter type (products_id, cPath, etc.)
    @param $id integer
    @param $separator string  NOTE: passed by reference
    @return string
  */
  private function make_url($page, $string, $anchor_type, $id,  &$separator){
    switch ( self::$SEO_REWRITE_TYPE ){
      case 'Rewrite':
	return $string . $this->reg_anchors['Rewrite'][$anchor_type] . $id .self::$extention;
      break;
      case 'Rewrite-mode2':
	return  $this->reg_anchors['Rewrite-mode2'][$anchor_type] . $id .'/'.$string. self::$extention;
      break;
    }
  }


  /**
   * \brief Ajout ext url
   * @param $url
   * @param $type
   * @param $realID
   * @param $connection
   * @param $separator
   */
  private function ValidateName($url, $type, $realID, $connection, $separator) {
    $parts = explode("-", self::$origUrl);

    if (isset($parts[count($parts) - 2]) && $parts[count($parts) - 2] == $type){  //make sure it is the correct type for this link
      if (($pos = strpos($parts[count($parts) - 1], self::$extention)) !== FALSE) $id = substr($parts[count($parts) - 1], 0, $pos);       //strip .html

      $catalog = DIR_WS_HTTP_CATALOG;
      if ($catalog[0] == '/')  substr(DIR_WS_HTTP_CATALOG, 1);             //strip leading slash if present

      if (strpos(self::$origUrl, $catalog) !== FALSE) self::$origUrl = substr(self::$origUrl, strlen($catalog));         //remove the catalog from the url string

      if (!empty(self::$origUrl) && $id === $realID && self::$origUrl !== $url){ //ID's match but not the text
         $url = self::$base_url . $url;
         $link = $this->add_sid($url, true, $connection, $separator); //build the correct link with SID
	  //header("HTTP/1.0 301 Moved Permanently");             //let the SE's know to not use this link
	  //header("Location: $link");                            //redirect to the real page
      }
    }
  }

  private function requested_page() {
    $protocol = ((int) $_SERVER['SERVER_PORT'] === 443)? 'https://' : 'http://';
    $current_page = $protocol . $_SERVER['HTTP_HOST'] . ((!empty($_SERVER['REQUEST_URI']))? $_SERVER['REQUEST_URI'] : '');
    $current_page = substr($current_page, strlen(HTTP_SERVER));
    if (($pos = strpos($current_page, "?osCsid")) !== FALSE)
      $current_page = substr($current_page, 0, $pos).'<br />';
    if ($current_page[0] == "/")
      $current_page = substr($current_page, 1);

    return $current_page;
  }

  /**
   * \brief Function to get the product name. Use evaluated cache, per page cache, or database query in that order of precedent
   * @param $pID integer
   * @return string Stripped anchor text
  */
  private function get_divers_name($pID){
    switch(true){
      case (_test_bool(self::$USE_SEO_CACHE_GLOBAL) && isset($this->cache['DIVERS'][$pID])):
	$this->performance['CACHE_QUERY_SAVINGS']++;
	$result = $this->get_cache_id($pID,'','DIVERS');
	$return = (isset($result['tName'])? $this->strip($result['tName']): $this->strip($pID));
	$this->transph_header_tag($result);
      break;
      default:
	$this->performance['NUMBER_QUERIES']++;
	 $sql = "select head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='page' and  ft.page_id = '" . $pID. "' and ftd.language_id = '" . (int)$this->languages_id. "' Limit 1";
	  $query=$this->DB->query( $sql );
	  if ($query->__get('numRows')>0) $result =$query->fetchAssoc();
	  else $result['tName'] = $this->strip($pID);
	  $this->get_cache_id($pID,'','DIVERS');
	  $this->performance['QUERIES']['DIVERS'][] = $sql;
	  $this->transph_header_tag($result);
	  $return = $result['tName'];
	break;
    }
    return $return;
  }

  /**
   * \brief Mise en cache de la table des type page,  / table TABLE_FULL_TAG
   * @return array
  */
  private function generate_divers_cache(){
    $res=$this->DB->query( "select ft.page_id as ID, head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='page' and   ftd.language_id = '" . (int)$this->languages_id. "' " );
    while ($result =$res->fetchAssoc()){
      $this->get_cache_id($result['ID'],$result,'DIVERS');
    }
  }

  /**
   * \brief Function to get the product name. Use evaluated cache, per page cache, or database query in that order of precedent
   * @author Bobby Easland
   * @version 1.1
   * @param $pID integer
   * @return string Stripped anchor text
  */
  private function get_links_name($pID){
    switch(true){
      case (_test_bool(self::$USE_SEO_CACHE_GLOBAL) && isset($this->cache['LINKS'][$pID]) ):
	$this->performance['CACHE_QUERY_SAVINGS']++;
	$result = $this->get_cache_id($pID,'','LINKS');
	$return = (isset($result['tName'])? $this->strip($result['tName']): $this->strip($pID));
	$this->transph_header_tag($result);
      break;
      default:
	$this->performance['NUMBER_QUERIES']++;
	  $query=$this->DB->query( "select nav_name as tName, l.nav_link_id as ID from " . TABLE_NAVIGATION_LINKS . " l, "  . TABLE_NAVIGATION_LINKS_DESCRIPTION .  " ld where l.nav_link_id=ld.nav_link_id l.nav_link_id = '" . $pID. "' and ld.language_id = '" . (int)$this->languages_id. "' Limit 1" );
	  if ($query->__get('numRows')>0) $result =$query->fetchAssoc();
	  else $result['tName'] = $this->strip($pID);
	  $this->get_cache_id($pID,'','LINKS');
	  $this->performance['QUERIES']['LINKS'][] = $query;
	  $this->transph_header_tag($result);
	  $return = $result['tName'];
	break;
    }
    return $return;
  }


  /**
   * \brief Mise en cache de la table des type page,  / table TABLE_FULL_TAG
   * @return array
  */
  private function generate_links_cache(){
    $res=$this->DB->query( "select ft.page_id as ID, head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='link' and   ftd.language_id = '" . (int)$this->languages_id. "' " );
    while ($result =$res->fetchAssoc()){
      $this->get_cache_id($result['ID'],$result,'LINKS');
    }
  }

  /**
   * \brief Function to get the product name. Use evaluated cache, per page cache, or database query in that order of precedent
   * @author Bobby Easland
   * @version 1.1
   * @param $pID integer
   * @return string Stripped anchor text
  */
  private function get_product_name($pID){
    switch(true){
      case (_test_bool(self::$USE_SEO_CACHE_GLOBAL) && isset($this->cache['PRODUCTS'][$pID]) ):
	$this->performance['CACHE_QUERY_SAVINGS']++;
	$result = $this->get_cache_id($pID,'','PRODUCTS');
	$return = (isset($result['tName'])? $this->strip($result['tName']): '');
	$this->transph_header_tag($result);
      break;
      default:
	$this->performance['NUMBER_QUERIES']++;
	switch(true){
	  case (_test_bool($this->attributes['SEO_ADD_CAT_PARENT'])):
	    $rsql = $this->DB->query("SELECT products_name as tName, categories_name as cName FROM ".TABLE_PRODUCTS_DESCRIPTION." p LEFT JOIN ".TABLE_PRODUCTS_TO_CATEGORIES." cd2 ON p.products_id=cd2.products_id JOIN ".TABLE_CATEGORIES_DESCRIPTION." cd ON cd.categories_id = cd2.categories_id AND cd.language_id='".(int)$this->languages_id."'   WHERE p.products_id='".(int)$pID."' AND p.language_id='".(int)$this->languages_id."' LIMIT 1");
	    $result = $rsql->fetchAssoc();
	    $cName = $result['cName'];

	    $result['tName']=$result['cName'].'/'.$result['tName'];
	    break;
	  default:
	    $rsql = $this->DB->query("SELECT products_name as tName FROM ".TABLE_PRODUCTS_DESCRIPTION." WHERE products_id='".(int)$pID."' AND language_id='".(int)$this->languages_id."' LIMIT 1");
	    $result = $rsql->fetchAssoc();
	    $cName = $result['tName'];
	    break;
	}
	/*if(!defined('TABLE_FULL_TAG'))*/
	// 	else $sql = "select head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='product' and  ft.page_id = '" . (int)$pID. "' and ftd.language_id = '" . (int)$this->languages_id. "' Limit 1";
// 	$result = $this->DB->FetchArray( $this->DB->Query( $sql ) );
	$this->get_cache_id($pID,$result,'PRODUCTS');
	$this->performance['QUERIES']['PRODUCTS'][] = $rsql;
	$this->transph_header_tag($result);
	$return = $this->strip( $result['tName'] );
	break;
    }
    return $return;
  }

  /**
   * \brief Mise en cache des element produits / table TABLE_FULL_TAG
   * @return array
  */
  private function generate_products_cache(){
    $res=$this->DB->query( "select ft.page_id as ID , head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='product' and   ftd.language_id = '" . (int)$this->languages_id. "'" );
    while ($result =$res->fetchAssoc()){
      $this->get_cache_id($result['ID'],$result,'PRODUCTS');
    }
  }

  /**
   * \brief Function to get the category name. Use evaluated cache, per page cache, or database query in that order of precedent
   * @author Bobby Easland
   * @version 1.1
   * @param $cID integer NOTE: passed by reference
   * @return string Stripped anchor text
  */
  private function get_category_name(&$cID){
    $ID=substr(strrchr($cID, '_'),1);
    $ID=(is_numeric($ID) ?$ID : $this->get_full_cPath($cID, $single_cID));
    switch(true){
      case (_test_bool(self::$USE_SEO_CACHE_GLOBAL) && isset($this->cache['CATEGORIES'][$ID])  ):
	$this->performance['CACHE_QUERY_SAVINGS']++;
	$result = $this->get_cache_id($ID,'','CATEGORIES');;
	$return = (isset($result['tName'])? $this->strip($result['tName']): '');
	$this->transph_header_tag($result);
      break;
      default:
	$this->performance['NUMBER_QUERIES']++;
	switch(true){
	  case (_test_bool($this->attributes['SEO_ADD_CAT_PARENT'])):
	    $rsql = $this->DB->query("SELECT c.categories_id as ID, c.parent_id, cd.categories_name as tName, cd2.categories_name as pName
			    FROM ".TABLE_CATEGORIES." c
			    JOIN ".TABLE_CATEGORIES_DESCRIPTION." cd
			    ON c.categories_id = cd.categories_id AND cd.language_id='".(int)$this->languages_id."'
			    LEFT JOIN ".TABLE_CATEGORIES_DESCRIPTION." cd2
			    ON c.parent_id=cd2.categories_id AND cd2.language_id='".(int)$this->languages_id."'
			    WHERE c.categories_id='".(int)$ID."'
			    LIMIT 1");
	    $result = $rsql->fetchAssoc();
	    $result['tName']=$cName = $this->not_null($result['pName']) ? $this->strip($result['pName'] . ' ' . $result['tName']) : $this->strip($result['tName']);
	    break;
	  default:
	     $rsql = $this->DB->query("SELECT categories_id as ID, categories_name as tName FROM ".TABLE_CATEGORIES_DESCRIPTION." WHERE categories_id='".(int)$ID."' AND language_id='".(int)$this->languages_id."' LIMIT 1");
	    $result = $rsql->fetchAssoc();
	    $cName = $result['tName'];
	  break;
	  $this->performance['QUERIES']['CATEGORIES'][] = $rsql;
	}
	$cName = $this->strip($cName);
	$this->get_cache_id($ID,$result,'CATEGORIES');
	$this->transph_header_tag($result);
	$return = $cName;
      break;
    }
    return $return;
  }

  /**
   * \brief Mise en cache des element produits / table TABLE_FULL_TAG
   * @package oscss-2 <www http://www.oscss.org>
   * @author oscim <mail oscim@oscss.org> <www http://www.oscim.fr>
   * @return array
  */
  private function generate_categories_cache(){
    $res =  $this->DB->query( "select ft.page_id as ID , head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='cat' and   ftd.language_id = '" . (int)$this->languages_id. "'" );
    while($result = $res->fetchAssoc()){
//       if(!isset($this->cache['GET_CATEGORIES_P'][$result['ID']])) $this->cache['GET_CATEGORIES_P'][$result['ID']]=$result['parent_id'];
      $this->get_cache_id($result['ID'],$result,'CATEGORIES');
    }
  }

  /**
   * \brief Function to get the manufacturer name. Use evaluated cache, per page cache, or database query in that order of precedent.
   * @author Bobby Easland
   * @param $mID integer
   * @return string
  */
  private function get_manufacturer_name($mID){
    switch(true){
      case (_test_bool(self::$USE_SEO_CACHE_GLOBAL) && isset($this->cache['MANUFACTURERS'][$mID])):
	$this->performance['CACHE_QUERY_SAVINGS']++;
	$result = $this->cache['MANUFACTURERS'][$mID];
	$return = (isset($result['tName'])? $result['tName']: '');
	$this->transph_header_tag($result);
      break;
      default:
	$this->performance['NUMBER_QUERIES']++;
	$rsql = $this->DB->query("SELECT manufacturers_name as tName FROM ".TABLE_MANUFACTURERS." WHERE manufacturers_id='".(int)$mID."' LIMIT 1");
	$result = $rsql->fetchAssoc(); // $this->DB->FetchArray( $this->DB->Query( $sql ) );
	$mName = $this->strip( $result['tName'] );
	$this->cache['MANUFACTURERS'][$mID] = $result;
	$this->performance['QUERIES']['MANUFACTURERS'][] = $rsql;
	$this->transph_header_tag($result);
	$return = $mName;
      break;
    }
    return $return;
  }


  /**
   * \brief Function to get the topic name. Use evaluated cache, per page cache, or database query in that order of precedent.
   * @author Bobby Easland
   * @version 1.1
   * @param $tID integer
   * @return string
  */
  private function get_topic_name($tID){
    switch(true){
      case (_test_bool(self::$USE_SEO_CACHE_GLOBAL) && isset($this->cache['TOPICS'][$tID]) ):
	$this->performance['CACHE_QUERY_SAVINGS']++;
	$result = $this->cache['TOPICS'][$tID];
	$return = (isset($result['tName'])? $this->strip($result['tName']): '');
	$this->transph_header_tag($result);
	break;
      default:
	$this->performance['NUMBER_QUERIES']++;
	$sql = "select content_title as tName from " . TABLE_CONTENT . " c, "  . TABLE_CONTENT_DESCRIPTION .  " cd where c.content_status = '1' and c.content_id=cd.content_id and c.content_id='".(int)$tID."' and cd.language_id='".(int)$this->languages_id."' Limit 1";
	$rsql=$this->DB->query($sql);
// 	else $sql = "select head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='content' and  ft.page_id = '" . (int)$tID. "' and ftd.language_id = '" . (int)$this->languages_id. "' Limit 1";
	$result = $rsql->fetchAssoc(); //$this->DB->FetchArray( $this->DB->query( $sql ) );
	$tName = $this->strip( $result['tName'] );
	$this->cache['ARTICLES'][$tID] = $result;
	$this->performance['QUERIES']['TOPICS'][] = $sql;
	$this->transph_header_tag($result);
	$return = $tName;
	break;
    }
    return $return;
  }



  /**
   * @version 1
   * @author oscim <mail oscim@oscss.org> <www http://www.oscim.fr>
   * @return array
  */
  private function generate_topics_cache(){
    $res=$this->DB->query( "select ft.page_id as ID, head_title_tag as tName, head_desc_tag, head_keywords_tag from " . TABLE_FULL_TAG . " ft, "  . TABLE_FULL_TAG_DESCRIPTION .  " ftd where ft.tag_id=ftd.tag_id and ft.page_type='content' and ftd.language_id = '" . (int)$this->languages_id. "'" );
    while ($result =$res->fetchAssoc()){
      $this->get_cache_id($result['ID'],$result,'TOPICS');
    }
  }

  /**
   * \brief Ajout et/ou recup le cache
   * @author oscim <mail oscim@oscss.org> <www http://www.oscim.fr>
   * @param $id
   * @param $result
   * @param $type
   * @return array
   */
  private function get_cache_id($id,$result,$type){
    if(!isset($this->cache[$type][$id])) return $this->cache[$type][$id]= $result;
    elseif(is_array($result)) return $this->cache[$type][$id]= array_merge($this->cache[$type][$id], $result);
    else return $this->cache[$type][$id];
  }


  /**
   * \brief Function to retrieve full cPath from category ID
   * @author Bobby Easland
   * @version 1.1
   * @param $cID mixed Could contain cPath or single category_id
   * @param $original integer Single category_id passed back by reference
   * @return string Full cPath string
   */
  private function get_full_cPath($cID, &$original){
    if ( is_numeric(substr($cID,strpos($cID, '_'))) ){
      $temp = @explode('_', $cID);
      $original = $temp[sizeof($temp)-1];
      return $cID;
    } else {
      $c = array();
      $this->GetParentCategories($c, $cID);
      $c = array_reverse($c);
      $c[] = $cID;
      $original = $cID;
      $cID = sizeof($c) > 1 ? implode('_', $c) : $cID;
      return $cID;
    }
  }

  /**
   * \brief Recursion function to retrieve parent categories from category ID
   * @author Bobby Easland
   * @version 1.0
   * @param $categories mixed  Passed by reference
   * @param $categories_id integer
  */
  private function GetParentCategories(&$categories, $categories_id) {
    if(_test_bool(self::$USE_SEO_CACHE_GLOBAL) && isset($this->cache['GET_CATEGORIES_P'][$categories_id])) {
      $categories[sizeof($categories)] =$this->cache['GET_CATEGORIES_P'][$categories_id];
    } else {
      $sql = "SELECT parent_id FROM " . TABLE_CATEGORIES . " WHERE categories_id='" . (int)$categories_id . "'";
      $parent_categories_query = $this->DB->query($sql);
      while ($parent_categories = $parent_categories_query->fetchAssoc()) {
	$this->cache['GET_CATEGORIES_P'][$categories_id]=$parent_categories['parent_id'];
	if ($parent_categories['parent_id'] == 0) return true;
	$categories[sizeof($categories)] = $parent_categories['parent_id'];
	if ($parent_categories['parent_id'] != $categories_id)  $this->GetParentCategories($categories, $parent_categories['parent_id']);
      }
    }
  }

  /**
   * \brief Function to check if a value is NULL
   * @author Bobby Easland as abstracted from osCommerce-MS2.2
   * @version 1.0
   * @param $value mixed
   * @return boolean
  */
  private function not_null($value) {
    if (is_array($value)) {
      if (sizeof($value) > 0)   return true;
      else   return false;
    } else {
      if (($value != '') && (strtolower($value) != 'null') && (strlen(trim($value)) > 0)) return true;
      else  return false;
    }
  }

  /**
   * \brief Function to check if the products_id contains an attribute
   * @author Bobby Easland
   * @param $pID integer
   * @return boolean
  */
  private function is_attribute_string($pID){
    if ( is_numeric(strpos($pID, '{')) )return true;
    else return false;
  }

  /**
   * \brief Function to check if the params contains a products_id
   * @author Bobby Easland
   * @param $params string
   * @return boolean
  */
  private function is_product_string($params){
    if ( is_numeric(strpos('products_id', $params)) ) return true;
    else  return false;
  }

  /**
   * \brief Function to check if cPath is in the parameter string
   * @author Bobby Easland
   * @param $params string
   * @return boolean
  */
  private function is_cPath_string($params){
    if ( preg_match('#Path#i', $params) )return true;
    else return false;
  }

  /**
   * \brief Function used to output class profile
   * @author Bobby Easland
   * @version 1.0
  */
  private function profile(){
    $this->calculate_performance();
    $this->PrintArray($this->attributes, 'Class Attributes');
    $this->PrintArray($this->cache, 'Cached Data');
  }

  /**
   * \brief Function used to calculate and output the performance metrics of the class
   * @author Bobby Easland
   * @return mixed Output of performance data wrapped in HTML pre tags
  */
  private function calculate_performance(){
    foreach ($this->cache as $type) $this->performance['TOTAL_CACHED_PER_PAGE_RECORDS'] += sizeof($type);
    $this->performance['TIME_PER_URL'] = $this->performance['TOTAL_TIME'] / $this->performance['NUMBER_URLS_GENERATED'];
    return $this->PrintArray($this->performance, 'Performance Data');
  }

  /**
   * \brief Function to strip the string of punctuation and white space
   * @author Bobby Easland
   * @param $string string
   * @return string Stripped text. Removes all non-alphanumeric characters.
  */
  private function strip($string){
    if ( is_array($this->attributes['SEO_CHAR_CONVERT_SET']) ) $string = strtr($string, $this->attributes['SEO_CHAR_CONVERT_SET']);
    if(_test_bool($this->attributes['SEO_REMOVE_ALL_SPEC_CHARS'])){
      $pattern = $this->attributes['SEO_REMOVE_ALL_SPEC_CHARS'] == 'true'
				      ?	"([^[:alnum:]])+"
				      :	"([[:punct:]])+";
      $anchor = preg_replace('#'.$pattern.'#', '-', strtolower($string));
      $pattern = "([[:space:]]|[[:blank:]])+";
      $anchor = preg_replace('#'.$pattern.'#', '-', $anchor);
    }
    else $anchor=$string;
    return $this->short_name($anchor); // return the short filtered name
  }

  /**
   * \brief Function to expand the SEO_CONVERT_SET group
   * @author Bobby Easland
   * @param $set string
   * @return mixed
  */
  private function expand($set){
    if ( $this->not_null($set) ){
      if ( $data = @explode(',', $set) ){
	foreach ( $data as $index => $valuepair){
	  $p = @explode('=>', $valuepair);
	  $container[trim($p[0])] = trim($p[1]);
	}
	return $container;
      }
      else return 'false';
    }
    else return 'false';
  }

  /**
   * \brief Function to return the short word filtered string
   * @author Bobby Easland
   * @param $str string
   * @param $limit integer
   * @return string Short word filtered
  */
  private function short_name($str, $limit=3){
    $container=array();
    if ( $this->attributes['SEO_URLS_FILTER_SHORT_WORDS'] != 'false' ) $limit = (int)$this->attributes['SEO_URLS_FILTER_SHORT_WORDS'];
    $foo = @explode('-', $str);
    foreach($foo as $index => $value){
      switch (true){
	case ( strlen($value) <= $limit ): continue;
	default: $container[] = $value; break;
      }
    }
    $container = ( sizeof($container) > 1 ? implode('-', $container) : $str );
    return $container;
  }

  /**
   * \brief Function to implode an associative array
   * @author Bobby Easland
   * @param $array array  Associative data array
   * @param $inner_glue string
   * @param $outer_glue string
   * @return string
  */
  private function implode_assoc($array, $inner_glue='=', $outer_glue='&') {
    $output = array();
    foreach( $array as $key => $item )
      if ( $this->not_null($key) && $this->not_null($item) ) $output[] = $key . $inner_glue . $item;

    return @implode($outer_glue, $output);
  }

  /**
   * \brief Function to start time for performance metric
   * @author Bobby Easland
   * @param $start_time float
  */
  private function start(&$start_time){
    $start_time = explode(' ', microtime());
  }

  /**
   * \brief Function to stop time for performance metric
   * @author Bobby Easland
   * @param $start float
   * @param $time float  NOTE: passed by reference
  */
  private function stop($start, &$time){
    $end = explode(' ', microtime());
    $time = number_format( array_sum($end) - array_sum($start), 8, '.', '' );
  }

  /**
   * \brief Function to translate a string
   * @author Bobby Easland
   * @param $data string  String to be translated
   * @param $parse array  Array of tarnslation variables
   * @return string
  */
  private function parse_input_field_data($data, $parse) {
    return strtr(trim($data), $parse);
  }

  /**
   * \brief Function to output a translated or sanitized string
   * @author Bobby Easland
   * @param $string string  String to be output
   * @param $translate mixed  Array of translation characters
   * @param $protected boolean  Switch for htemlspecialchars processing
   * @return string
  */
  private function output_string($string, $translate = false, $protected = false) {
    if ($protected == true)  return htmlspecialchars($string);
    else {
      if ($translate == false) return $this->parse_input_field_data($string, array('"' => '&quot;'));
      else   return $this->parse_input_field_data($string, $translate);
    }
  }

  /**
   * \brief Function to return the session ID
    @author Bobby Easland
    @param $sessid string
    @return string
  */
  private function SessionID($sessid = '') {
    if (!empty($sessid))  return session_id($sessid);
    else  return session_id();
  }

  /**
   * \brief Function to return the session name
    @author Bobby Easland
    @param $name string
    @return string
  */
  private function SessionName($name = '') {
    if (!empty($name))  return session_name($name);
    else  return session_name();
  }


  /**
   * \brief Function to save the cache to database
    @author Bobby Easland
    @param $name string  Cache name
    @param $value mixed  Can be array, string, PHP code, or just about anything
    @param $method string  RETURN, ARRAY, EVAL
    @param $gzip integer  Enables compression
    @param $global integer  Sets whether cache record is global is scope
    @param $expires string  Sets the expiration
  */
  private function save_cache($name, $value, $method='RETURN', $gzip=1, $global=0, $expires = '30/days'){
    $expires = $this->convert_time($expires);
    if ($method == 'ARRAY' ) $value = serialize($value);
    $value = ( $gzip === 1 ? base64_encode(gzdeflate($value, 1)) : addslashes($value) );
    $sql_data_array = array('cache_id' => md5($name),
			    'cache_language_id' => (int)$this->languages_id,
			    'cache_name' => $name,
			    'cache_data' => $value,
			    'cache_global' => (int)$global,
			    'cache_gzip' => (int)$gzip,
			    'cache_method' => $method,
			    'cache_date' => date("Y-m-d H:i:s"),
			    'cache_expires' => $expires
			    );
    $this->is_cached($name, $is_cached, $is_expired);
    $cache_check = ( $is_cached ? 'true' : 'false' );
    switch ( $cache_check ) {
      case 'true':
	$this->DB->DBPerform(TABLE_SEO_CACHE, $sql_data_array, 'update', "cache_id='".md5($name)."'");
      break;
      case 'false':
	$this->DB->DBPerform(TABLE_SEO_CACHE, $sql_data_array, 'insert');
      break;
      default:
    }
    # unset the variables...clean as we go
    unset($value, $expires, $sql_data_array);
  }

  /**
   * \brief Function to get cache entry
    @author Bobby Easland
    @param $name string
    @param $local_memory boolean
    @return mixed
  */
  private function get_cache($name = 'GLOBAL', $local_memory = false){
    $select_list = 'cache_id, cache_language_id, cache_name, cache_data, cache_global, cache_gzip, cache_method, cache_date, cache_expires';
    $global = ( $name == 'GLOBAL' ? true : false ); // was GLOBAL passed or is using the default?
    switch($name){
      case 'GLOBAL':
	$this->cache_query = $this->DB->query("SELECT ".$select_list." FROM ".TABLE_SEO_CACHE." WHERE cache_language_id='".(int)$this->languages_id."' AND cache_global='1'");
      break;
      default:
	$this->cache_query = $this->DB->query("SELECT ".$select_list." FROM ".TABLE_SEO_CACHE." WHERE cache_id='".md5($name)."' AND cache_language_id='".(int)$this->languages_id."'");
      break;
    }
    $num_rows = $this->cache_query->__get('numRows');
    if ( $num_rows ){
      $container = array();
      while($cache = $this->cache_query->fetchAssoc()){
	$cache_name = $cache['cache_name'];
	if ( $cache['cache_expires'] > date("Y-m-d H:i:s") ) {
	  $cache_data = ( $cache['cache_gzip'] == 1 ? gzinflate(base64_decode($cache['cache_data'])) : stripslashes($cache['cache_data']) );
	  switch($cache['cache_method']){
	    case 'EVAL': // must be PHP code
	      eval("$cache_data");
	    break;
	    case 'ARRAY':
	      $cache_data = unserialize($cache_data);
	    case 'RETURN':
	    default:
	  }
	  if ($global) $container['GLOBAL'][$cache_name] = $cache_data;
	  else $container[$cache_name] = $cache_data; // not global
	} else { // cache is expired
	  if ($global) $container['GLOBAL'][$cache_name] = false;
	  else $container[$cache_name] = false;
	}
	if (/* $this->keep_in_memory ||*/ $local_memory ) {
	  if ($global) $this->data['GLOBAL'][$cache_name] = $container['GLOBAL'][$cache_name];
	  else $this->data[$cache_name] = $container[$cache_name];
	}
      }
      unset($cache_data);
      $this->DB->Free($this->cache_query);
      switch (true) {
	case ($num_rows == 1):
	  if ($global){
	    if ($container['GLOBAL'][$cache_name] == false || !isset($container['GLOBAL'][$cache_name])) return false;
	    else return $container['GLOBAL'][$cache_name];
	  } else { // not global
	    if ($container[$cache_name] == false || !isset($container[$cache_name])) return false;
	    else return $container[$cache_name];
	  }
	case ($num_rows > 1):
	default:
	  return $container;
	break;
      }
    } else {
      return false;
    }
  }

  /**
   * \brief Function to get cache from memory
    @author Bobby Easland
    @param $name string
    @param $method string
    @return mixed
  */
  private function get_cache_memory($name, $method = 'RETURN'){
    $data = ( isset($this->data['GLOBAL'][$name]) ? $this->data['GLOBAL'][$name] : $this->data[$name] );
    if ( isset($data) && !empty($data) && $data != false ){
      switch($method){
	case 'EVAL': // data must be PHP
	  eval("$data");
	  return true;
	break;
	case 'ARRAY':
	case 'RETURN':
	default:
	  return $data;
      }
    }
    else  return false;
  }

  /**
   * \brief Function to perform basic garbage collection for database cache system
    @author Bobby Easland
  */
  private function cache_gc(){
    $this->DB->query("DELETE FROM ". TABLE_SEO_CACHE." WHERE cache_expires <= '" . date("Y-m-d H:i:s") . "'" );
  }

  /**
   * \brief Function to convert time for cache methods
    @author Bobby Easland
    @param $expires string
    @return string
  */
  private function convert_time($expires){ //expires date interval must be spelled out and NOT abbreviated !!
    $expires = explode('/', $expires);
    switch( strtolower($expires[1]) ){
      case 'seconds':
	$expires = mktime( date("H"), date("i"), date("s")+(int)$expires[0], date("m"), date("d"), date("Y") );
      break;
      case 'minutes':
	$expires = mktime( date("H"), date("i")+(int)$expires[0], date("s"), date("m"), date("d"), date("Y") );
      break;
      case 'hours':
	$expires = mktime( date("H")+(int)$expires[0], date("i"), date("s"), date("m"), date("d"), date("Y") );
      break;
      case 'days':
	$expires = mktime( date("H"), date("i"), date("s"), date("m"), date("d")+(int)$expires[0], date("Y") );
      break;
      case 'months':
	$expires = mktime( date("H"), date("i"), date("s"), date("m")+(int)$expires[0], date("d"), date("Y") );
      break;
      case 'years':
	$expires = mktime( date("H"), date("i"), date("s"), date("m"), date("d"), date("Y")+(int)$expires[0] );
      break;
      default: // if something fudged up then default to 1 month
	$expires = mktime( date("H"), date("i"), date("s"), date("m")+1, date("d"), date("Y") );
    }
    return date("Y-m-d H:i:s", $expires);
  }

  /**
   * \brief Function to check if the cache is in the database and expired
    @author Bobby Easland
    @param $name string
    @param $is_cached boolean  NOTE: passed by reference
    @param $is_expired boolean  NOTE: passed by reference
  */
  private function is_cached($name, &$is_cached, &$is_expired){ // NOTE: $is_cached and $is_expired is passed by reference !!
    $this->cache_query = $this->DB->query("SELECT cache_expires FROM ". TABLE_SEO_CACHE." WHERE cache_id='".md5($name)."' AND cache_language_id='".(int)$this->languages_id."' LIMIT 1");
    $is_cached = ( $this->cache_query->__get('NumRows') > 0 ? true : false );
    if ($is_cached){
      $check = $this->cache_query->fetchAssoc($this->cache_query);
      $is_expired = ( $check['cache_expires'] <= date("Y-m-d H:i:s") ? true : false );
      unset($check);
    }
    $this->DB->Free($this->cache_query);
  }

  /**
   * \brief Function to initialize the redirect logic
    @author Bobby Easland
  */
  private function check_redirect(){
    $this->need_redirect = false;
    $this->path_info = is_numeric(strpos(ltrim(getenv('PATH_INFO'), '/') , '/')) ? ltrim(getenv('PATH_INFO'), '/') : NULL;
    $this->uri = ltrim( basename($_SERVER['REQUEST_URI']), '/' );
    $this->real_uri = ltrim( basename($_SERVER['SCRIPT_NAME']) . '?' . $_SERVER['QUERY_STRING'], '/' );
    $this->uri_parsed = $this->not_null( $this->path_info )
			? parse_url(basename($_SERVER['SCRIPT_NAME']) . '?' . $this->parse_path($this->path_info) )
			: parse_url(basename($_SERVER['REQUEST_URI']));
    $this->attributes['SEO_REDIRECT']['PATH_INFO'] = $this->path_info;
    $this->attributes['SEO_REDIRECT']['URI'] = $this->uri;
    $this->attributes['SEO_REDIRECT']['REAL_URI'] = $this->real_uri;
    $this->attributes['SEO_REDIRECT']['URI_PARSED'] = $this->uri_parsed;
    $this->need_redirect();
    $this->check_seo_page();

    if ( $this->need_redirect && $this->is_seopage &&  self::$USE_SEO_REDIRECT == 'true') $this->do_redirect();
  }

  /**
   * \brief Function to check if the URL needs to be redirected
    @author Bobby Easland
  */
  private function need_redirect(){
    foreach( $this->reg_anchors as $param => $value){ $pattern[] = $param; }
    switch(true){
      case ($this->is_attribute_string($this->uri)): $this->need_redirect = false; break;
      case ($this->uri != $this->real_uri && !$this->not_null($this->path_info)): $this->need_redirect = false; break;
      case (is_numeric(strpos($this->uri, '.htm'))): $this->need_redirect = false; break;
      case (@preg_match("/(".@implode('|', $pattern).")/i", $this->uri)): $this->need_redirect = true; break;
      case (@preg_match("/(".@implode('|', $pattern).")/i", $this->path_info)): $this->need_redirect = true; break;
      default:
    }
    $this->attributes['SEO_REDIRECT']['NEED_REDIRECT'] = $this->need_redirect ? 'true' : 'false';
  }

  /**
   * \brief Function to check if it's a valid redirect page
    @author Bobby Easland
  */
  private function check_seo_page(){
    switch (true){
      case (isset($this->uri_parsed['path']) && in_array($this->uri_parsed['path'], $this->attributes['SEO_PAGES'])): $this->is_seopage = true; break;
      default: $this->is_seopage = false; break;
    }
    $this->attributes['SEO_REDIRECT']['IS_SEOPAGE'] = $this->is_seopage ? 'true' : 'false';
  }

  /**
   * \brief Function to parse the path for old SEF URLs
    @author Bobby Easland
    @param $path_info string
    @return array
  */
  private function parse_path($path_info){
    $tmp = @explode('/', $path_info);
    if ( sizeof($tmp) > 2 ){
      $container = array();
      for ($i=0, $n=sizeof($tmp); $i<$n; $i++) {
	$container[] = $tmp[$i] . '=' . $tmp[$i+1];
	$i++;
      }
      return @implode('&amp;', $container);
    } else {
      return @implode('=', $tmp);
    }
  }

  /**
   * \brief Function to perform redirect
    @author Bobby Easland
  */
  private function do_redirect(){
    $rs=str_replace('&amp;','&',$this->uri_parsed['query']);
    $p = @explode('&', $rs);
    foreach( $p as $index => $value ){
      $tmp = @explode('=', $value);
      switch($tmp[0]){
	case 'products_id':
	  if ( $this->is_attribute_string($tmp[1]) ){
	    $pieces = @explode('{', $tmp[1]);
	    $params[] = $tmp[0] . '=' . $pieces[0];
	  } else {
	    $params[] = $tmp[0] . '=' . $tmp[1];
	  }
	  break;
	case '_ID':
	  $page_tmp=$tmp[1];
	break;
	default:
	  $params[] = $tmp[0].'='.$tmp[1];
	  break;
      }
    }
    switch($this->uri_parsed['path']){
      case 'content.php';
	if(isset($page_tmp))  $this->uri_parsed['path'] = $page_tmp;
      break;
    }
    $params = ( sizeof($params) > 1 ? implode('&amp;', $params) : $params[0] );
    $url = $this->href_link($this->uri_parsed['path'], $params, 'NONSSL', false);

    switch(true){
      case (_cst_bool(self::USE_SEO_REDIRECT_DEBUG)):
	$this->attributes['SEO_REDIRECT']['REDIRECT_URL'] = $url;
      break;
      case (_cst_bool( self::$USE_SEO_REDIRECT)):
	header("HTTP/1.0 301 Moved Permanently");
	header("Location: $url"); // redirect...bye bye
      break;
      default:
	$this->attributes['SEO_REDIRECT']['REDIRECT_URL'] = $url;
      break;
    }
  }
}
?>