<?php
/**
 * sh404SEF - SEO extension for Joomla!
 *
 * @author      Yannick Gaultier
 * @copyright   (c) Yannick Gaultier 2014
 * @package     sh404SEF
 * @license     http://www.gnu.org/copyleft/gpl.html GNU/GPL
 * @version     4.4.4.1791
 * @date		2014-07-01
 */

// Security check to ensure this file is being included by a parent file.
if (!defined('_JEXEC'))
	die('Direct Access to this location is not allowed.');

jimport('joomla.filesystem.file');

defined('SH404SEF_BASE_CLASS_LOADED') or define('SH404SEF_BASE_CLASS_LOADED', 1);

// load a few constants
require_once JPATH_ROOT . '/administrator/components/com_sh404sef/defines.php';

// prevent timezone not set warnings to appear all over,
// especially for PHP 5.3.3+
$serverTimezone = @date_default_timezone_get();
@date_default_timezone_set($serverTimezone);

// include sub-libraries
include_once(sh404SEF_ADMIN_ABS_PATH . 'shSEFConfig.class.php');
include_once(sh404SEF_ADMIN_ABS_PATH . 'sh_Net_URL.class.php');

// set of utility functions

/**
 * Disable caching of Joomfish language selection module
 *
 * Caching would otherwise new SEF urls in non-default language to
 * be created.
 *
 */
function shDisableJFModuleCaching()
{
	ShlSystem_Log::debug('sh404sef', 'Using deprecated shDisableJFModuleCaching, removed as not applicable anymore');
	return;
}

// returns found languages, but will check request language ($_GET or $_POST)
// and use that over user lang if it exists
// returns a lnguage code : en, fr, sp
function shDecideRequestLanguage()
{
	$reqLang = JRequest::getVar('lang', '');
	if ($reqLang != '')
		$finalLang = $reqLang;
	else
		$finalLang = shDiscoverUserLanguage();
	return $finalLang;
}

/** The function finds the language which is to be used for the user/session
 *
 * It is possible to choose the language based on the client browsers configuration,
 * the activated language of the configuration and the language a user has choosen in
 * the past. The decision of this order is done in the JoomFish configuration.
 *
 * This is a modified copy of what's available in Joomfish system bot.
 * Returns a language code : en, fr, sp
 */

function shDiscoverUserLanguage()
{
	$shCookieLang = shGetCookieLanguage();
	$userLang = empty($shCookieLang) ? shGetParamUserLanguage() : $shCookieLang;
	return $userLang;
}

