Location: PHPKode > projects > Joomla SEF / SEO - extending OpenSEF > core/sef.resolver.php
<?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( '&amp;', '&', $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 ) );
        }
      }
    }
  }

}
?>
Return current item: Joomla SEF / SEO - extending OpenSEF