<?php
/**
* JoomlaSEF for Joomla!
* @copyright (c) 2005 The OpenSEF Project (www.opensef.org)
* @copyright (c) 2004-2005 Xaneon Development (www.xaneon.com)
* @license GPL http://www.gnu.org/copyleft/gpl.html
*
* The Resolver is responsible for handling incoming requests that
* use a SEF URL. It rewrites the SEF URL into an ordinary
* "old-fashioned" Joomla! URL, * or displays a 404 Not Found error
* page if the URL could not be resolved.
*/
/** Ensure this file is being included by a parent file */
defined( '_VALID_MOS' ) or die( 'Direct access to this location is not allowed.' );
@define( '_SEF_RESOLVER', 'sefResolver' );
/**
* resolver class
*
*/
class sefResolver extends sefObject {
var $automapper = null;
var $input = null;
var $output = null;
var $params = null;
var $status = null;
var $aliasObj = null; // matched alias object
/**
* sefresolver
*
* @param object $db the database connection object
* @param object $config an instance of JosOpenSEFConfig
* @param object $site an instance of JosOpenSEFSite
* @return sefResolver
*/
function sefResolver( &$db, &$config, &$site ) {
$this->sefObject( $db, $config, $site );
$this->params = array();
$this->log = array();
$this->status = 200;
}
/**
* call automapper
*
* @param object $object
*/
function setAutoMapper( &$object ) {
$this->automapper =& $object;
}
/**
* define the output
*
* @return array
*/
function getOutput() {
return $this->output;
}
/**
* define status
*
* @return string
*/
function getStatus() {
return $this->status;
}
/**
* get querystring
*
* @return array
*/
function getQueryString() {
if (is_array( $this->params ) && count( $this->params ) > 0) {
$result = array();
foreach ($this->params as $param => $value)
# fixed by tvlgiao {{{
if (is_array($value))
foreach ($value as $i => $v) $result[] = "{$param}[$i]=$v";
else
# }}}
$result[] = $param . '=' . $value;
return implode( '&', $result );
}
return '';
}
/**
* not yet
*
*/
function loadCaches() {
// TODO: PHP serialization?
//require_once( $sefAdminPath . '/cache.aliases.php' );
//require_once( $sefAdminPath . '/cache.rules.php' );
}
/**
* URL normalization
*
* @param unknown_type $url
*/
function parse( $url ) {
if (strpos( $url, '?' ) !== false) {
$split = explode( '?', $url, 2 );
$this->output = $split[0];
if (count( $split ) > 1) {
parse_str( $split[1], $this->params );
}
} else {
$this->output = $url;
}
}
/**
* Resolve URL
*
* @param string $url
* @return boole
*/
function resolve( $url ) {
$input = $this->site->getRelativeURL( $url );
if ($this->debug) {
$this->log( '--- Initializing SEF resolver: ' . get_class( $this ) );
$this->log( "'$url' => '$input' (relative site URL)" );
}
$resolved = $this->input( $input );
if (!$resolved || $this->status >= 400) {
if (!empty( $this->config->validate_level )) {
// 3PD and site admins can use the following to check for error
// 404 in, for example, modules or the site template.
if (!defined( '_SEF_404_NOTFOUND' ))
define( '_SEF_404_NOTFOUND', 1 );
if (!$this->debug) {
$this->error404( $input );
} else {
$this->log( '--- Error: 404 Not Found' );
}
}
return;
}
else if (sefIsExternalURL( $this->output )) {
// External address; clean up output buffers and send an HTTP redirect.
if (!$this->debug) {
while (@ob_end_clean());
if ($this->status != 200 && !headers_sent())
header( 'HTTP/1.1 301 Moved Permanently' );
die( mosRedirect( $this->output ) );
} else {
$this->log( '--- Redirecting to: ' . $this->output );
}
}
else if (sefIsJoomlaURL( $this->output )) {
if ($this->debug) {
$params = $this->getQueryString();
$this->log( '--- Internal URL: ' . $this->output . ($params ? '?' . $params : '') );
}
$this->activate();
return true;
}
}
/**
* Input
*
* @param string $input
* @param array $params
* @return result
*/
function input( $input, $params = null ) {
global $opensef_components;
$this->input = $input;
$this->parse( $this->input );
if ($this->debug) {
$msg = "--- Resolving: '$this->input'";
//if ($this->input != $this->output);
$this->log( $msg );
}
// Check whether Search Engine Friendly URLs have been enabled in Joomla's
// Global Configuration. This setting is found under the SEO tab.
if (!empty( $GLOBALS['mosConfig_sef'] ) && sefIsJoomlaSEF( $this->output )) {
// TODO: write code to preserve any extra URL parameters.
$url = '/' . sefRewriteFromJoomlaSEF( $this->output );
$this->parse( $url );
if ($this->debug) $this->log( "'$this->input' => '$url' (Joomla SEF)" );
$this->input = $url;
}
if (eregi('banners',$this->input)) {
if ($this->debug)
$this->log( "'$this->output' is an internal Bannerlink Joomla URL" );
if (($result = $this->resolveJoomlaURL()))
return $result;
}
if (eregi('weblinks',$this->input)) {
if ($this->debug)
$this->log( "'$this->output' is an internal Weblink Joomla URL" );
if (($result = $this->resolveJoomlaURL()))
return $result;
}
if (eregi('newsfeeds',$this->input)) {
if ($this->debug)
$this->log( "'$this->output' is an internal Newsfeed Joomla URL" );
if (($result = $this->resolveJoomlaURL()))
return $result;
}
if (eregi('wrapper',$this->input)
&& !eregi('wrapper.html',$this->input)
) {
if ($this->debug)
$this->log( "'$this->output' is an internal Wrapper Joomla URL" );
if (($result = $this->resolveJoomlaURL()))
return $result;
}
// JOOMLA URL
if (sefIsJoomlaURL( $this->output )) {
if ($this->debug)
$this->log( "'$this->output' is an internal Joomla URL" );
if (($result = $this->resolveJoomlaURL()))
return $result;
}
// ALIAS LOOKUP
if (($alias = $this->lookupIncoming( $this->output )) != null) {
if ($this->debug)
$this->log( "'$this->output' => '$alias->internal' (incoming alias lookup)" );
if (($result = $this->resolveAlias( $alias ))) {
$this->aliasObj = $alias;
if (!sefIsExternalURL( $this->output ))
$this->parse( '/' . $this->output );
return $result;
}
}
else if ($this->debug) {
$this->log( "'$this->output' not matched by incoming alias lookup" );
}
// WEBSITE ROOT
if ($this->output == '/') {
if ($this->debug)
$this->log( "'$this->output' is the root URL for the site" );
return true;
}
// COMPONENT LOOKUP
if(count($opensef_components) > 0){
foreach($opensef_components as $components){
$temp = split("/", $this->output);
if ( $temp[1] == $components->alias ) {
if ($this->debug)
$this->log( "'$this->output' is a component URL ($components->component)" );
if (($result = $this->resolveComponent( $components ))) {
$this->parse( $this->output );
return $result;
}
}
}
}
else if ($this->debug) {
$this->log( "'$this->output' not matched by component alias lookup" );
}
// If we've reached the end of the rewriting process without a result,
// we need to let the caller know in order to do URL Validation.
if ($this->debug)
$this->log( "'$this->output' was NOT resolved" );
if (!empty( $this->config->record_invalid_urls )) {
if ($this->debug)
$this->log( "'$this->output' recorded as invalid URL" );
JosOpenSEFAlias::recordHit( $this->site->id, $this->output, false );
}
return false;
}
/**
* Automap URL
*
* @param string $url
* @return result
*/
function mapContent( $url ) {
if (is_object( $this->automapper ))
return $this->automapper->map( $url );
return null;
}
/**
* Check the internal URL if exist in the Aliases
*
* @param string $lookup
* @return result
*/
function lookupIncoming( $lookup ) {
return JosOpenSEFAlias::lookupIncoming( $this->site->id, $lookup );
}
/**
* Check the external URL if exist in the Aliases
*
* @param string $lookup
* @return result
*/
function lookupOutgoing( $lookup ) {
return JosOpenSEFAlias::lookupOutgoing( $this->site->id, $lookup );
}
/**
* Resolve the Alias
*
* @param object $alias
* @return result
*/
function resolveAlias( &$alias ) {
if (strlen( $alias->internal ) > 0
&& strlen( $alias->external ) > 0
&& !empty( $alias->published )
&& !empty( $alias->valid )) {
$this->output = $alias->internal;
if (!empty( $this->config->record_hits )) {
if ($this->debug)
$this->log( "'$alias->external' hit count increased (record hits enabled)" );
$alias->hit();
}
} else {
if ($this->debug) {
$errors = array();
if (strlen( $alias->internal ) == 0)
$errors[] = "no internal URL";
if (strlen( $alias->external ) == 0)
$errors[] = "no external URL";
if (empty( $alias->valid ))
$errors[] = "invalid";
if (empty( $alias->published ))
$errors[] = "not published";
$this->log( "'$alias->external' fails URL validation (" .
implode( ', ', $errors ) . ')' );
}
$this->status = 404;
if (!empty( $this->config->record_hits )
|| !empty( $this->config->record_invalid_urls )) {
if ($this->debug)
$this->log( "'$alias->external' hit count increased (record hits or record invalid enabled)" );
$alias->hit();
}
}
return true;
}
/**
* Resolve Components
*
* @param object $c
* @return result
*/
function resolveComponent( $c ) {
$component = str_replace( 'com_', '', $c->component );
$params = substr( $this->output, strlen( '/' . $c->alias . '/' ) );
$this->output = '/index.php?option=com_' . $component . '&Itemid=' . $c->menu_id;
if ($params) {
sefLoadExtension( $component );
if ($component=="poll"){
$class = 'sef_onfly_poll';
}
else if ($component=="contact"){
$class = 'sef_onfly_contact';
}
else {
$class = 'sef_' . $component;
}
if ($this->config->use_sef_ext == "1"
&& class_exists( $class )
&& is_callable( array($class, 'revert') )) {
$pos = substr_count( $c->alias, '/' );
$url_array = array_merge(
array('component'),
explode( '/', $c->alias ),
explode( '/', $params )
);
$sef_ext = new $class();
$url = $sef_ext->revert($url_array, $pos);
$this->output .= ($url[0] != '&' ? '&' . $url : $url);
} else {
$this->output .= '&' . str_replace( '/', '&', str_replace( ',', '=', $params ) );
}
}
return true;
}
/**
* Resolve Jommla URL
*
* @return boole
*/
function resolveJoomlaURL() {
// Special case for calls to just index.php
if ($this->input == '/index.php' && !empty( $this->config->use_canonical_urls ) ) {
if ( mosGetParam( $_POST, 'task', '' ) == 'save'
OR mosGetParam( $_POST, 'task','') == 'apply_new'
OR mosGetParam( $_POST, 'ff_task','') != ''
OR mosGetParam( $_POST, 'func','') != ''
OR mosGetParam( $_GET, 'func','') != ''
OR mosGetParam( $_POST, 'task','') == 'popup'
OR mosGetParam( $_GET, 'task','') == 'popup'
OR mosGetParam( $_GET, 'action','') != '' ) {
return true;
}
if (eregi("add",mosGetParam( $_POST, 'task','')) || eregi("save",mosGetParam( $_POST, 'task',''))) {
return true;
}
$this->output = $this->site->getSiteURL() . '/';
$params = $this->getQueryString();
$this->output .= ($params ? '?' . $params : '');
$this->status = 301; // Permanent redirect
if ($this->debug)
$this->log( "'/index.php' => '$this->output' (enforce canonical URLs)" );
return true;
}
if (array_key_exists( 'option', $this->params )) {
$option = $this->params['option'];
if ($option == 'com_weblinks' && $this->params['task'] != 'edit' && $this->params['task'] != 'new') {
if ($this->debug)
$this->log( "'$this->input' is a Joomla Weblink URL" );
return $this->resolveContentURL();
}
if ($option == 'com_newsfeeds' && $this->params['task'] != 'edit' && $this->params['task'] != 'new') {
if ($this->debug)
$this->log( "'$this->input' is a Joomla Newsfeed URL" );
return $this->resolveContentURL();
}
if ($option == 'com_wrapper' && $this->params['task'] != 'edit' && $this->params['task'] != 'new') {
if ($this->debug)
$this->log( "'$this->input' is a Joomla Wrapper URL" );
return $this->resolveContentURL();
}
if ($option == 'com_banners' && $this->params['task'] != 'edit' && $this->params['task'] != 'new') {
if ($this->debug)
$this->log( "'$this->input' is a Joomla Banner URL" );
return $this->resolveContentURL();
}
if ($option == 'com_content' && isset($this->params['pop']) == '1' OR isset($this->params['do_pdf']) == '1') {
return true;
}
if ($option == 'com_content' && $this->params['task'] != 'edit' && $this->params['task'] != 'new' && $this->params['task'] != 'emailsend' && $this->params['task'] != 'emailform' ) {
if ($this->debug)
$this->log( "'$this->input' is a Joomla content URL" );
return $this->resolveContentURL();
} else {
if ($this->debug)
$this->log( "'$this->input' is a Joomla component URL ($option)" );
// TODO: URL Validation in lockdown mode.
return true;
}
}
// If we got here, we're probably dealing with some special URL
// such as registration etc.
return true;
}
/**
* Resolve a Content URL
*
* @return boole
*/
function resolveContentURL() {
global $database;
$params =& $this->params;
if (array_key_exists( 'task', $params )
&& ($params['task'] == 'new'
|| $params['task'] == 'edit'
|| $params['task'] == 'emailsend'
|| $params['task'] == 'emailform')) {
if ($this->debug)
$this->log( "'$this->output' is a Joomla content editing URL, no further action" );
return true;
}
// TODO: take into account any non-Joomla URL parameters in the content URL.
// This will require reconstructing the internal URL from predefined set of params.
// Strip preceding slash and do a reverse alias lookup
$url = substr( $this->input, 1 );
$alias = $this->lookupOutgoing( $url );
if ($alias != null) {
if ($this->debug) {
$url = ($alias->external ? $alias->external : $alias->internal);
$this->log( "'$this->input' => '$url' (reverse alias lookup)" );
}
if (!empty( $alias->valid ) && $alias->external != ''
&& !empty( $this->config->use_canonical_urls )) {
$this->output = $this->site->getSiteURL();
$this->output .= $alias->external;
$this->status = 301; // Permanent redirect
if ($this->debug)
$this->log( "'$this->input' => '$this->output' (enforcing canonical URLs)" );
return true;
}
if (!empty( $this->config->record_hits )) {
if ($this->debug)
$this->log( "'$this->input' hit count increased (record hits enabled)" );
$alias->hit();
}
if (empty( $alias->valid )) {
if ($this->debug)
$this->log( "'$this->input' fails URL validation (marked as invalid)" );
$this->status = 404;
return true;
}
}
else if (!empty( $this->config->use_automap )) {
$alias = $this->mapContent( $url );
if (is_object( $alias ) && $alias->external != ''
&& !(empty( $alias->valid ) || empty( $alias->published ))) {
if ($this->debug)
$this->log( "'$this->input' => '$alias->external' (automapping)" );
if (!empty( $this->config->use_automap_redirect )) {
$this->output = $this->site->getSiteURL();
$this->output .= $alias->external;
return true;
}
}
else if ($this->debug) {
$this->log( "'$this->input' failed automapping but was recorded" );
$this->status = 404;
} else {
$this->status = 404;
}
}
else if (!empty( $this->config->record_unmapped_urls ) && !empty( $this->config->use_automap ) ) {
JosOpenSEFAlias::recordHit( $this->site->id, $this->input, true );
// TODO: URL Validation in lockdown mode.
if ($this->debug)
$this->log( "'$this->input' recorded as undefined URL" );
$this->status = 404;
return true;
}
else if ( empty( $this->config->use_automap ) ) {
$sefResolver =& sefLoadResolver( $database, $this->config, $this->site_id );
$sefAutoMapper =& sefLoadAutoMapper( $database, $this->config, $this->site_id );
$sefResolver->setAutoMapper( $sefAutoMapper );
$alias = $sefResolver->mapContent( $url );
}
if (strlen( $alias->external ) > 0){
return true;
} else {
$this->status = 404;
return true;
}
}
/**
* Use the URL
*
*/
function activate() {
global $REQUEST_URI, $QUERY_STRING;
$url = $this->output;
$queryStr = $this->getQueryString();
$urlInfo = explode( '?', $url, 2 );
$url = $urlInfo[0];
if (count( $urlInfo ) > 1) {
$queryStr = (!$queryStr ? $urlInfo[1] :
$urlInfo[1] . "&" . $queryStr);
}
$request = $this->site->base_url . ($url[0] != '/' ? '/' : '') . $url;
if ($queryStr) $request .= '?' . $queryStr;
$_SERVER["REQUEST_URI"] = $REQUEST_URI = $request;
$_SERVER["QUERY_STRING"] = $QUERY_STRING = $queryStr;
$params = array();
parse_str( $queryStr, $params ); // TODO: magic_quotes_gpc?
if (count( $params ) > 0) {
foreach ($params as $name => $value) {
$_GET[$name] = $_REQUEST[$name] = $value;
$GLOBALS[$name] = $value;
}
}
}
/**
* Errro 404 handling
*
* @param string $requestURL
*/
function error404( $requestURL ) {
global $database;
$errorHeader = 'HTTP/1.1 404 Not Found';
$errorURL = $this->config->validate_404_url;
if (!$errorURL) {
$language = ($GLOBALS['mosConfig_lang'] != '' ?
$GLOBALS['mosConfig_lang'] : 'english');
@include_once( 'language/' . $language . '.php' );
while (@ob_end_clean());
header( $errorHeader );
die( defined( '_NOT_EXIST' ) ? _NOT_EXIST : '404 Not Found' );
} else {
if (strpos( $errorURL, '://') === false) {
header( $errorHeader );
$errorURL = str_replace( '&', '&', $errorURL );
if (!eregi("index.php", $errorURL)) {
if ($errorURL != $GLOBALS['mosConfig_live_site'] . $requestURL) {
while (@ob_end_clean());
header( $errorHeader );
die( mosRedirect( $GLOBALS['mosConfig_live_site'] . $errorURL ) );
}
} else {
if ($errorURL != $GLOBALS['mosConfig_live_site'] . '/'.$requestURL) {
while (@ob_end_clean());
header( $errorHeader );
die( mosRedirect( $GLOBALS['mosConfig_live_site'] .'/'.$errorURL ) );
}
}
} else {
// Prevent infinite recursion; trust that the URL the user entered as
// the error page is indeed valid and that Joomla will accept it.
// TODO: multisites support.
if ($errorURL != $GLOBALS['mosConfig_live_site'] . $requestURL) {
while (@ob_end_clean());
header( $errorHeader );
die( mosRedirect( $errorURL ) );
}
}
}
}
}
?>