// returns language code (en, fr, sp after lookign up Joomfish params
// probably does not work with J 1.6
function shGetParamUserLanguage()
{
	$shortCode = Sh404sefFactory::getPageInfo()->currentLanguageShortTag;
	if (!shIsMultilingual())
		return $shortCode;

	$database = ShlDbHelper::getDb();
	// check if param query has previously been processed
	$determitLanguage = 1;
	$newVisitorAction = "browser";
	if ($newVisitorAction == "browser" && !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
	{
		// no language chooses - assume from browser configuration
		// language negotiation by Kochin Chang, June 16, 2004
		// retrieve active languages from database
		$active_lang = null;
		$activeLanguages = shGetActiveLanguages();
		if (count($activeLanguages) == 0)
		{
			return $shortCode;
		}
		foreach ($activeLanguages as $lang)
		{
			$active_lang[] = $lang->iso;
		}
		// figure out which language to use
		$browserLang = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
		foreach ($browserLang as $lang)
		{
			$shortLang = JString::substr($lang, 0, 2);
			if (in_array($lang, $active_lang))
			{
				$client_lang = $lang;
				break;
			}
			if (in_array($shortLang, $active_lang))
			{
				$client_lang = $shortLang;
				break;
			}
		}
		// if language is still blank then use first active language!
		if (empty($client_lang))
		{
			$client_lang = Sh404sefHelperLanguage::getDefaultLanguageSef();
		}
	}
	elseif ($newVisitorAction == "joomfish")
	{
		// This list is ordered already!
		$activeLanguages = shGetActiveLanguages();
		if (count($activeLanguages) == 0)
		{
			return $shortCode;
		}
		else
		{
			$client_lang = Sh404sefHelperLanguage::getDefaultLanguageSef();
		}

	}
	else
	{// otherwise default use site default language
		$activeLanguages = shGetActiveLanguages();
		if (count($activeLanguages) == 0)
		{
			return $shortCode;
		}
		foreach ($activeLanguages as $lang)
		{
			if ($lang->code == Sh404sefFactory::getPageInfo()->currentLanguageTag)
			{
				$client_lang = $lang->iso;
				break;
			}
		}
		// if language is still blank then use first active language!
		if ($client_lang == "")
		{
			$client_lang = Sh404sefHelperLanguage::getDefaultLanguageSef();
		}
	}
	return $client_lang;
}

function shGetCookieLanguage()
{
	$jfCookie = JRequest::getVar('jfcookie', null, 'COOKIE');
	if (isset($jfCookie["lang"]) && $jfCookie["lang"] != "")
	{
		$lang = $jfCookie["lang"];
	}
	else
	{
		$lang = '';
	}
	return $lang;
}

// 1.2.4.t 10/08/2007 12:17:37 return false if not multilingual
// deprecated
function shIsMultilingual()
{
	static $shIsMultiLingual = null;

	if (is_null($shIsMultiLingual))
	{
		// joomfish detection:
		$conf = JFactory::getConfig();
		$shIsMultiLingual = !is_null($conf->get('multilingual_support', null)) ? 'joomfish' : false;

		// falang
		if ($shIsMultiLingual === false)
		{
			if (class_exists('plgSystemFalangdriver'))
			{
				$falangPluginRecord = JPluginHelper::getPlugin('system', 'falangdriver');
				if (!empty($falangPluginRecord))
				{
					$dispatcher = ShlSystem_factory::dispatcher();
					$falangPlugin = new plgSystemFalangdriver($dispatcher, (array) ($falangPluginRecord));
					$shIsMultiLingual = $falangPlugin->isFalangDriverActive() ? 'falang' : false;
				}
			}
		}

		// joomla builtin
		if (empty($shIsMultiLingual))
		{
			jimport('joomla.language.helper');
			$languages = JLanguageHelper::getLanguages();
			if (count($languages) > 1)
			{
				$shIsMultiLingual = 'joomla';
			}
		}
	}
	return $shIsMultiLingual;

}

// 1.2.4.t 10/08/2007 12:17:37 return true if param is default language
function shIsDefaultLang($langName)
{
	return ($langName == Sh404sefHelperLanguage::getDefaultLanguageTag() || $langName == '*');
}

/**
 * Get list of front-end available langauges
 *
 * @return unknown
 */
function shGetFrontEndActiveLanguages()
{
	static $shLangs = null;

	if (is_null($shLangs))
	{
		$shLangs = array();
		jimport('joomla.language.helper');
		$languages = JLanguageHelper::getLanguages();
		if (!empty($languages))
		{
			foreach ($languages as $i => &$language)
			{
				// Do not display language without frontend UI
				if (!JLanguage::exists($language->lang_code))
				{
					unset($languages[$i]);
				}
			}
			foreach ($languages as $language)
			{
				$shLang = new StdClass();
				$shLang->iso = $language->sef;
				if (empty($shLang->iso))
				{
					$shLang->iso = substr($language->lang_code, 0, 2);
				}
				$shLang->code = $language->lang_code;
				$shLangs[] = $shLang;
			}
		}
	}

	return $shLangs;

}

// utility function to return list of available languages / isolate from JFish/Joomla compat issues
function shGetActiveLanguages()
{

	$mainframe = JFactory::getApplication();

	static $shActiveLanguages = null; // cache this, to reduce DB queries
	if (!is_null($shActiveLanguages))
		return $shActiveLanguages;

	$shKind = shIsMultilingual();
	switch ($shKind)
	{
		case 'joomfish':
			$languages = JoomFishManager::getActiveLanguages();
			if (!empty($languages))
			{
				foreach ($languages as $language)
				{
					$shLang = new stdClass();
					$shLang->code = $language->code;
					$shLang->iso = $language->shortcode;
					$shActiveLanguages[] = $shLang;
				}
			}
			else
				$shKind = '';
			break;
		case 'falang':
		case 'joomla':
			jimport('joomla.language.helper');
			$languages = JLanguageHelper::getLanguages();
			if (!empty($languages))
			{
				foreach ($languages as $language)
				{
					$shLang = new stdClass();
					$shLang->code = $language->lang_code;
					$shLang->iso = $language->sef;
					if (empty($shLang->iso))
					{
						$shLang->iso = substr($language->lang_code, 0, 2);
					}
					$shActiveLanguages[] = $shLang;
				}
			}
			else
				$shKind = '';
			break;
	}
	if (empty($shKind))
	{
		// not multilingual
		$shActiveLanguages = shGetFrontEndActiveLanguages();
	}
	return $shActiveLanguages;
}

function shAdjustToRewriteMode($url)
{
	return $url;
}

function shFinalizeURL($url)
{
	// V 1.2.4.s hack to workaround Virtuemart/SearchEngines issue with cookie check
	// V 1.2.4.t fixed bug, was checking for vmcchk instead of vmchk
	if (shIsSearchEngine() && (strpos($url, 'vmchk') !== false))
	{
		$url = str_replace('vmchk/', '', $url); // remove check,
		//cookie will be forced if user agent is searchengine
	}
	$url = str_replace('&amp;', '&', $url); // when Joomla wil turn that into &amp; we are sur we won't have &amp;amp;
	return $url;
}

// V 1.2.4.p compatibility function with SEFAdvance
function sefencode($string)
{
	return titleToLocation($string);
}

function titleToLocation(&$title)
{
	$sefConfig = Sh404sefFactory::getConfig();
	$title = JString::trim($title);
	$debug = 0;
	if ($debug)
		$t[] = $title;
	$shRep = $sefConfig->shGetReplacements();
	if (!empty($shRep))
	{
		foreach ($shRep as $from => $to)
		{
			$title = str_replace($from, $to, $title);
		}
	}
	if ($debug)
		$t[] = $title;
	$shStrip = $sefConfig->shGetStripCharList();
	if (!empty($shStrip))
		$title = str_replace($shStrip, '', $title);
	if ($debug)
		$t[] = $title;
	// remove spaces
	$title = ShlSystem_Strings::pr('/[\s]+/iu', $sefConfig->replacement, $title);
	if ($debug)
		$t[] = $title;
	$title = str_replace('\'', $sefConfig->replacement, $title);
	$title = str_replace('"', $sefConfig->replacement, $title);
	// V x strip # as it breaks anchor management
	$title = str_replace('#', $sefConfig->replacement, $title);
	// remove question marks
	$title = str_replace('?', $sefConfig->replacement, $title);
	if ($debug)
		$t[] = $title;
	$title = str_replace('\\', $sefConfig->replacement, $title);
	if ($debug)
		$t[] = $title;
	// remove duplicate replacement chars
	if (!empty($sefConfig->replacement)) // V x protect/allow empty
		$title = ShlSystem_Strings::pr('/' . preg_quote($sefConfig->replacement) . '{2,}/u', $sefConfig->replacement, $title);
	if ($debug)
		$t[] = $title;
	$title = JString::trim($title, str_replace('|', '', $sefConfig->friendlytrim)); // V 1.2.4.t add SEF URL trimming of user set characters
	$title = $sefConfig->LowerCase ? JString::strtolower($title) : $title; // V w 27/08/2007 13:11:48
	if ($debug)
		$t[] = $title;
	if ($debug && strpos($t[0], '\'') !== false)
	{
		var_dump($t);
		die();
	}
	return $title;
}

// V x utility 01/09/2007 22:18:55 function to remove mosmsg var from url
function shCleanUpMosMsg($string)
{
	return ShlSystem_Strings::pr('/(&|\?)mosmsg=[^&]*/i', '', $string);
}

// V x utility 01/09/2007 22:18:55 function to return mosmsg var from url
function shGetMosMsg($string)
{
	$matches = array();
	$result = preg_match('/(&|\?)mosmsg=[^&]*/i', $string, $matches);
	if (!empty($matches))
		return JString::trim($matches[0], '&?');
	else
		return '';
}

// V 1.2.4.q utility function to clean language and pagination info from url
function shCleanUpPag($string)
{
	$shTempString = preg_replace('/(&|\?)limit=[^&]*/i', '', $string);
	$shTempString = preg_replace('/(&|\?)limitstart=[^&]*/i', '', $shTempString);
	return $shTempString;
}

// V 1.2.4.t utility function to clean language from url
function shCleanUpLang($string)
{
	return preg_replace('/(&|\?)lang=[a-zA-Z]{2}/i', '', $string);
}

// V 1.2.4.q utility function to clean language and pagination info from url
function shCleanUpLangAndPag($string)
{
	$shTempString = shCleanUpLang($string);
	$shTempString = shCleanUpPag($shTempString);
	return $shTempString;
}

// V 1.2.4.t utility function to clean anchor from url
function shCleanUpAnchor($string)
{
	$bits = explode('#', $string);
	return $bits[0];
}

/**
 * @deprecated Will be removed in 4.x. No replacemend, has been empty shell for long time
 */
function shIncludeLanguageFile()
{
}

function shGETGarbageCollect()
{
	// V 1.2.4.m moved to main component from plugins
	// builds up a string using all remaining GET parameters, to be appended to the URL without any sef transformation
	// those variables passed litterally must be removed from $string as well, so that they are not stored in DB
	global $shGETVars;
	$sefConfig = Sh404sefFactory::getConfig();
	if (!$sefConfig->shAppendRemainingGETVars || empty($shGETVars))
		return '';
	$ret = '';
	ksort($shGETVars);
	foreach ($shGETVars as $param => $value)
	{
		if (is_array($value))
		{
			foreach ($value as $k => $v)
			{
				$ret .= '&' . $param . '[' . $k . ']=' . $v;
			}
		}
		else
		{
			$ret .= '&' . $param . '=' . $value;
		}

	}
	return $ret;
}

function shRebuildNonSefString($string)
{
	// V 1.2.4.m moved to main component from plugins
	// rebuild a non-sef string, removing all GET vars that were not turned into SEF
	// as we do not want to store them in DB

	global $shRebuildNonSef;
	$sefConfig = &Sh404sefFactory::getConfig();
	if (!$sefConfig->shAppendRemainingGETVars || empty($shRebuildNonSef))
		return $string;
	$shNewString = '';
	if (!empty($shRebuildNonSef))
	{
		foreach ($shRebuildNonSef as $param)
		{
			// need to sort, and still place option in first pos.
			if (strpos($param, 'sh404SEF_title=') !== false)
				$param = str_replace('sh404SEF_title=', 'title=', $param);
			$shNewString .= $param;
		}
		$ret = Sh404sefHelperUrl::sortUrl('index.php?' . JString::ltrim($shNewString, '&'));
	}
	return $ret;
}

function shRemoveFromGETVarsList($paramName)
{
	global $shGETVars, $shRebuildNonSef;

	$sefConfig = Sh404sefFactory::getConfig();
	if (!$sefConfig->shAppendRemainingGETVars)
		return null;
	if (!empty($paramName))
	{
		if (isset($shGETVars[$paramName]))
		{
			$shValue = $shGETVars[$paramName];
			if (is_array($shValue))
			{
				// array handling, fix provided by VinhCV
				foreach ($shValue as $value)
				{
					$shRebuildNonSef[] = '&' . $paramName . '[]=' . $value;
				}
			}
			else
			{
				$shRebuildNonSef[] = '&' . $paramName . '=' . $shValue;
			} // build up a non-sef string with the GET vars used to
			// build the SEF string. This string will be the one stored in db instead of
			// the full, original one
			unset($shGETVars[@$paramName]);
		}
	}
}

function shAddToGETVarsList($paramName, $paramValue)
{
	// V 1.2.4.m
	global $shGETVars, $shRebuildNonSef;
	if (empty($paramName))
		return;
	$shGETVars[$paramName] = $paramValue;
	// check and remove from $shRebuildNonSef, in case this param was previously added to the list, using shRemoveFromGETVarsList
	if (!empty($shRebuildNonSef))
	{
		$indexFound = -1;
		$index = -1;
		foreach ($shRebuildNonSef as $item)
		{
			$index++;
			if ($item == '&' . $paramName . '=' . $paramValue)
			{
				$indexFound = $index;
				break;
			}
		}
		if ($indexFound > -1)
		{
			unset($shRebuildNonSef[$indexFound]);
		}
	}
}

function shComputeItemidString($nonSefUrl, &$title, $shLangName)
{

	$sefConfig = &shRouter::shGetConfig();

	$shItemidString = '';

	$shHomePageFlag = shIsHomepage($nonSefUrl);

	if (!$shHomePageFlag)
	{
		// we may have found that this is homepage, so we msut return an empty string
		// do something about that Itemid thing
		if (!preg_match('/Itemid=[0-9]+/i', $nonSefUrl))
		{
			// if no Itemid in non-sef URL
			// V 1.2.4.t moved back here
			$shCurrentItemid = JRequest::getInt('Itemid');
			if ($sefConfig->shInsertGlobalItemidIfNone && !empty($shCurrentItemid))
			{
				$nonSefUrl .= '&Itemid=' . $shCurrentItemid;
				; // append current Itemid
				$Itemid = $shCurrentItemid;
				shAddToGETVarsList('Itemid', $Itemid); // V 1.2.4.m
			}

			$shItemidString = '';
			if ($sefConfig->shAlwaysInsertItemid && (!empty($Itemid) || !empty($shCurrentItemid)))
			{
				$shItemidString = JText::_('COM_SH404SEF_ALWAYS_INSERT_ITEMID_PREFIX') . $sefConfig->replacement
					. (empty($Itemid) ? $shCurrentItemid : $Itemid);
			}
		}
		else
		{ // if Itemid in non-sef URL
			$shItemidString = $sefConfig->shAlwaysInsertItemid
				? JText::_('COM_SH404SEF_ALWAYS_INSERT_ITEMID_PREFIX') . $sefConfig->replacement . Sh404sefHelperUrl::getUrlVar($nonSefUrl, 'Itemid')
				: '';
		}

	}

	return $shItemidString;
}

function shFinalizePlugin($string, $title, &$shAppendString, $shItemidString, $limit, $limitstart, $shLangName, $showall = null,
	$suppressPagination = false)
{
	// V 1.2.4.s
	global $shGETVars;
	if (empty($shItemidString))
	{
		$shItemidString = shComputeItemidString($string, $title, $shLangName);
	}
	if (!empty($shItemidString))
	{
		$title[] = $shItemidString; // V 1.2.4.m
	}
	// stitch back additional parameters, not sef-ified
	$shAppendString .= shGETGarbageCollect(); // add automatically all GET variables that had not been used already
	if (!empty($shAppendString))
		$shAppendString = '?' . JString::ltrim($shAppendString, '&');
	// don't add to $string, otherwise it will be stored in the DB
	return sef_404::sefGetLocation(shRebuildNonSefString($string), $title, null, (isset($limit) ? @$limit : null),
		(isset($limitstart) ? @$limitstart : null), (isset($shLangName) ? @$shLangName : null), (isset($showall) ? @$showall : null),
		$suppressPagination);
}

function shInitializePlugin($lang, &$shLangName, &$shLangIso, $option)
{

	$conf = JFactory::getConfig();
	$configDefaultLanguage = $conf->get('language');

	$shLangName = empty($lang) ? Sh404sefFactory::getPageInfo()->currentLanguageTag : Sh404sefHelperLanguage::getLangTagFromUrlCode($lang);
	$shLangIso = (shTranslateUrl($option, $shLangName))
		? (isset($lang) ? $lang : Sh404sefHelperLanguage::getUrlCodeFromTag(Sh404sefFactory::getPageInfo()->currentLanguageTag))
		: (isset($configDefaultLanguage) ? Sh404sefHelperLanguage::getUrlCodeFromTag($configDefaultLanguage)
			: Sh404sefHelperLanguage::getUrlCodeFromTag(Sh404sefFactory::getPageInfo()->currentLanguageTag));
	if (strpos($shLangIso, '_') !== false)
	{
		//11/08/2007 14:30:16 mambo compat
		$shTemp = explode('_', $shLangIso);
		$shLangIso = $shTemp[0];
	}

	// reset pageid creation : the plugin must turn it on by itself
	shMustCreatePageId('set', false);

	// added protection : do not SEF if component is not installed. Do not attempt to build SEF URL
	// if component is not installed, or else plugin may try to read from comp DB tables. This will cause DB table names
	// to be displayed
	return !sh404SEF_CHECK_COMP_IS_INSTALLED
		|| (sh404SEF_CHECK_COMP_IS_INSTALLED
			&& shFileExists(sh404SEF_ABS_PATH . 'components/' . $option . '/' . str_replace('com_', '', $option) . '.php'));
}

function shLoadPluginLanguage($pluginName, $language, $defaultString, $path = '')
{
	// V 1.2.4.m
	global $sh_LANG;

	// load the Language File
	$path = JString::rtrim($path, DIRECTORY_SEPARATOR);
	$path = JString::rtrim($path, '/') . '/';
	$path = $path == '/' ? sh404SEF_ADMIN_ABS_PATH . 'language/plugins/' : $path;
	if (shFileExists($path . $pluginName . '.php'))
	{
		include_once($path . $pluginName . '.php');
	}
	else
		JError::RaiseWarning(500, 'sh404SEF - missing language file for plugin ' . $pluginName . '.');

	if (!isset($sh_LANG[$language][$defaultString]))
		return 'en';
	else
		return $language;
}

function shInsertIsoCodeInUrl($compName, $shLang = null)
{
	// V 1.2.4.m

	$sefConfig = &Sh404sefFactory::getConfig();
	$shLang = empty($shLang) ? Sh404sefFactory::getPageInfo()->currentLanguageTag : $shLang; // V 1.2.4.q
	if (empty($compName) || !$sefConfig->shInsertLanguageCode // if no compname or global param is off
		|| $sefConfig->shLangInsertCodeList[$shLang] == 2 // set to not insertcode
		|| ($sefConfig->shLangInsertCodeList[$shLang] == 0 && Sh404sefHelperLanguage::getDefaultLanguageTag() == $shLang
			&& !Sh404sefHelperLanguage::getInsertLangCodeInDefaultLanguage()) // or set to default
	) // but this is default language
		return false;
	$compName = str_replace('com_', '', $compName);
	return !in_array($compName, $sefConfig->notInsertIsoCodeList);
}

function shTranslateUrl($compName, $shLang = null)
{
	// V 1.2.4.m  // V 1.2.4.q added $shLang param

	// temporarily disable ability to not translate
	// until 1.7+ multi-lingual options are sorted out
	return true;

	$sefConfig = &Sh404sefFactory::getConfig();

	$shLang = empty($shLang) ? Sh404sefFactory::getPageInfo()->currentLanguageTag : $shLang;
	if (empty($compName) || !$sefConfig->shTranslateURL || $sefConfig->shLangTranslateList[$shLang] == 2) // set to not translate
		return false;
	$compName = str_replace('com_', '', $compName);
	$result = !in_array($compName, $sefConfig->notTranslateURLList);
	return $result;
}

// V 1.2.4.q returns true if current page is home page.
function shIsCurrentPageHome()
{

	$currentPage = Sh404sefHelperUrl::sortUrl(
		ShlSystem_Strings::pr('/(&|\?)lang=[a-zA-Z]{2,3}/iu', '', empty($_SERVER['QUERY_STRING']) ? '' : $_SERVER['QUERY_STRING'])); // V 1.2.4.t
	$currentPage = JString::ltrim(str_replace('index.php', '', $currentPage), '/');
	$currentPage = JString::ltrim($currentPage, '?');
	$shHomePage = ShlSystem_Strings::pr('/(&|\?)lang=[a-zA-Z]{2,3}/iu', '', Sh404sefFactory::getPageInfo()->homeLink);
	$shHomePage = JString::ltrim(str_replace('index.php', '', $shHomePage), '/');
	$shHomePage = JString::ltrim($shHomePage, '?');
	return $currentPage == $shHomePage;
}

function shUrlEncode($path)
{
	$ret = $path;
	if (!empty($path))
	{
		$bits = explode('/', $path);
		$enc = array();
		if (count($bits))
		{
			foreach ($bits as $key => $value)
			{
				$enc[$key] = rawurlencode($value);
			}
			$ret = implode($enc, '/');
		}
	}
	return $ret;
}
function shUrlDecode($path)
{
	$ret = $path;
	if (!empty($path))
	{
		$bits = explode('/', $path);
		$dec = array();
		if (count($bits))
		{
			foreach ($bits as $key => $value)
			{
				$dec[$key] = rawurldecode($value);
			}
			$ret = implode($dec, '/');
		}
	}
	return $ret;
}

// returns default items per page from menu items params. menu item selected by its id taken from a URL
function shGetDefaultDisplayNumFromURL($url, $includeBlogLinks = false)
{

	$menuItemid = Sh404sefHelperUrl::getUrlVar($url, 'Itemid');
	return shGetDefaultDisplayNum($menuItemid, $url, $fromSession = true, $includeBlogLinks);
}

/**
 * Compared to shGetDefaultDisplayNum, this function only reads default
 * num items per page out of configuration and url requested, regardless of values
 * stored in session
 *
 * @param $url
 * @return unknown_type
 */
function shGetDefaultDisplayNumFromConfig($url, $includeBlogLinks = false)
{

	$menuItemid = Sh404sefHelperUrl::getUrlVar($url, 'Itemid');
	return shGetDefaultDisplayNum($menuItemid, $url, $fromSession = false, $includeBlogLinks);

}

// returns default items per page from menu items params. menu item selected by its id taken from a URL
function shGetDefaultDisplayNum($menuItemid, $url, $fromSession = false, $includeBlogLinks = false)
{

	$app = JFactory::getApplication();

	// default value is general configuration list length param
	$ret = $app->getCfg('list_limit', 10);

	// get elements of the url
	$option = Sh404sefHelperUrl::getUrlVar($url, 'option');
	$layout = Sh404sefHelperUrl::getUrlVar($url, 'layout');
	if (empty($layout))
	{
		$layout = 'default';
	}
	$view = Sh404sefHelperUrl::getUrlVar($url, 'view');

	// if there is a menu item, we can try read more params
	if (!empty($menuItemid))
	{

		// itemid, try read params from the menu item
		$menu = JFactory::getApplication()->getMenu();
		$menuItem = $menu->getItem($menuItemid); // load menu item from DB
		if (empty($menuItem))
			return $ret;
		// if none, default
		jimport('joomla.html.parameter');

		// Load the parameters. Merge Global and Menu Item params into new object
		$currentOption = JRequest::getCmd('option');
		$params = new JRegistry($menuItem->params); // get params from menu item
		if (!empty($currentOption))
		{
			$appParams = $app->getParams();
			$params->merge($appParams);
		}

		// layout = blog and frontpage
		if (($option == 'com_content' && $layout == 'blog') || ($option == 'com_content' && $view == 'featured'))
		{
			$num_leading_articles = $params->get('num_leading_articles');
			$num_intro_articles = $params->get('num_intro_articles');
			//adjust limit and listLimit for page calculation as blog views include
			//# of links in the limit value, while it should not be included for
			// page number calculation
			$num_links = $includeBlogLinks ? $params->get('num_links') : 0;

			$ret = $num_leading_articles + $num_intro_articles + $num_links; // calculate how many items on a page
			return $ret;
		}

		// elements with a display_num parameter
		$displayNum = intval($params->get('display_num'));
		$ret = !empty($displayNum) ? $displayNum : $ret;
	}

	if ($fromSession)
	{
		// now handle special cases
		if ($option == 'com_content' && $layout != 'blog' && ($view == 'category' || $view == 'section'))
		{
			$limit = $app->getUserStateFromRequest('com_content.sh.' . $view . '.' . $layout . '.limit', 'limit', null);
			if (!is_null($limit))
			{
				return $limit;
			}
		}

		if ($option == 'com_contact')
		{
			$limit = $app->getUserState($option . '.' . $view . '.limit');
			if (!is_null($limit))
			{
				return $limit;
			}
		}

		if ($option == 'com_weblinks')
		{
			$limit = $app->getUserState($option . '.limit');
			if (!is_null($limit))
			{
				return $limit;
			}
		}
	}

	// return calculated value
	return $ret;
}

function getSefUrlFromDatabase($url, &$sefString)
{

	try
	{
		$result = ShlDbHelper::selectObject('#__sh404sef_urls', array('oldurl', 'dateadd'), array('newurl' => $url));

		if (!empty($result))
		{
			$sefString = $result->oldurl;
			if (empty($result->oldurl))
			{
				return sh404SEF_URLTYPE_404;
			}
			return $result->dateadd == '0000-00-00' ? sh404SEF_URLTYPE_AUTO : sh404SEF_URLTYPE_CUSTOM;
		}

	}
	catch (Exception $e)
	{
		ShlSystem_Log::error('sh404sef', '%s::%s::%d: %s', __CLASS__, __METHOD__, __LINE__, $e->getMessage());
	}

	return sh404SEF_URLTYPE_NONE;
}

// V 1.2.4.t check both cache and DB
function shGetSefURLFromCacheOrDB($string, &$sefString)
{
	$sefConfig = Sh404sefFactory::getConfig();
	if (empty($string))
		return sh404SEF_URLTYPE_NONE;
	$sefString = '';
	$urlType = sh404SEF_URLTYPE_NONE;
	if ($sefConfig->shUseURLCache)
		$urlType = Sh404sefHelperCache::getSefUrlFromCache($string, $sefString);
	// Check if the url is already saved in the database.
	if ($urlType == sh404SEF_URLTYPE_NONE)
	{
		$urlType = getSefUrlFromDatabase($string, $sefString);
		if ($urlType == sh404SEF_URLTYPE_NONE || $urlType == sh404SEF_URLTYPE_404)
			return $urlType;
		else
		{
			if ($sefConfig->shUseURLCache)
			{
				Sh404sefHelperCache::addSefUrlToCache($string, $sefString, $urlType);
			}
		}
	}
	return $urlType;
}

// add URL to DB and cache. URL must no exists, this is insert, not update
function shAddSefUrlToDBAndCache($nonSefUrl, $sefString, $rank, $urlType)
{

	$db = ShlDbHelper::getDb();
	$sefString = JString::ltrim($sefString, '/'); // V 1.2.4.t just in case you forgot to remove leading slash
	switch ($urlType)
	{
		case sh404SEF_URLTYPE_AUTO:
			$dateAdd = '0000-00-00';
			break;
		case sh404SEF_URLTYPE_CUSTOM:
			$dateAdd = date("Y-m-d");
			break;
		case sh404SEF_URLTYPE_NONE:
			return null;
			break;
	}

	try
	{

		$query = '';
		if ($urlType == sh404SEF_URLTYPE_AUTO)
		{
			$result = ShlDbHelper::quoteQuery('select ??, ?? from ?? where ?? = ? and (?? = ? or ?? = ?)',
				array('id', 'newurl', '#__sh404sef_urls', 'oldurl', 'newurl', 'newurl'), array($sefString, '', addslashes(urldecode($nonSefUrl))))
				->loadObject();

			if (!empty($result))
			{
				// sef urls was found either as a 404 or as already existing, with also the same non-sef
				if ($result->newurl == $nonSefUrl)
				{
					// url already in db, nothing to do
					ShlSystem_Log::debug('sh404sef', 'url already in db, nothing to do');
					return true;
				}
				ShlDbHelper::update('#__sh404sef_urls', array('newurl' => addslashes(urldecode($nonSefUrl)), 'rank' => $rank, 'dateadd' => $dateAdd),
					array('oldurl' => $sefString));

			}
			else
			{
				// another option: sef exists, but with another non-sef: that's a duplicate
				// need to check that
				$result = ShlDbHelper::selectObject('#__sh404sef_urls', array('id', 'newurl', 'rank'),
					$db->quoteName('oldurl') . ' = ? and ' . $db->quoteName('newurl') . ' <> ?',
					array($sefString, addslashes(urldecode($nonSefUrl))), array('rank' => 'desc'));

				if (!empty($result))
				{
					// we found at least one identical SEF url, with another non-sef. Mark the new one as duplicate of the old one
					$rank = $result->rank + 1;
				}

				ShlDbHelper::insert('#__sh404sef_urls', array('oldurl' => $sefString, 'newurl' => $nonSefUrl, 'rank' => $rank, 'dateadd' => $dateAdd));

			}
		}

		// store new sef/non-sef pair in memory cache
		Sh404sefHelperCache::addSefUrlToCache($nonSefUrl, $sefString, $urlType);

		// create shURL : get a shURL model, and ask url creation
		$model = ShlMvcModel_Base::getInstance('pageids', 'Sh404sefModel');
		$model->createPageId($sefString, $nonSefUrl);

	}
	catch (Exception $e)
	{
		ShlSystem_Log::error('sh404sef', '%s::%s::%d: %s', __CLASS__, __METHOD__, __LINE__, $e->getMessage());
	}
}

/**
 * Returns true if current sef url being created can have a shURL
 * Can be set from within a plugin, otherwise default to false
 * Reset to false upon each creation of a new sef url in shInitializePlugin()
 *
 * @param unknown_type $action
 * @param unknown_type $value
 * @return unknown
 */
function shMustCreatePageId($action = 'get', $value = false)
{

	jimport('joomla.application.component.model');
	$model = ShlMvcModel_Base::getInstance('pageids', 'Sh404sefModel');
	$mustCreate = $model->mustCreatePageId($action, $value);

	return $mustCreate;
}

// V 1.2.4.t build up a string with a page number
function shBuildPageNumberString($pagenum)
{
	$sefConfig = Sh404sefFactory::getConfig();

	if ($sefConfig->pagetext && (false !== strpos($sefConfig->pagetext, '%s')))
	{
		return str_replace('%s', $pagenum, $sefConfig->pagetext);
	}
	else
	{
		return $pagenum;
	}
}

function shReadFile($shFileName, $asString = false)
{
	$ret = array();
	if (is_readable($shFileName))
	{
		$shFile = fOpen($shFileName, 'r');
		do
		{
			$shRead = fgets($shFile, 1024);
			if (!empty($shRead) && JString::substr($shRead, 0, 1) != '#')
				$ret[] = JString::trim(stripslashes($shRead));
		}
		while (!feof($shFile));
		fclose($shFile);
	}
	if ($asString)
		$ret = implode("\n", $ret);
	return $ret;
}

function shSaveFile($shFileName, $fileData)
{
	if (empty($shFileName))
		return;
	$fileIsThere = file_exists($shFileName);
	if (!$fileIsThere || ($fileIsThere && is_writable($shFileName)))
	{
		if (is_array($fileData))
		{
			$fileData = implode("\n", $fileData); //make sure we write a string
		}
		$fileData = empty($fileData) ? '' : $fileData;
		JFile::Write($shFileName, $fileData);
	}
}

/**
 * utility function to obtain iso code from language name
 * 
 * @deprecated Use Sh404sefHelperLanguage::getUrlCodeFromTag($langName) instead
 * @param string $langName
 * @return string
 */
function shGetIsoCodeFromName($langName)
{

	ShlSystem_Log::debug('sh404sef', 'shGetIsoCodeFromName is deprecated, use Sh404sefHelperLanguage::getUrlCodeFromTag($langName) instead');
	return Sh404sefHelperLanguage::getUrlCodeFromTag($langName);
}

/**
 * utility function to obtain language name from iso code
 *
 * @deprecated Use Sh404sefHelperLanguage::getLangTagFromUrlCode($langName) instead
 * @param string $langName
 * @return string
 */
function shGetNameFromIsoCode($langCode)
{

	ShlSystem_Log::debug('sh404sef', 'shGetNameFromIsoCode is deprecated, use Sh404sefHelperLanguage::getLangTagFromUrlCode() instead');
	return Sh404sefHelperLanguage::getLangTagFromUrlCode($langCode);
}

// returns prefix for $option component, as per user settings
function shGetComponentPrefix($option)
{

	if (empty($option))
		return '';
	$sefConfig = Sh404sefFactory::getConfig();
	$option = str_replace('com_', '', $option);
	$prefix = '';
	$prefix = empty($sefConfig->defaultComponentStringList[@$option]) ? '' : $sefConfig->defaultComponentStringList[@$option];
	return $prefix;
}

function shRedirect($url, $msg = '', $redirKind = '301', $msgType = 'message')
{

	$mainframe = JFactory::getApplication();
	$sefConfig = &Sh404sefFactory::getConfig();

	// specific filters
	if (class_exists('InputFilter'))
	{
		$iFilter = new InputFilter();
		$url = $iFilter->process($url);
		if (!empty($msg))
		{
			$msg = $iFilter->process($msg);
		}

		if ($iFilter->badAttributeValue(array('href', $url)))
		{
			$url = Sh404sefFactory::getPageInfo()->getDefaultFrontLiveSite();
		}
	}

	// If the message exists, enqueue it
	if (JString::trim($msg))
	{
		$mainframe->enqueueMessage($msg, $msgType);
	}

	// Persist messages if they exist
	$queue = $mainframe->getMessageQueue();
	if (count($queue))
	{
		$session = JFactory::getSession();
		$session->set('application.queue', $queue);
	}

	$document = JFactory::getDocument();
	@ob_end_clean(); // clear output buffer
	if (headers_sent())
	{
		echo '<html><head><meta http-equiv="content-type" content="text/html; charset=' . $document->getCharset()
			. '" /><script>document.location.href=\'' . $url . '\';</script></head><body></body></html>';
	}
	else
	{
		switch ($redirKind)
		{
			case '302':
				$redirHeader = 'HTTP/1.1 302 Moved Temporarily';
				break;
			case '303':
				$redirHeader = 'HTTP/1.1 303 See Other';
				break;
			default:
				$redirHeader = 'HTTP/1.1 301 Moved Permanently';
				break;
		}
		header('Cache-Control: no-cache'); // prevent Firefox5+ and IE9+ to consider this a cacheable redirect
		header($redirHeader);
		header('Location: ' . $url);
		header('Content-Type: text/html; charset=' . $document->getCharset());
	}
	$mainframe->close();
}

function shCloseLogFile()
{

	global $shLogger;
	if (!empty($shLogger))
	{
		$shLogger->log('Closing log file at shutdown' . "\n\n");
		if (!empty($shLogger->logFile))
			fClose($shLogger->logFile);
	}
}

function _log($text, $data = '')
{

	ShlSystem_Log::debug('sh404sef', '_log is deprecated, used shlSystem_Log instead');
}

// J 1.5 : will put unused vars in uri query
function shRebuildVars($appendString, &$uri)
{
	if (empty($uri))
		return;
	$string = empty($appendString) ? '' : JString::ltrim($appendString, '?');
	$uri->setQuery($string);
}

function shFileExists($fileName)
{
	static $files = array();

	$fileMD5 = md5($fileName);
	if (!isset($files[$fileMD5]))
	{
		$files[$fileMD5] = file_exists($fileName);
	}
	return $files[$fileMD5];
}

function shGetMenuItemSsl($id)
{

	if (empty($id))
	{
		return 'ignore';
	}
	$secure = 0;
	$app = JFactory::getApplication();
	$menu = $app->getMenu();
	if (!empty($menu))
	{
		$params = $menu->getParams($id);
		$secure = $params->get('secure');
	}
	switch ($secure)
	{
		case -1:
			$secure = 'no';
			break;
		case 1:
			$secure = 'yes';
			break;
		default:
			$secure = 'ignore';
	}

	return $secure;
}

function shGetMenuItemLanguage($id)
{

	if (empty($id))
	{
		return '';
	}
	$language = '';
	$app = JFactory::getApplication();
	$menu = $app->getMenu();
	if (!empty($menu))
	{
		$item = $menu->getItem($id);
		if (!empty($item))
		{
			$language = $item->language == '*' ? Sh404sefFactory::getPageInfo()->currentLanguageTag : $item->language;
		}
	}
	return $language;
}

function shSefRelToAbs($string, $shLanguageParam, &$uri, &$originalUri)
{

	global $_SEF_SPACE, $shGETVars, $shRebuildNonSef;

	ShlSystem_Log::debug('sh404sef', 'Entering shSefRelToAbs with ' . $string . ' | Lang = ' . $shLanguageParam);

	$mainframe = JFactory::getApplication();

	$pageInfo = Sh404sefFactory::getPageInfo();
	$sefConfig = Sh404sefFactory::getConfig();
	$app = JFactory::getApplication();

	// return unmodified anchors
	if (JString::substr($string, 0, 1) == '#')
	{
		// V 1.2.4.t
		return $string;
	}
	// Quick fix for shared SSL server : if https, switch to non sef
	$id = Sh404sefHelperUrl::getUrlVar($string, 'Itemid', $app->input->getInt('Itemid'));
	$secure = 'yes' == shGetMenuItemSsl($id);
	if ($secure && $sefConfig->shForceNonSefIfHttps)
	{
		ShlSystem_Log::debug('sh404sef', 'Returning shSefRelToAbs : Forced non sef if https');
		return shFinalizeURL($string);
	}

	$database = ShlDbHelper::getDb();

	$shOrigString = $string;
	$shMosMsg = shGetMosMsg($string); // V x 01/09/2007 22:45:52
	$string = shCleanUpMosMsg($string);// V x 01/09/2007 22:45:52

	// V x : removed shJoomfish module. Now we set $mosConfi_lang here
	$shOrigLang = $pageInfo->currentLanguageTag; // save current language
	$shLanguage = Sh404sefHelperUrl::getUrlLang($string); // target language in URl is always first choice
	// second choice is param
	if (empty($shLanguage))
	{
		$shLanguage = !empty($shLanguageParam) ? $shLanguageParam : $shLanguage;
	}
	// third choice is to read from menu, based on Itemid
	// but only for Joomla multilingual, this breaks
	// Joomfish/Falang, as current menu language may not be correct
	if (($pageInfo->isMultilingual === false || $pageInfo->isMultilingual == 'joomla') && empty($shLanguage) && !empty($id))
	{
		$shLanguage = shGetMenuItemLanguage($id);
	}
	if (empty($shLanguage))
	{
		$shLanguage = !empty($shLanguageParam) ? $shLanguageParam : $pageInfo->currentLanguageTag;
	}

	$pageInfo->setCurrentLanguage($shLanguage);
	ShlSystem_Log::debug('sh404sef', 'Language used : ' . $shLanguage);

	// V 1.2.4.t workaround for old links like option=compName instead of option=com_compName
	if (strpos(strtolower($string), 'option=login') === false && strpos(strtolower($string), 'option=logout') === false
		&& strpos(strtolower($string), 'option=&') === false && JString::substr(strtolower($string), -7) != 'option='
		&& strpos(strtolower($string), 'option=cookiecheck') === false && strpos(strtolower($string), 'option=') !== false
		&& strpos(strtolower($string), 'option=com_') === false)
	{
		$string = str_replace('option=', 'option=com_', $string);
	}
	// V 1.2.4.j string to be appended to URL, but not saved to DB
	$shAppendString = '';
	$shRebuildNonSef = array();
	$shComponentType = ''; // V w initialize var to avoid notices

	if ($pageInfo->homeLink)
	{
		// now check URL against our homepage, so as to always return / if homepage
		$v1 = JString::ltrim(str_replace($pageInfo->getDefaultFrontLiveSite(), '', $string), '/');
		// V 1.2.4.m : remove anchor if any
		$v2 = explode('#', $v1);
		$v1 = $v2[0];
		$shAnchor = isset($v2[1]) ? '#' . $v2[1] : '';
		$shSepString = (JString::substr($v1, -9) == 'index.php' || strpos($v1, '?') === false) ? '?' : '&';
		$shLangString = 'lang=' . Sh404sefHelperLanguage::getUrlCodeFromTag($shLanguage);
		if (!Sh404sefHelperUrl::getUrlVar($v1, 'lang'))
		{
			$v1 .= $shSepString . $shLangString;
		}
		$v1 = str_replace('&amp;', '&', Sh404sefHelperUrl::sortUrl($v1));
		// V 1.2.4.t check also without pagination info
		if (strpos($v1, 'limitstart=0') !== false)
		{
			// the page has limitstart=0
			$stringNoPag = shCleanUpPag($v1); // remove paging info to be sure this is not homepage
		}
		else
		{
			$stringNoPag = null;
		}
		if ($v1 == $pageInfo->homeLink || $v1 == $pageInfo->allLangHomeLink || $v1 == 'index.php?' . $shLangString
			|| $stringNoPag == $pageInfo->homeLink || $stringNoPag == $pageInfo->allLangHomeLink)
		{
			$shTemp = $v1 == $pageInfo->homeLink || shIsDefaultLang($shLanguage) ? '' : Sh404sefHelperLanguage::getUrlCodeFromTag($shLanguage) . '/';

			if (!empty($sefConfig->shForcedHomePage))
			{
				// V 1.2.4.t
				$shTmp = $shTemp . $shAnchor;
				$ret = shFinalizeURL($sefConfig->shForcedHomePage . (empty($shTmp) ? '' : '/' . $shTmp));
				if (empty($uri)) // if no URI, append remaining vars directly to the string
					$ret .= $shAppendString;
				else
					shRebuildVars($shAppendString, $uri);
				$pageInfo->setCurrentLanguage($shOrigLang);
				ShlSystem_Log::debug('sh404sef', 'Returning shSefRelToAbs 1 with ' . $ret);
				return $ret;
			}
			else
			{
				$shRewriteBit = shIsDefaultLang($shLanguage) ? '/' : $sefConfig->shRewriteStrings[$sefConfig->shRewriteMode];
				$ret = shFinalizeURL($pageInfo->getDefaultFrontLiveSite() . $shRewriteBit . $shTemp . $shAnchor);
				if (empty($uri)) // if no URI, append remaining vars directly to the string
					$ret .= $shAppendString;
				else
					shRebuildVars($shAppendString, $uri);
				$pageInfo->setCurrentLanguage($shOrigLang);
				ShlSystem_Log::debug('sh404sef', 'Returning shSefRelToAbs 2 with ' . $ret);
				return $ret;
			}
		}
	}

	$newstring = str_replace($pageInfo->getDefaultFrontLiveSite() . '/', '', $string);

	$letsGo = JString::substr($newstring, 0, 9) == 'index.php';
	$letsGoSsl = false;
	if ($letsGo || $letsGoSsl)
	{
		// Replace & character variations.
		$string = str_replace(array('&amp;', '&#38;'), array('&', '&'), $letsGo ? $newstring : $newStringSsl);
		$newstring = $string; // V 1.2.4.q
		$shSaveString = $string;
		// warning : must add &lang=xx (only if it does not exists already), so as to be able to recognize the SefURL in the db if it's there
		if (!Sh404sefHelperUrl::getUrlVar($string, 'lang'))
		{
			$shSepString = JString::substr($string, -9) == 'index.php' ? '?' : '&';
			$anchorTable = explode('#', $string); // V 1.2.4.m remove anchor before adding language
			$string = $anchorTable[0];
			$string .= $shSepString . 'lang=' . Sh404sefHelperLanguage::getUrlCodeFromTag($shLanguage)
				. (!empty($anchorTable[1]) ? '#' . $anchorTable[1] : ''); // V 1.2.4.m then stitch back anchor
		}
		$URI = new sh_Net_URL($string);
		// V 1.2.4.l need to save unsorted URL
		if (count($URI->querystring) > 0)
		{
			// Import new vars here.
			$option = null;
			$task = null;
			//$sid = null;  V 1.2.4.s
			// sort GET parameters to avoid some issues when same URL is produced with options not
			// in the same order, ie index.php?option=com_virtuemart&category_id=3&Itemid=2&lang=fr
			// Vs index.php?category_id=3&option=com_virtuemart&Itemid=2&lang=fr
			ksort($URI->querystring); // sort URL array
			$string = Sh404sefHelperUrl::sortUrl($string);
			// now we are ready to extract vars
			$shGETVars = $URI->querystring;
			extract($URI->querystring, EXTR_REFS);
		}

		if (empty($option))
		{
			// V 1.2.4.r protect against empty $option : we won't know what to do
			$pageInfo->setCurrentLanguage($shOrigLang);
			ShlSystem_Log::debug('sh404sef', 'Returning shSefRelToAbs 3 with ' . $shOrigString);
			return $shOrigString;
		}

		// get plugin associated with the extension
		$extPlugin = Sh404sefFactory::getExtensionPlugin($option);

		// get component type
		$shComponentType = $extPlugin->getComponentType();
		$shOption = str_replace('com_', '', $option);

		//list of extension we always skip
		$alwaysSkip = array('jce', 'akeeba', 'media', 'contenthistory');
		if (in_array($shOption, $alwaysSkip))
		{
			$shComponentType = Sh404sefClassBaseextplugin::TYPE_SKIP;
		}

		// V 1.2.4.s : fallback to to JoomlaSEF if no extension available
		// V 1.2.4.t : this is too early ; it prevents manual custom redirect to be checked agains the requested non-sef URL
		ShlSystem_Log::debug('sh404sef', 'Component type = ' . $shComponentType);
		// is there a named anchor attached to $string? If so, strip it off, we'll put it back later.
		if ($URI->anchor)
		{
			$string = str_replace('#' . $URI->anchor, '', $string);
		}
		// V 1.2.4.m
		// shumisha special homepage processing (in other than default language)
		if ((shIsAnyHomePage($string)) || ($string == 'index.php') // 10/08/2007 18:13:43
		)
		{
			$sefstring = '';
			$urlType = shGetSefURLFromCacheOrDB($string, $sefstring);
			// still use it so we need it both ways
			if (($urlType == sh404SEF_URLTYPE_NONE || $urlType == sh404SEF_URLTYPE_404) && empty($showall)
				&& (!empty($limit) || (!isset($limit) && !empty($limitstart))))
			{
				$urlType = shGetSefURLFromCacheOrDB(shCleanUpPag($string), $sefstring); // V 1.2.4.t check also without page info
				//to be able to add pagination on custom
				//redirection or multi-page homepage
				if ($urlType != sh404SEF_URLTYPE_NONE && $urlType != sh404SEF_URLTYPE_404)
				{
					$sefstring = shAddPaginationInfo(@$limit, @$limitstart, @showall, 1, $string, $sefstring, null);
					// a special case : com_content  does not calculate pagination right
					// for frontpage and blog, they include links shown at the bottom in the calculation of number of items
					// For instance, with joomla sample data, the frontpage has only 5 articles
					// but the view sets $limit to 9 !!!
					if (($option == 'com_content' && isset($layout) && $layout == 'blog')
						|| ($option == 'com_content' && isset($view) && $view == 'featured'))
					{
						$listLimit = shGetDefaultDisplayNumFromURL($string, $includeBlogLinks = true);
						$string = Sh404sefHelperUrl::setUrlVar($string, 'limit', $listLimit);
						$string = Sh404sefHelperUrl::sortUrl($string);
					}

					// that's a new URL, so let's add it to DB and cache
					shAddSefUrlToDBAndCache($string, $sefstring, 0, $urlType); // created url must be of same type as original
				}
				if ($urlType == sh404SEF_URLTYPE_NONE || $urlType == sh404SEF_URLTYPE_404)
				{
					require_once(sh404SEF_FRONT_ABS_PATH . 'sef_ext.php');
					$sef_ext = new sef_404();
					// Rewrite the URL now.
					// a special case : com_content  does not calculate pagination right
					// for frontpage and blog, they include links shown at the bottom in the calculation of number of items
					// For instance, with joomla sample data, the frontpage has only 5 articles
					// but the view sets $limit to 9 !!!
					if (($option == 'com_content' && isset($layout) && $layout == 'blog')
						|| ($option == 'com_content' && isset($view) && $view == 'featured'))
					{
						$listLimit = shGetDefaultDisplayNumFromURL($string, $includeBlogLinks = true);
						$string = Sh404sefHelperUrl::setUrlVar($string, 'limit', $listLimit);
						$string = Sh404sefHelperUrl::sortUrl($string);
					}
					$urlVars = is_array($URI->querystring) ? array_map('urldecode', $URI->querystring) : $URI->querystring;
					$sefstring = $sef_ext->create($string, $urlVars, $shAppendString, $shLanguage, $shOrigString, $originalUri); // V 1.2.4.s added original string
				}
			}
			else if (($urlType == sh404SEF_URLTYPE_NONE || $urlType == sh404SEF_URLTYPE_404))
			{
				// not found but no $limit or $limitstart
				$sefstring = Sh404sefHelperLanguage::getUrlCodeFromTag($shLanguage) . '/';
				shAddSefUrlToDBAndCache($string, $sefstring, 0, sh404SEF_URLTYPE_AUTO); // create it
			}
			// V 1.2.4.j : added $shAppendString to pass non sef parameters. For use with parameters that won't be stored in DB
			$ret = $pageInfo->getDefaultFrontLiveSite()
				. (empty($sefstring) ? '' : $sefConfig->shRewriteStrings[$sefConfig->shRewriteMode] . $sefstring);

			$ret = shFinalizeURL($ret);
			if (empty($uri))
			{ // if no URI, append remaining vars directly to the string
				$ret .= $shAppendString;
			}
			else
			{
				shRebuildVars($shAppendString, $uri);
			}
			$pageInfo->setCurrentLanguage($shOrigLang);
			ShlSystem_Log::debug('sh404sef', 'Returning shSefRelToAbs 4 with ' . $ret);
			return $ret;
		}

		if (isset($option) && !($option == 'com_content' && @$task == 'edit') && (strtolower($option) != 'com_sh404sef'))
		{
			// V x 29/08/2007 23:19:48
			// check also that option = com_content, otherwise, breaks some comp
			switch ($shComponentType)
			{
				case Sh404sefClassBaseextplugin::TYPE_SKIP:
					{
						$sefstring = $shSaveString; // V 1.2.4.q : restore untouched URL, except anchor which will be added later
						// J! 1.6 kill all query vars
						$shGETVars = array();
						$uri->setQuery(array());
						break;
					}

				case Sh404sefClassBaseextplugin::TYPE_SIMPLE:
				// check for custom urls
					$sefstring = '';
					$urlType = shGetSefURLFromCacheOrDB($string, $sefstring);
					// if no custom found, then build default url
					if ($urlType != sh404SEF_URLTYPE_CUSTOM)
					{
						// if not found then fall back to Joomla! SEF
						if (isset($URI))
						{
							unset($URI);
						}
						$sefstring = 'component/';
						$URI = new sh_Net_URL(Sh404sefHelperUrl::sortUrl($shSaveString)); // can't remove yet, anchor is use later down
						$jUri = new JUri(Sh404sefHelperUrl::sortUrl($shSaveString));
						$uriVars = $jUri->getQuery($asArray = true);
						// remove lang information, if set to
						// based on remove_default_prefix params of languagefilter plugin
						$languageTag = empty($uriVars['lang']) ? Sh404sefFactory::getPageInfo()->currentLanguageTag
							: Sh404sefHelperLanguage::getLangTagFromUrlCode($uriVars['lang']);
						$shouldInsertLanguageCode = shInsertIsoCodeInUrl($shOption, $languageTag);
						if (isset($uriVars['lang']))
						{
							unset($uriVars['lang']);
						}
						if (count($uriVars) > 0)
						{
							foreach ($uriVars as $key => $value)
							{
								if (is_array($value))
								{
									foreach ($value as $k => $v)
									{
										// fix for arrays, thanks doorknob
										$sefstring .= $key . '[' . $k . '],' . $v . '/';
									}
								}
								else
								{
									$sefstring .= $key . ',' . $value . '/';
								}
							}
							$sefstring = str_replace('option,', '', $sefstring);
						}
						if ($shouldInsertLanguageCode)
						{
							$sefstring = Sh404sefHelperLanguage::getUrlCodeFromTag($languageTag) . '/' . $sefstring;
						}
					}
					break;

				default:
					{
						$sefstring = '';
						// base case:
						$urlType = shGetSefURLFromCacheOrDB($string, $sefstring);

						// first special case. User may have customized paginated urls
						// this will be picked up by the line above, except if we're talking about
						// a category or section blog layout, where Joomla does not uses the correct
						// value for limit
						if (($urlType == sh404SEF_URLTYPE_NONE || $urlType == sh404SEF_URLTYPE_404) && empty($showall)
							&& (!empty($limit) || (!isset($limit) && !empty($limitstart))))
						{
							if (($option == 'com_content' && isset($layout) && $layout == 'blog')
								|| ($option == 'com_content' && isset($view) && $view == 'featured'))
							{
								$listLimit = shGetDefaultDisplayNumFromURL($string, $includeBlogLinks = true);
								$tmpString = Sh404sefHelperUrl::setUrlVar($string, 'limit', $listLimit);
								$tmpString = Sh404sefHelperUrl::sortUrl($tmpString);
								$urlType = shGetSefURLFromCacheOrDB($tmpString, $sefstring);
								if ($urlType != sh404SEF_URLTYPE_NONE && $urlType != sh404SEF_URLTYPE_404)
								{
									// we found a match with pagination info!
									$string = $tmpString;
								}
							}
						}

						// now let's try again without any pagination at all
						/*
						 if (($urlType == sh404SEF_URLTYPE_NONE || $urlType == sh404SEF_URLTYPE_404) && empty( $showall) && (!empty($limit) || (!isset($limit) && !empty($limitstart)))) {
						$urlType = shGetSefURLFromCacheOrDB(shCleanUpPag($string), $sefstring); // search without pagination info
						if ($urlType != sh404SEF_URLTYPE_NONE && $urlType != sh404SEF_URLTYPE_404) {
						$sefstring = shAddPaginationInfo( @$limit, @$limitstart, @showall, 1, $string, $sefstring, null);
						// a special case : com_content  does not calculate pagination right
						// for frontpage and blog, they include links shown at the bottom in the calculation of number of items
						// For instance, with joomla sample data, the frontpage has only 5 articles
						// but the view sets $limit to 9 !!!
						if (($option == 'com_content' && isset($layout) && $layout == 'blog')
						    || ($option == 'com_content' && isset( $view) && $view == 'featured' )) {
						$listLimit = shGetDefaultDisplayNumFromURL($string, $includeBlogLinks = true);
						$string = Sh404sefHelperUrl::setUrlVar( $string, 'limit', $listLimit);
						$string = Sh404sefHelperUrl::sortUrl($string);
						}
						
						// that's a new URL, so let's add it to DB and cache
						ShlSystem_Log::debug( 'sh404sef', 'Created url based on non paginated base url:' . $string);
						shAddSefUrlToDBAndCache( $string, $sefstring, 0, $urlType);
						}
						}
						 */
						if ($urlType == sh404SEF_URLTYPE_NONE)
						{
							// If component has its own sef_ext plug-in included.
							$shDoNotOverride = in_array($shOption, $sefConfig->shDoNotOverrideOwnSef);
							if (shFileExists(sh404SEF_ABS_PATH . 'components/' . $option . '/sef_ext.php')
								&& ($shDoNotOverride // and param said do not override
									|| (!$shDoNotOverride // or param said override, but we don't have a plugin either in sh404SEF dir or component sef_ext dir
										&& (!shFileExists(sh404SEF_ABS_PATH . 'components/com_sh404sef/sef_ext/' . $option . '.php')
											&& !shFileExists(sh404SEF_ABS_PATH . 'components/' . $option . '/sef_ext/' . $option . '.php')))))
							{
								// Load the plug-in file. V 1.2.4.s changed require_once to include
								include_once(sh404SEF_ABS_PATH . 'components/' . $option . '/sef_ext.php');
								$_SEF_SPACE = $sefConfig->replacement;
								$comp_name = str_replace('com_', '', $option);
								$className = 'sef_' . $comp_name;
								$sef_ext = new $className;
								// V x : added default string in params
								if (empty($sefConfig->defaultComponentStringList[$comp_name]))
								{
									$title[] = getMenuTitle($option, null, isset($Itemid) ? @$Itemid : null, null, $shLanguage);
								}
								// V 1.2.4.x
								else
								{
									$title[] = $sefConfig->defaultComponentStringList[$comp_name];
								}
								// V 1.2.4.r : clean up URL BEFORE sending it to sef_ext files, to have control on what they do
								// remove lang information, we'll put it back ourselves later
								//$shString = preg_replace( '/(&|\?)lang=[a-zA-Z]{2,3}/iU' ,'', $string);
								// V 1.2.4.t use original non-sef string. Some sef_ext files relies on order of params, which may
								// have been changed by sh404SEF
								$shString = preg_replace('/(&|\?)lang=[a-zA-Z]{2,3}/iu', '', $shSaveString);
								$finalstrip = explode("|", $sefConfig->stripthese);
								$shString = str_replace('&', '&amp;', $shString);
								ShlSystem_Log::debug('sh404sef', 'Sending to own sef_ext.php plugin : ' . $shString);
								$sefstring = $sef_ext->create($shString);
								ShlSystem_Log::debug('sh404sef', 'Created by sef_ext.php plugin : ' . $sefstring);
								$sefstring = str_replace("%10", "%2F", $sefstring);
								$sefstring = str_replace("%11", $sefConfig->replacement, $sefstring);
								$sefstring = rawurldecode($sefstring);
								if ($sefstring == $string)
								{
									if (!empty($shMosMsg)) // V x 01/09/2007 22:48:01
										$string .= '?' . $shMosMsg;
									$ret = shFinalizeURL($string);
									$pageInfo->currentLanguageTag = $shOrigLang;
									ShlSystem_Log::debug('sh404sef', 'Returning shSefRelToAbs 5 with ' . $ret);
									return $ret;
								}
								else
								{
									// V 1.2.4.p : sef_ext extensions for opensef/SefAdvance do not always replace '
									$sefstring = str_replace('\'', $sefConfig->replacement, $sefstring);
									// some ext. seem to html_special_chars URL ?
									$sefstring = str_replace('&#039;', $sefConfig->replacement, $sefstring); // V w 27/08/2007 13:23:56
									$sefstring = str_replace(' ', $_SEF_SPACE, $sefstring);
									$sefstring = str_replace(' ', '',
										(shInsertIsoCodeInUrl($option, $shLanguage) ? // V 1.2.4.q
											Sh404sefHelperLanguage::getUrlCodeFromTag($shLanguage) . '/' : '') . titleToLocation($title[0]) . '/'
											. $sefstring . (($sefstring != '') ? $sefConfig->suffix : ''));
									if (!empty($sefConfig->suffix))
										$sefstring = str_replace('/' . $sefConfig->suffix, $sefConfig->suffix, $sefstring);

									//$finalstrip = explode("|", $sefConfig->stripthese);
									$sefstring = str_replace($finalstrip, $sefConfig->replacement, $sefstring);
									$sefstring = str_replace($sefConfig->replacement . $sefConfig->replacement . $sefConfig->replacement,
										$sefConfig->replacement, $sefstring);
									$sefstring = str_replace($sefConfig->replacement . $sefConfig->replacement, $sefConfig->replacement, $sefstring);
									$suffixthere = 0;
									if (!empty($sefConfig->suffix) && strpos($sefstring, $sefConfig->suffix) !== false) // V 1.2.4.s
										$suffixthere = strlen($sefConfig->suffix);
									$takethese = str_replace("|", "", $sefConfig->friendlytrim);
									$sefstring = JString::trim(JString::substr($sefstring, 0, strlen($sefstring) - $suffixthere), $takethese);
									$sefstring .= $suffixthere == 0 ? '' : $sefConfig->suffix; // version u 26/08/2007 17:27:16
									// V 1.2.4.m store it in DB so as to be able to use sef_ext plugins really !
									$string = str_replace('&amp;', '&', $string);
									// V 1.2.4.r without mod_rewrite
									$shSefString = shAdjustToRewriteMode($sefstring);
									// V 1.2.4.p check for various URL for same content
									$dburl = ''; // V 1.2.4.t prevent notice error
									$urlType = sh404SEF_URLTYPE_NONE;
									if ($sefConfig->shUseURLCache)
									{
										$urlType = Sh404sefHelperCache::getNonSefUrlFromCache($shSefString, $dburl);
									}
									$newMaxRank = 0; // V 1.2.4.s
									$shDuplicate = false;
									if ($sefConfig->shRecordDuplicates || $urlType == sh404SEF_URLTYPE_NONE)
									{
										try
										{
											$sql = "SELECT newurl, rank, dateadd FROM #__sh404sef_urls WHERE oldurl = "
												. $database->Quote($shSefString) . " ORDER BY rank ASC";
											$database->setQuery($sql);
											$dbUrlList = $database->loadObjectList();
										}
										catch (Exception $e)
										{
											ShlSystem_Log::error('sh404sef', '%s::%s::%d: %s', __CLASS__, __METHOD__, __LINE__, $e->getMessage());
											$dbUrlList = array();
										}
										if (count($dbUrlList) > 0)
										{
											$dburl = $dbUrlList[0]->newurl;
											$newMaxRank = $dbUrlList[count($dbUrlList) - 1]->rank + 1;
											$urlType = $dbUrlList[0]->dateadd == '0000-00-00' ? sh404SEF_URLTYPE_AUTO : sh404SEF_URLTYPE_CUSTOM;
										}
									}
									if ($urlType != sh404SEF_URLTYPE_NONE && ($dburl != $string))
										$shDuplicate = true;
									$urlType = $urlType == sh404SEF_URLTYPE_NONE ? sh404SEF_URLTYPE_AUTO : $urlType;
									ShlSystem_Log::debug('sh404sef',
										'Adding from sef_ext to DB : ' . $shSefString . ' | rank = ' . ($shDuplicate ? $newMaxRank : 0));
									shAddSefUrlToDBAndCache($string, $shSefString, ($shDuplicate ? $newMaxRank : 0), $urlType);
								}
							}
							// Component has no own sef extension.
							else
							{
								$string = JString::trim($string, "&?");

								// V 1.2.4.q a trial in better handling homepage articles
								// disabled in J! 1.6. Becomes too complex with multi-language
								// TODO: remove guessItemidOnHomepage setting
								if (false && shIsCurrentPageHome() && ($option == 'com_content') // com_content component on homepage
									&& (isset($task)) && ($task == 'view') && $sefConfig->guessItemidOnHomepage)
								{
									$string = preg_replace('/(&|\?)Itemid=[^&]*/iu', '', $string); // we remove Itemid, as com_content plugin
									$Itemid = null; // will hopefully do a better job at finding the right one
									unset($URI->querystring['Itemid']);
									unset($shGETVars['Itemid']);
								}

								require_once(sh404SEF_FRONT_ABS_PATH . 'sef_ext.php');
								$sef_ext = new sef_404();
								// Rewrite the URL now. // V 1.2.4.s added original string
								// a special case : com_content  does not calculate pagination right
								// for frontpage and blog, they include links shown at the bottom in the calculation of number of items
								// For instance, with joomla sample data, the frontpage has only 5 articles
								// but the view sets $limit to 9 !!!
								if (($option == 'com_content' && isset($layout) && $layout == 'blog')
									|| ($option == 'com_content' && isset($view) && $view == 'featured'))
								{
									$listLimit = shGetDefaultDisplayNumFromURL($string, $includeBlogLinks = true);
									$string = Sh404sefHelperUrl::setUrlVar($string, 'limit', $listLimit);
									$string = Sh404sefHelperUrl::sortUrl($string);
									//$URI->addQueryString( 'limit', $listLimit);
								}
								$sefstring = $sef_ext->create($string, $URI->querystring, $shAppendString, $shLanguage, $shOrigString, $originalUri);
								ShlSystem_Log::debug('sh404sef', 'Created sef url from default plugin: ' . $sefstring);
							}
						}
					}
			} // end of cache check shumisha
			if (isset($sef_ext))
			{
				unset($sef_ext);
			}

			// if string has not been modified, then we have decided for a non-sef
			if ($string == $sefstring)
			{
				// J! 1.6 kill all query vars
				$shGETVars = array();
				$uri->setQuery(array());
			}
			else
			{
				// include rewrite mode bit
				$shRewriteBit = $shComponentType == Sh404sefClassBaseextplugin::TYPE_SKIP ? '/'
					: $sefConfig->shRewriteStrings[$sefConfig->shRewriteMode];
				if (strpos($sefstring, 'index.php') === 0)
				{
					$shRewriteBit = '/';
				}
				// V 1.2.4.t bug #119
				$string = $pageInfo->getDefaultFrontLiveSite() . $shRewriteBit . JString::ltrim($sefstring, '/')
					. (($URI->anchor) ? "#" . $URI->anchor : '');
			}
		}
		else
		{ // V x 03/09/2007 13:47:37 editing content
			$shComponentType = Sh404sefClassBaseextplugin::TYPE_SKIP; // will prevent turning & into &amp;
			ShlSystem_Log::debug('sh404sef', 'shSefrelfToAbs: option not set, skipping');
		}
		$ret = $string;
		// $ret = str_replace('itemid', 'Itemid', $ret); // V 1.2.4.t bug #125
		ShlSystem_Log::debug('sh404sef', '(1) Setting shSefRelToAbs return string as: ' . $ret);
	}
	if (!isset($ret))
	{
		$ret = $string;
		ShlSystem_Log::debug('sh404sef', '(2) Setting shSefRelToAbs return string as: ' . $ret);
	}
	$ret = ($shComponentType == Sh404sefClassBaseextplugin::TYPE_DEFAULT) ? shFinalizeURL($ret) : $ret; // V w 27/08/2007 13:21:28
	ShlSystem_Log::debug('sh404sef', '(3) shSefRelToAbs return string after shFinalize: ' . $ret);
	if (empty($uri) || $shComponentType == Sh404sefClassBaseextplugin::TYPE_SKIP)
	{
		// we don't have a uri : we must be doing a redirect from non-sef to sef or similar
		$ret .= $shAppendString; // append directly to url
		ShlSystem_Log::debug('sh404sef', '(4) shSefRelToAbs return string after appendString: ' . $ret);
	}
	else
	{
		if (empty($sefstring) || (!empty($sefstring) && strpos($sefstring, 'index.php') !== 0))
		{
			shRebuildVars($shAppendString, $uri); // instead, add to uri. Joomla will put everything together. Only do this if we have a sef url, and not if we have a non-sef
			ShlSystem_Log::debug('sh404sef', '(5) shSefRelToAbs no sefstring, adding rebuild vars : ' . $shAppendString);
		}
	}
	$pageInfo->setCurrentLanguage($shOrigLang);
	ShlSystem_Log::debug('sh404sef', 'shSefRelToAbs: finally returning: ' . $ret);
	return $ret;
}

// V 1.2.4.t returns sef url with added pagination information
function shAddPaginationInfo($limit, $limitstart, $showall, $iteration, $url, $location, $shSeparator = null, $defaultListLimitValue = null,
	$suppressPagination = false)
{
	$mainframe = JFactory::getApplication();

	$pageInfo = Sh404sefFactory::getPageInfo(); // get page details gathered by system plugin
	$sefConfig = Sh404sefFactory::getConfig();
	$database = ShlDbHelper::getDb();

	//echo 'Incoming pagination : $listLimit : ' . $listLimit . ' | $defaultListLimit : ' . $defaultListLimit . "\n";

	// clean suffix and index file before starting to add things to the url
	// clean suffix
	if (strpos($url, 'option=com_content') !== false && strpos($url, 'format=pdf') !== false)
	{
		$shSuffix = '.pdf';
	}
	else
	{
		$shSuffix = $sefConfig->suffix;
	}
	$suffixLength = JString::strLen($shSuffix);
	if (!empty($shSuffix) && ($shSuffix != '/') && JString::substr($location, -$suffixLength) == $shSuffix)
	{
		$location = JString::substr($location, 0, JString::strlen($location) - $suffixLength);
	}

	// clean index file
	if ($sefConfig->addFile && (empty($shSuffix) || JString::subStr($location, -$suffixLength) != $shSuffix))
	{
		$indexFileLength = JString::strlen($sefConfig->addFile);
		if (($sefConfig->addFile != '/') && JString::substr($location, -$indexFileLength) == $sefConfig->addFile)
		{
			$location = JString::substr($location, 0, JString::strlen($location) - $indexFileLength);
		}
	}

	// get a default limit value, for urls where it's missing
	$listLimit = shGetDefaultDisplayNumFromURL($url, $includeBlogLinks = true);
	$defaultListLimit = is_null($defaultListLimitValue) ? shGetDefaultDisplayNumFromConfig($url, $includeBlogLinks = false) : $defaultListLimitValue;

	// do we have a trailing slash ?
	if (empty($shSeparator))
	{
		$shSeparator = (JString::substr($location, -1) == '/') ? '' : '/';
	}

	if (!$suppressPagination)
	{
		// start computing pagination
		if (!empty($limit) && is_numeric($limit))
		{
			$pagenum = intval($limitstart / $limit);
			$pagenum++;
		}
		else if (!isset($limit) && !empty($limitstart))
		{
			// only limitstart
			if (strpos($url, 'option=com_content') !== false && strpos($url, 'view=article') !== false)
			{
				$pagenum = intval($limitstart + 1); // multipage article
			}
			else
			{
				$pagenum = intval($limitstart / $listLimit) + 1; // blogs, tables, ...
			}
		}
		else
		{
			$pagenum = $iteration;
		}
		// Make sure we do not end in infite loop here.
		if ($pagenum < $iteration)
			$pagenum = $iteration;
		// shumisha added to handle table-category and table-section which may have variable number of items per page
		// There still will be a problem with filter, which may reduce the total number of items. Thus the item we are looking for
		if ($sefConfig->alwaysAppendItemsPerPage || (strpos($url, 'option=com_virtuemart') && $sefConfig->shVmUsingItemsPerPage))
		{
			$shMultPageLength = $sefConfig->pagerep . (empty($limit) ? $listLimit : $limit);
		}
		else
			$shMultPageLength = '';
		// shumisha : modified to add # of items per page to URL, for table-category or section-category

		if (!empty($sefConfig->pageTexts[$pageInfo->currentLanguageTag])
			&& (false !== strpos($sefConfig->pageTexts[$pageInfo->currentLanguageTag], '%s')))
		{
			$page = str_replace('%s', $pagenum, $sefConfig->pageTexts[$pageInfo->currentLanguageTag]) . $shMultPageLength;
		}
		else
		{
			$page = $sefConfig->pagerep . $pagenum . $shMultPageLength;
		}

		// V 1.2.4.t special processing to replace page number by headings
		$shPageNumberWasReplaced = false;
		if (strpos($url, 'option=com_content') !== false && strpos($url, 'view=article') !== false && !empty($limitstart))
		{
			// this is multipage article - limitstart instead of limit in J1.5
			if ($sefConfig->shMultipagesTitle)
			{
				parse_str($url, $shParams);
				if (!empty($shParams['id']))
				{
					$shPageTitle = '';
					try
					{
						$contentElement = ShlDbHelper::selectObject('#__content', array('id', 'fulltext', 'introtext'),
							array('id' => $shParams['id']));
					}
					catch (Exception $e)
					{
						JError::raise(E_ERROR, 500, $e->getMessage());
					}
					$contentText = $contentElement->introtext . $contentElement->fulltext;
					if (!empty($contentElement) && (strpos($contentText, 'class="system-pagebreak') !== false))
					{
						// search for mospagebreak tags
						// copied over from pagebreak plugin
						// expression to search for
						$regex = '#<hr([^>]*)class=(\"|\')system-pagebreak(\"|\')([^>]*)\/>#iU';
						// find all instances of mambot and put in $matches
						$shMatches = array();
						preg_match_all($regex, $contentText, $shMatches, PREG_SET_ORDER);
						// adds heading or title to <site> Title
						if (empty($limitstart))
						{
							// if first page use heading of first mospagebreak
							/* if ( $shMatches[0][2] ) {
							 parse_str( html_entity_decode( $shMatches[0][2] ), $args );
							if ( @$args['heading'] ) {
							$shPageTitle = stripslashes( $args['heading'] );
							}
							}*/
						}
						else
						{ // for other pages use title of mospagebreak
							if ($limitstart > 0 && $shMatches[$limitstart - 1][1])
							{
								$args = JUtility::parseAttributes($shMatches[$limitstart - 1][0]);
								if (@$args['title'])
								{
									$shPageTitle = $args['title'];
								}
								else if (@$args['alt'])
								{
									$shPageTitle = $args['alt'];
								}
								else
								{ // there is a page break, but no title. Use a page number
									$shPageTitle = str_replace('%s', $limitstart + 1, $sefConfig->pageTexts[$pageInfo->currentLanguageTag]);
								}
							}
						}
					}
					if (!empty($shPageTitle))
					{
						// found a heading, we should use that as a Title
						$location .= $shSeparator . titleToLocation($shPageTitle);
					}
					$shPageNumberWasReplaced = true; // always set the flag, otherwise we'll a Page-1 added
				}
			}
			else
			{
				// mutiple pages article, but we don't want to use smart title.
				// directly use limitstart
				$page = str_replace('%s', $limitstart + 1, $sefConfig->pageTexts[$pageInfo->currentLanguageTag]);
			}
		}
		// maybe this is a multipage with "showall=1"
		if (strpos($url, 'option=com_content') !== false && strpos($url, 'view=article') !== false && strpos($url, 'showall=1') !== false)
		{
			// this is multipage article with showall
			$tempTitle = JText::_('All Pages');
			$location .= $shSeparator . titleToLocation($tempTitle);
			$shPageNumberWasReplaced = true; // always set the flag, otherwise we'll a Page-1 added
		}

		// make sure we remove bad characters
		$takethese = str_replace('|', '', $sefConfig->friendlytrim);
		$location = JString::trim($location, $takethese);

		// add page number
		if (!$shPageNumberWasReplaced
			&& ((!isset($limitstart) && (isset($limit) && $limit != 1 && $limit != $listLimit && $limit != $defaultListLimit)) || !empty($limitstart)))
		{
			$location .= $shSeparator . $page;
		}
	}
	// add suffix
	if (!empty($shSuffix) && !empty($location) && $location != '/' && JString::substr($location, -1) != '/')
	{
		$location = $shSuffix == '/' ? $location . $shSuffix : str_replace($shSuffix, '', $location) . $shSuffix;
	}

	// add default index file
	if ($sefConfig->addFile)
	{
		// V 1.2.4.t
		if ((empty($shSuffix) || (!empty($shSuffix) && JString::subStr($location, -$suffixLength) != $shSuffix)))
			$location .= (JString::substr($location, -1) == '/' ? '' : '/') . $sefConfig->addFile;
	}
	return JString::ltrim($location, '/');
}

// V 1.2.4.t check if this is a request for VM cookie check AND done by a search engine
// if so, this has to be an old link left over in search engine index, and  we must 301 redirectt to
// same URl without vmvhk/
function shCheckVMCookieRedirect()
{

	$pageInfo = &Sh404sefFactory::getPageInfo();

	if (shIsSearchEngine() && strpos($pageInfo->currentSefUrl, 'vmchk/') !== false)
	{
		shRedirect(str_replace('vmchk/', '', $pageInfo->currentSefUrl));
	}
}

/*
 * 404SEF SUPPORT FUNCTIONS
 */

// V 1.2.4.q detect homepage, disregarding pagination
function shIsHomepage($string)
{

	static $pages = array();
	static $home = '';

	if (!isset($pages[$string]))
	{
		$pageInfo = &Sh404sefFactory::getPageInfo();
		if (empty($home) && !empty($pageInfo->homeLink))
		{
			$home = Sh404sefHelperUrl::sortUrl(shCleanUpPag($pageInfo->homeLink));
		}

		$shTempString = JString::rtrim(str_replace($pageInfo->getDefaultFrontLiveSite(), '', $string), '/');
		$pages[$string] = Sh404sefHelperUrl::sortUrl(shCleanUpPag($shTempString)) == $home; // version t added sorting
	}
	return $pages[$string];
}

function shIsAnyHomepage($string)
{

	static $pages = array();
	static $home = '';
	static $cleanedHomeLinks = array();

	if (!isset($pages[$string]))
	{
		$pageInfo = &Sh404sefFactory::getPageInfo();
		if (empty($cleanedHomeLinks))
		{
			foreach ($pageInfo->homeLinks as $link)
			{
				$cleanedHomeLinks[] = shCleanUpPag($link);
			}
		}

		$shTempString = JString::rtrim(str_replace($pageInfo->getDefaultFrontLiveSite(), '', $string), '/');
		$shTempString = Sh404sefHelperUrl::sortUrl(shCleanUpPag($shTempString));

		// check all homepages
		$pages[$string] = false;
		foreach ($cleanedHomeLinks as $link)
		{
			if ($link == $shTempString)
			{
				$pages[$string] = true;
			}
		}

	}
	return $pages[$string];
}

function getMenuTitle($option, $task, $id = null, $string = null, $shLanguage = null)
{

	$pageInfo = &Sh404sefFactory::getPageInfo();
	$sefConfig = &Sh404sefFactory::getConfig();

	$shLanguage = empty($shLanguage) ? $pageInfo->currentLanguageTag : $shLanguage;
	$nameField = $sefConfig->useMenuAlias ? 'alias' : 'title';

	$menu = JFactory::getApplication()->getMenu();

	$attr = array();
	$values = array();
	if (!empty($string))
	{
		$attr[] = 'link';
		$values[] = $string;
	}
	else if (!empty($id))
	{
		$attr[] = 'id';
		$values[] = $id;
	}
	else if (!empty($option))
	{
		// need to find component id
		$component = JComponentHelper::getComponent($option, $strict = true);
		if (!$component->enabled)
		{
			return ('/');
		}
		$attr[] = 'component_id';
		$values[] = $component->id;
	}
	else
	{
		return '/';
	}

	// now ask J! to fetch menu item title
	$menuItem = shFindMenuItem($menu->getMenu(), $attr, $values, $firstOnly = true);

	if (!empty($menuItem))
	{

		$languages = JLanguageHelper::getLanguages();
		foreach ($languages as $langId => $language)
		{
			if (strpos($pageInfo->homeLinks[$language->lang_code], 'Itemid=' . $menuItem->id) !== false)
			{
				$title = $langId == 0 ? '/' : $language->sef;
				return $title; // this is one of the homepages, retunr / or a lang code
			}
		}
		// non-homepage
		if (!empty($menuItem->$nameField))
		{
			return $menuItem->$nameField;
		}

	}

	return str_replace('com_', '', $option);

}

function shFindMenuItem($menuItems, $attributes, $values, $firstonly = false)
{
	$items = array();
	$attributes = (array) $attributes;
	$values = (array) $values;

	foreach ($menuItems as $item)
	{
		if (!is_object($item))
		{
			continue;
		}

		$test = true;
		for ($i = 0, $count = count($attributes); $i < $count; $i++)
		{
			if (is_array($values[$i]))
			{
				if (!in_array($item->$attributes[$i], $values[$i]))
				{
					$test = false;
					break;
				}
			}
			else
			{
				if ($item->$attributes[$i] != $values[$i])
				{
					$test = false;
					break;
				}
			}
		}

		if ($test)
		{
			if ($firstonly)
			{
				return $item;
			}

			$items[] = $item;
		}
	}

	return $items;
}

function shIsSearchEngine()
{
	// return true if user agant is a search engine
	static $isSearchEngine = null;

	//return true;
	if (!is_null($isSearchEngine))
	{
		return $isSearchEngine;
	}
	else
	{
		$isSearchEngine = false;
		$useragent = empty($_SERVER['HTTP_USER_AGENT']) ? '' : strtolower($_SERVER['HTTP_USER_AGENT']);
		if (!empty($useragent))
		{
			$remoteConfig = Sh404sefHelperUpdates::getRemoteConfig($forced = false);
			$remotes = empty($remoteConfig->config['searchenginesagents']) ? array() : $remoteConfig->config['searchenginesagents'];
			$agents = array_unique(array_merge(Sh404sefFactory::getPConfig()->searchEnginesAgents, $remotes));
			foreach ($agents as $agent)
			{
				if (strpos($useragent, strtolower($agent)) !== false)
				{
					$isSearchEngine = true;
					return true;
				}
			}
		}
		return $isSearchEngine;
	}
}

// J 1.5 specific functions
function shRemoveSlugs($vars, $removeWhat = true)
{
	// remove slugs from a J! 1.5 non-sef style vars array
	if (!empty($vars))
	{
		foreach ($vars as $k => $v)
		{
			$m = is_string($v) ? explode(':', $v) : null; // tracker #14107, thanks 3dentech
			if (!empty($m) && !empty($m[1]) && is_numeric($m[0]))
			{
				// an integer followed by : followed by something
				$vars[$k] = $removeWhat === 'removeId' ? $m[1] : $m[0]; // depending on params, either keep id or slug
			}
			else
			{
				// use the raw value, for arrays for instance
				$vars[$k] = $v;
			}
		}
		// fix some problems in incoming URLs
		if (!empty($vars['Itemid']))
		{
			// sometimes we get doubles : ?Itemid=xx?Itemid=xx
			$vars['Itemid'] = intval($vars['Itemid']);
		}
		if (!empty($vars['view']))
		{
			// some links have view=article;
			$vars['view'] = str_replace('article;', 'article', $vars['view']);
			// view is set but no option : use default controller (com_content)
			if (empty($vars['option']))
				$vars['option'] = 'com_content';
		}
		if (empty($vars['option']) && !empty($vars['format']) && $vars['format'] == 'feed')
		{
			$vars['option'] = 'com_content';
		}
	}
	return $vars;
}

function shNormalizeNonSefUri(&$uri, $menu = null, $removeSlugs = true)
{
	// put back a J!1.5 non-sef url to J! 1.0.x format
	// Get the route
	$route = $uri->getPath();
	//Get the query vars
	$vars = $uri->getQuery(true);
	// fix some problems in incoming URLs
	if (!empty($vars['Itemid']))
	{
		// sometimes we get doubles : ?Itemid=xx?Itemid=xx
		$vars['Itemid'] = intval($vars['Itemid']);
		$uri->setQuery($vars);
	}

	// fix urls obtained through a single Itemid, in menus : url is option=com_xxx&Itemid=yy
	$urlLang = $uri->getVar('lang', '');
	if ((count($vars) == 2 && $uri->getVar('Itemid')) || (count($vars) == 3 && $uri->getVar('Itemid') && $uri->getVar('lang')))
	{
		if (empty($menu))
		{
			$menu = JFactory::getApplication()->getMenu();
		}
		$shItem = $menu->getItem($vars['Itemid']);
		if (!empty($shItem))
		{
			// we found the menu item
			$url = $shItem->link . '&Itemid=' . $shItem->id;
			$newUri = new JURI($url); // rebuild $uri based on this new url
			$vars = $newUri->getQuery(true);
			if (empty($urlLang))
			{
				$urlLang = $shItem->language;
				if ($urlLang == '*')
				{
					// use current page language
					$urlLang = Sh404sefFactory::getPageInfo()->currentLanguageTag;
				}
				$urlLang = Sh404sefHelperLanguage::getUrlCodeFromTag($urlLang);
			}
			if (!empty($urlLang))
			{
				$vars['lang'] = $urlLang;
			}
		}
	}

	if ($removeSlugs !== false)
	{
		$vars = shRemoveSlugs($vars, $removeSlugs);
	}
	$uri->setQuery($vars);
}

function shNormalizeNonSefUrl($url)
{
	// returns non-sef url with slugs removed + a few fixes
	$uri = new JURI($url);
	shNormalizeNonSefUri($uri);
	return $uri->toString(array('path', 'query', 'fragment'));

}

function shCheckRedirect($dest, $incomingUrl)
{
	$sefConfig = Sh404sefFactory::getConfig();
	if (!empty($dest) && $dest != $incomingUrl)
	{
		// redirect to alias
		if ($dest == sh404SEF_HOMEPAGE_CODE)
		{
			if (!empty($sefConfig->shForcedHomePage))
			{
				$dest = shFinalizeURL($sefConfig->shForcedHomePage);
			}
			else
			{
				$dest = shFinalizeURL(Sh404sefFactory::getPageInfo()->getDefaultFrontLiveSite());
			}
		}
		else
		{
			$shUri = new JURI($dest);
			$shOriginalUri = clone ($shUri);
			$dest = shSefRelToAbs($dest, '', $shUri, $shOriginalUri) . $shUri->toString(array('query'));
		}

		if ($dest != $incomingUrl)
		{
			ShlSystem_Log::debug('sh404sef', 'Redirecting to ' . $dest . ' from alias ' . $incomingUrl);
			shRedirect($dest);
		}
	}
}

function shUrlSafeDisplay($url)
{
	return htmlentities($url, ENT_QUOTES, 'UTF-8');
}

/**
 * Read config values from sobi2 config table
 *
 * @param $key
 * @param $section
 * @return string
 */
function shGetSobi2Config($key, $section)
{
	ShlSystem_Log::debug('sh404sef', 'Using removed shGetSobi2Config function, not applicable anymore');
	return false;
}

/**
 * Insert an intro text into the content table
 *
 * @param strng $shIntroText
 * @return boolean, true if success
 */
function shInsertContent($pageTitle, $shIntroText)
{
	jimport('joomla.database.table');
	try
	{
		$catid = Sh404sefHelperCategories::getUncategorizedCat();
		if (empty($catid))
		{
			$this->setError(JText::_('COM_SH404SEF_CANNOT_SAVE_404_NO_UNCAT'));
			return;
		}
		$contentTable = JTable::getInstance('content');
		$content = array('title' => $pageTitle, 'alias' => $pageTitle, 'title_alias' => $pageTitle, 'introtext' => $shIntroText, 'state' => 1,
			'catid' => $catid,
			'attribs' => '{"menu_image":"-1","show_title":"0","show_section":"0","show_category":"0","show_vote":"0","show_author":"0","show_create_date":"0","show_modify_date":"0","show_pdf_icon":"0","show_print_icon":"0","show_email_icon":"0","pageclass_sfx":""');

		$status = $contentTable->save($content);
	}
	catch (Exception $e)
	{
		$status = false;
	}

	return $status;
}

/**
 * Returns a string with an article id, in accordance
 * with various settings
 * @param $id
 * @param $view
 * @param $option
 * @param $shLangName
 */
function shGetArticleIdString($id, $view, $option, $shLangName)
{
	$sefConfig = &Sh404sefFactory::getConfig();

	// V 1.5.7 : article id, on some categories only
	$articleId = '';
	if ($sefConfig->ContentTitleInsertArticleId && isset($sefConfig->shInsertContentArticleIdCatList) && !empty($id) && ($view == 'article'))
	{
		$slugsModel = Sh404sefModelSlugs::getInstance();
		$article = $slugsModel->getArticle($id);
		if (empty($article[$shLangName]))
		{
			$shLangName = '*';
		}

		// check if article categogy is in the settings categories list,
		// or that the categories list is empty, which means user selected the "All categories" option
		if (!empty($article[$shLangName]))
		{
			$foundCat = (!empty($sefConfig->shInsertContentArticleIdCatList) && empty($sefConfig->shInsertContentArticleIdCatList[0]))
				|| in_array($article[$shLangName]->catid, $sefConfig->shInsertContentArticleIdCatList);
			if ($foundCat !== null && $foundCat !== false)
			{
				$articleId = $article[$shLangName]->id;
			}
		}
	}

	return $articleId;
}

/**
 * Reads an return the page title assigned to either
 * current or a specific menu item
 *
 * @param $Itemid itemid of the desired menu item
 */
function shGetJoomlaMenuItemPageTitle($Itemid = 0)
{
	// get the current menu item, or possibly the one asked for
	$menus = JFactory::getApplication()->getMenu();
	$menuItem = empty($Itemid) ? $menus->getActive() : $menus->getItem($Itemid);

	// get value, if any set
	$title = is_object($menuItem) ? $menuItem->params->get('page_title') : '';

	// return whatever we found
	return $title;
}

/**
 * check various conditions to decide if we
 * should redirect from non-sef url to its
 * sef equivalent
 */
function shShouldRedirectFromNonSef($pageInfo)
{
	die('voluntary die in ' . __METHOD__ . ' of class ' . __CLASS__);
}

function shCheckCustomRedirects($path, $pageInfo)
{
	die('voluntary die in ' . __METHOD__ . ' of class ' . __CLASS__);
}

function shCheckAlias($incomingUrl)
{
	die('voluntary die in ' . __METHOD__ . ' of class ' . __CLASS__);
}

function shRawUrlDecodeDeep($data)
{
	if (is_array($data))
	{
		foreach ($data as $key => $element)
		{
			$data[$key] = shRawUrlDecodeDeep($element);
		}
		return $data;
	}
	else
	{
		return rawurldecode($data);
	}
}

function shUrlDecodeFull($url)
{
	// security checks: copied from Joomla security patch,
	// tracker id: 22767
	// Need to check that the URI is fully decoded in case of multiple-encoded attack vectors.
	$halt = 0;
	while (true)
	{
		$last = $url;
		$url = rawurldecode($url);

		// Check whether the last decode is equal to the first.
		if ($url == $last)
		{
			// Break out of the while if the URI is stable.
			break;
		}
		else if (++$halt > 10)
		{
			// Runaway check. URI has been seriously compromised.
			if (!headers_sent())
			{
				header('HTTP/1.0 403 FORBIDDEN');
				echo 'Forbidden access';
			}
			jexit();
		}
	}

	return $url;
}
