PHP Code:
<?php
/**
* Simple SEO URL
* $Author: yellow1912 $
* $Rev: 257 $
* @license http://www.zen-cart.com/license/2_0.txt GNU Public License V2.0
*/
class simple_seo_url extends yclass {
var $cache_folder;
var $pc_cache_folder; // parameter caching, currently use for links contain products_id and cPath only to avoid database loading
var $categories_cache_folder;
var $products_cache_folder;
var $catalog_dir;
var $link;
var $extension;
var $category_identifier;
// these 2 vars store categories/products name in array so we dont have to query/retrieve them more than once per page load
var $categories_names = array();
var $products_names = array();
var $exclude_list;
var $products_handlers = array();
var $products_identifiers = array();
// this is a php4 constructor
function simple_seo_url(){
// This is here to support multiple product types shorthand links
// if you want to add support for your custom type, first you have to go to your database and check table products_types
$this->products_handlers = array('1'=> 'product_info',
'2'=> 'product_music_info',
'3'=> 'document_general_info',
'4'=> 'document_product_info',
'5'=> 'product_free_shipping_info',
'6'=> 'document_website_info'
);
$this->products_identifiers = array('1'=> SSU_ID_DELIMITER.'p'.SSU_ID_DELIMITER,
'2'=> SSU_ID_DELIMITER.'m'.SSU_ID_DELIMITER,
'3'=> SSU_ID_DELIMITER.'g'.SSU_ID_DELIMITER,
'4'=> SSU_ID_DELIMITER.'d'.SSU_ID_DELIMITER,
'5'=> SSU_ID_DELIMITER.'f'.SSU_ID_DELIMITER,
'6'=> SSU_ID_DELIMITER.'w'.SSU_ID_DELIMITER
);
// set some vars
$this->category_identifier = (((int)SSU_MAX_LEVEL > 0) ? SSU_ID_DELIMITER : '').'c'.SSU_ID_DELIMITER;
// the list of pages we want to exclude from using ssu links
$this->exclude_list = explode(',',SSU_EXCLUDE_LIST);
// attempt to check current protocol being used
// $request_type is a global var of ZC
global $request_type;
if($request_type == 'NONSSL'){
$this->catalog_dir = DIR_WS_CATALOG;
$this->link = HTTP_SERVER.DIR_WS_CATALOG;
}
else{
$this->catalog_dir = DIR_WS_HTTPS_CATALOG;
$this->link = HTTPS_SERVER.DIR_WS_HTTPS_CATALOG;
}
$this->cache_folder = DIR_FS_SQL_CACHE."/ssu/";
$this->pc_cache_folder = $this->cache_folder.'pc/';
$this->categories_cache_folder = $this->cache_folder.'categories/';
$this->products_cache_folder = $this->cache_folder.'products/';
$this->extension = SSU_FILE_EXTENSION;
$this->extension = trim($this->extension);
}
// this function will rebuild $_GET array
// it will also take care of redirection if needed
function parse_url(){
global $request_type;
$mr_regex = array($this->catalog_dir);
if(!empty($this->extension)) $mr_regex[] = '/\./'.$this->extension.'/';
// we want to remove $catalog_dir and extension if any
// we preg_replace because we want to limit the number of replacement, maybe there is a better way to to this?
$mr = trim(($this->catalog_dir=='/' && empty($this->extension)) ? getenv('REQUEST_URI') : preg_replace($mr_regex,'',getenv('REQUEST_URI'), 1),'/');
$mr_parts = explode('/', current(explode('?', str_replace(array('&','&','=','?'),'/',$mr))));
// here we check if this is a product info link by searching for category and product identifier.
if(strpos($mr_parts[0],$this->category_identifier) !== false){
if(($product_type = $this->_get_product_handler($mr_parts[1])) != false)
$_get['main_page'] = $product_type;
else
$_get['main_page'] = 'index';
}
elseif(($product_type = $this->_get_product_handler($mr_parts[0])) != false)
$_get['main_page'] = $product_type;
else{
$_get['main_page'] = isset($_GET['main_page'])? $_GET['main_page'] : $mr_parts[0];
unset($mr_parts[0]);
$mr_parts = array_values($mr_parts);
}
if($this->_check_exclude_list($_get['main_page'])) return;
// if the query string is there, lets rebuild the path and redirect our users
if(strpos($mr,'index.php?') !== false){
$params = str_replace('index.php?','', $mr);
$this->_redirect($this->ssu_link('',$params,$request_type));
}
$mr_parts_count = count($mr_parts);
// TODO: tidy up this, improve performance
for($i= 0; $i < $mr_parts_count; $i++){
// TODO: a more efficient way to do this?
if(strpos($mr_parts[$i],$this->category_identifier) !== false){
$_get['cPath'] = end(explode(SSU_ID_DELIMITER, $mr_parts[$i]));
}
elseif($this->_get_product_handler($mr_parts[$i]) !== false){
$_get['products_id'] = end(explode(SSU_ID_DELIMITER, $mr_parts[$i]));
}
elseif(isset($mr_parts[($i + 1)])){
$_get[$mr_parts[$i]] = $mr_parts[($i + 1)];
$i++;
}
else{
$_get[$mr_parts[$i]] = '';
$i++;
}
}
// if our current link contains the category or product name, we want to make sure the name is correct, otherwise we do a redirection
if(((strpos($mr, $this->category_identifier) !== false) || $this->_get_product_handler($mr) !== false)){
$params = '';
// here we will attempt to rebuild the link using $_get array, and see if it matches the current link
// we want to take out zenid however
$temp = $_get;
$page = '';
if(isset($temp[zen_session_name()])) unset($temp[zen_session_name()]);
if(isset($temp['main_page'])) {$page = $temp['main_page']; unset($temp['main_page']);}
foreach ($temp as $key => $value)
$params .= '&' . $key . '=' . $value;
$link = $this->ssu_params($page, $params, true);
if(strpos($mr,$link) === false)
$this->_redirect($this->link.(!empty($page) ? $page.'/' : '').$link);
}
// set $_GET
$_GET = $_get;
// rebuild $PHP_SELF which is used by ZC in several places
$GLOBALS['PHP_SELF'] = $this->catalog_dir.'index.php';//print_r($_get);die();
}
function _redirect($link){
if($link === false) $link = $this->link;
Header( "HTTP/1.1 301 Moved Permanently" );
Header( "Location: $link" );
exit;
}
// this function builds the ssu links
function ssu_link($page = '', $parameters = '', $connection = 'NONSSL', $add_session_id = true, $search_engine_safe = true, $static = false, $use_dir_ws_catalog = true){
global $request_type, $session_started, $http_domain, $https_domain;
// if this is anything other than index.php, dont ssu it
if(strpos($page, '.php') !== false && strpos($page, 'index.php') === false)
return false;
if($page=='index' && empty($parameters)) return false;
// this is for the way ZC builds ezpage links. $page is empty and $parameters contains main_page
// remember. non-static links always have index.php?main_page=
// so first we check if this is static
if(strpos($page, 'main_page=') !== false){
$parameters = $page;
}
// remove index.php? if exists
if(($index_start = strpos($parameters, 'index.php?')) !== false) $parameters = substr($parameters, $index_start+10);
if(($main_page_start = strpos($parameters, 'main_page=')) !== false){
$main_page_end = strpos($parameters.'&', '&', $main_page_start);
$page = substr($parameters, $main_page_start+10, $main_page_end-$main_page_start-10);
$parameters = (($main_page_start > 1) ? substr($parameters, 0, $main_page_start-1) : '' ).substr($parameters, $main_page_end);
}
if(empty($page))
return false;
if($this->_check_exclude_list($page)) return false;
if(($parameters = $this->ssu_params($page, $parameters)) === false)
return false;
$link = '';
// build the http://www.site.com
$link = HTTP_SERVER;
if ($connection == 'SSL' && ENABLE_SSL == 'true')
$link = HTTPS_SERVER ;
// attach the shop folder if any -> http://www.site.com/shop
if ($use_dir_ws_catalog) {
if ($connection == 'SSL' && ENABLE_SSL == 'true') {
$link .= DIR_WS_HTTPS_CATALOG;
} else {
$link .= DIR_WS_CATALOG;
}
}
$sid = '';
// Build session id
if ( ($add_session_id == true) && ($session_started == true) && (SESSION_FORCE_COOKIE_USE == 'False') ) {
if (defined('SID') && zen_not_null(SID)) {
$sid = SID;
} elseif ( ( ($request_type == 'NONSSL') && ($connection == 'SSL') && (ENABLE_SSL == 'true') ) || ( ($request_type == 'SSL') && ($connection == 'NONSSL') ) ) {
if ($http_domain != $https_domain) {
$sid = zen_session_name() . '=' . zen_session_id();
}
}
}
//$sid = str_replace('=','/',$sid);
// Any param? No? return page.extension
if(empty($parameters)){
if(empty($page))
return false;
elseif(empty($sid))
return $link.$page.(trim(SSU_FILE_EXTENSION)=='' ? '' : '.'.SSU_FILE_EXTENSION);
else
return $link.$page.'?'.$sid;
}
$link .= $page.(!empty($page) ? '/' : '').$parameters;
while (substr($link, -1) == '/') $link = substr($link, 0, -1);
// append sid
if(!empty($sid))
$link .= '?'.$sid;
return $link;
}
// this function takes the parameters in the query string and turns that to our nice looking link
function ssu_params(&$page, $parameters, $re_calculate_cpath= false){
$parameters = trim($parameters,' ?&');
$cachefile = $this->pc_cache_folder.md5($page.$parameters);
$params = '';
$use_cache = false;
$set_cache = false;
$is_product_info_page = in_array($page, $this->products_handlers);
$is_category_page = ($page == 'index' && (strpos($parameters,'cPath=') !==false));
if(!empty($parameters)){
// if this contains cPath or products_id lets attempt to use cache
if((strpos($parameters,'cPath=') !== false) || (strpos($parameters,'products_id=') !== false)){
$use_cache = true;
if (($params = @file_get_contents($cachefile)) === false)
$set_cache = true;
}
// process params if we dont use cache or need to reset cache
if(!$use_cache || $set_cache){
// No need for '?'
$parameters = str_replace('/', '%2f', $parameters);
$parameters = str_replace(array('&','&','=','?'),'/',$parameters);
// clean up
//$parameters = preg_replace('/\/\/+/', '/', $parameters);
$parameters = trim($parameters, '/');
$cPath='';
$products_id='';
// Appending category name and product name into the link
$parameters = explode('/',$parameters);
$param_counter = count($parameters);
// we want to make sure the order of params for product info and category pages
if($is_product_info_page)
$params = "%s/%p";
elseif($is_category_page)
$params = "%s";
for($i=0; $i < $param_counter; $i++){
if($parameters[$i]=='cPath'){
$cPath = $parameters[++$i];
if(!$is_product_info_page && !$is_category_page)
$params .= "/%s";
}
elseif($parameters[$i]=='products_id'){
$products_id = $parameters[++$i];
if(!$is_product_info_page && !$is_category_page)
$params .= "/%p";
}
elseif(!empty($parameters[$i]) && !empty($parameters[$i+1])){
$params .= "/".$parameters[$i]."/".$parameters[++$i];
}
else
$i++;
}
// dont trust the info passed to us, always rebuild the cPath in this case
if($is_product_info_page && !empty($products_id)){
$cPath = zen_get_product_path($products_id);
}
elseif(!empty($cPath) && $re_calculate_cpath && !empty($products_id)){
$cPath = zen_get_product_path($products_id);
}
if(!empty($cPath)){
$params = str_replace('%s', $this->get_category_name($cPath), $params);
}
if(!empty($products_id)){
$params = str_replace('%p', $this->get_product_name($products_id), $params);
}
}
}
if($is_product_info_page || $is_category_page)
$page ='';
$params = trim($params, '/');
if($set_cache) $this->_write_to_file($cachefile, trim($params,'/')); // we cache the whole link so that we dont have to recalculate it again
// TODO: change the way we check if the language is already in the link
$params .= (isset($_SESSION['languages_code']) && strpos($params, 'language/') === false && $_SESSION['languages_code'] != DEFAULT_LANGUAGE) ? '/language/' . $_SESSION['languages_code'] : '';
return $params;
}
// We store the name in files because we dont want to query database too many times (which will kill the server)
// This is how the files are named:
// for category: 'c' followed by category id
// for product : 'p' followed by product id
function get_category_name($cPath){
if((int)SSU_MAX_LEVEL <= 0)
return 'c'.SSU_ID_DELIMITER.$cPath;
if(empty($cPath)) return '';
if(isset($this->categories_names[$cPath])) return $this->categories_names[$cPath];
$file_name = $this->build_category_filename($cPath);
// lets attempt to retrieve from file first, shall we?
if(($content = @file_get_contents($this->categories_cache_folder.$file_name)) == false)
$content = $this->set_category_name($cPath);
$this->categories_names[$cPath] = $content;
return $content;
}
// this function retrieves categories names from db, save it to file, returns it
function set_category_name($cPath){
$category_ids = explode('_',$cPath);
$cat_counter = count($category_ids);
$i = $cat_counter-(int)SSU_MAX_LEVEL;
if($i < 0) $i = 0;
$result = '';
// this may not be the best way to build the category name, but we do this once per cPath only
while($i<=($cat_counter-1)){
$sql_query = 'SELECT categories_name FROM '.TABLE_CATEGORIES_DESCRIPTION.' WHERE categories_id ='.$category_ids[$i].' LIMIT 1';
$result .= SSU_NAME_DELIMITER.$this->_get_name_from_db($file_name,$sql_query,'categories_name');
$i++;
}
$result = trim($this->_parse_name($result), SSU_NAME_DELIMITER);
$result = $result.$this->category_identifier.$cPath;
// now append the id
$file_name = $this->build_category_filename($cPath);
// write to file EVEN if we get an empty content
$this->_write_to_file($this->categories_cache_folder.$file_name, $result);
return $result;
}
function build_category_filename($cPath){
if(!empty($cPath))
return "c$cPath";
else
return "";
}
function get_product_name($products_id){
$products_id = zen_get_prid($products_id);
if(empty($products_id)) return '';
if(isset($this->products_names[$products_id])) return $this->products_names[$products_id];
$file_name = $this->build_product_filename($products_id);
if(($content = @file_get_contents($this->products_cache_folder.$file_name)) == false)
$content = $this->set_product_name($products_id);
$this->products_names[$products_id] = $content;
return $content;
}
// this function retrieves categories names from db, save it to file, returns it
function set_product_name($products_id){
$file_name = $this->build_product_filename($products_id);
$sql_query = 'SELECT d.products_name, p.products_type FROM '.TABLE_PRODUCTS.' p, '.TABLE_PRODUCTS_DESCRIPTION.' d WHERE p.products_id ='.$products_id.' AND d.products_id= p.products_id LIMIT 1';
$result = $this->_get_name_from_db($file_name,$sql_query);
$result = $this->_parse_name($result->fields['products_name']).$this->products_identifiers[$result->fields['products_type']].$products_id;
// write to file EVEN if we get an empty content, that just means the productname is blank
$this->_write_to_file($this->products_cache_folder.$file_name, $result);
return $result;
}
function build_product_filename($products_id){
return "p$products_id";
}
// this function attempts to get the name of the current product/category name from database
function _get_name_from_db($file_name, $sql_query, $field_name = ''){
global $db;
$result = '';
$sql_result = $db->Execute($sql_query);
if($sql_result->RecordCount() > 0){
if(!empty($field_name))
$result = $sql_result->fields[$field_name];
else
$result = $sql_result;
}
return $result;
}
// this function parses the name we retrieve from database
function _parse_name($name){
// Remove short words first
if(($word_length = (int)SSU_MINIMUM_WORD_LENGTH - 1) > 0)
$name = preg_replace('/\b\w{1,'.$word_length.'}\b\s*/', '', $name); // TODO: improve this mechanism
// trim the sentence
if ((int)SSU_MAX_NAME_LENGTH > 0 && strlen($name) > (int)SSU_MAX_NAME_LENGTH){
preg_match('/(.{' . (int)SSU_MAX_NAME_LENGTH . '}.*?)\b/', $name, $matches);
$name = rtrim($matches[1]);
}
// we replace any non alpha numeric character by the name delimiter
$name = preg_replace("/[^a-zA-Z0-9s]/", SSU_NAME_DELIMITER, $name);
// remove excess SSU_NAME_DELIMITER
$name = preg_replace('/'.SSU_NAME_DELIMITER.SSU_NAME_DELIMITER.'+/', SSU_NAME_DELIMITER, $name);
// remove anything that looks like our identifiers in the name
$name = str_replace($this->category_identifier, '', $name);
foreach($this->products_identifiers as $products_identifier)
$name = str_replace($products_identifier, '', $name);
// remove trailing _
$name = strtolower(trim($name, SSU_NAME_DELIMITER));
return $name;
}
// this function writes the category/product name to the approriate file so we dont have to query database over and over
function _write_to_file($file_link, $content){
// write into file. Cant use file_put_contents since it's not in php4
if($handle = @fopen($file_link, "w")){
if (fwrite($handle, $content) === FALSE) {
// TODO: sound the alarm
}
fclose($handle);
}
else{
// TODO: sound the alarm here
}
}
// this function checks if the page is in the excluded list
function _check_exclude_list($page){
if(in_array($page, $this->exclude_list))
return true;
}
function _get_product_handler($string){
foreach($this->products_identifiers as $key => $value){
if(strpos($string, $value) !== false) return $this->products_handlers[$key];
}
return false;
}
}
Bookmarks