<?php
/*-------------------------------------------------------+
| PHPFusion Content Management System
| Copyright (C) PHP Fusion Inc
| https://phpfusion.com/
+--------------------------------------------------------+
| Filename: Router.php
| Author: Frederick MC Chan (Chan)
| Co-Author: Ankur Thakur
| Co-Author: Takács Ákos (Rimelek)
+--------------------------------------------------------+
| This program is released as free software under the
| Affero GPL license. You can redistribute it and/or
| modify it under the terms of this license which you
| can read by viewing the included agpl.txt or online
| at www.gnu.org/licenses/agpl.html. Removal of this
| copyright header is strictly prohibited without
| written permission from the original author(s).
+--------------------------------------------------------*/
namespace PHPFusion\Rewrite;
/**
* Rewrite API for PHPFusion
* This Rewrite API handles the Permalinks Requests
* and map them to suitable existing URLs in website.
* You can use this API to Add custom rules for handling requests
*/
class Router extends RewriteDriver {
private static $router_instance = NULL;
/**
* True = SEF URL to FilePath
* False = File Path to SEF URL
*
* @var bool
*/
public $routing = TRUE;
/**
* Name of the php file which will be loaded
* for the permalink.
* example: news.php, articles.php
*
* @data_type String
* @access private
*/
private $file_path = "";
/**
* Array of Parameters with their
* actual values.
* example: thread_id => 1, rowstart => 20
*
* @data_type Array
* @access private
*/
private $get_parameters = [];
public function __construct() {
// Pretty URL
$this->requesturi = rtrim(urldecode($this->removeParam(PERMALINK_CURRENT_PATH)), '/');
}
/**
* @param string $url
*
* @return mixed|string
*/
public function removeParam($url) {
$urlArray = explode('?', $url);
if (isset($urlArray)) {
return $urlArray[0];
}
return $url;
}
/**
* @return static|null
*/
public static function getRouterInstance() {
if (self::$router_instance === NULL) {
self::$router_instance = new static();
}
return self::$router_instance;
}
/**
* @return array
*/
public function getFileParams() {
return $this->get_parameters;
}
/**
* @param mixed $file_path
*/
public function setPathtofile($file_path) {
$this->file_path = $file_path;
}
/**
* @param mixed $get_parameters
*/
public function setGetParameters($get_parameters) {
$this->get_parameters = $get_parameters;
}
/**
* Call all the functions to process rewrite detection and further actions.
* This will call all the other functions after all the included files have been included
* and all the patterns have been made.
*
* @access public
*/
public function rewritePage() {
// Import the required Handlers
$this->loadSqlDrivers();
// Include the Rewrites
$this->includeRewrite();
// Import Patterns from DB
$this->importPatterns();
// Prepare Search Strings
$this->prepareSearchRegex();
// Check if there is any Alias matching with current URL
if (!$this->checkAlias()) {
// Check if any Alias Pattern is matching with current URL
if (!$this->checkAliasPatterns()) {
// Check if any Pattern is matching with current URL
$this->checkPattern();
}
}
// If path to File is empty, set a warning
if ($this->file_path == "") {
$this->setWarning(6);
}
if (fusion_get_settings('debug_seo') && iSUPERADMIN && iADMIN) {
$this->displayWarnings();
}
}
/**
* Import the Available Patterns from Database
* This will Import all the available Patterns for the Handlers
* from the Database and put it into $pattern_search and
* $pattern_replace array.
*
* @access private
*/
private function importPatterns() {
if (!empty($this->handlers)) {
$types = [];
foreach ($this->handlers as $value) {
$types[] = "'".$value."'"; // When working on string, the values should be inside single quotes.
}
$types_str = implode(",", $types);
$query = "SELECT r.rewrite_name, p.pattern_type, p.pattern_source, p.pattern_target, p.pattern_cat FROM ".DB_PERMALINK_METHOD." p INNER JOIN ".DB_PERMALINK_REWRITE." r WHERE r.rewrite_id=p.pattern_type AND r.rewrite_name IN(".$types_str.") ORDER BY p.pattern_type";
$this->queries[] = $query;
$result = dbquery($query);
if (dbrows($result)) {
while ($data = dbarray($result)) {
if ($data['pattern_cat'] == "normal") {
$this->pattern_search[$data['rewrite_name']][] = $data['pattern_source'];
$this->pattern_replace[$data['rewrite_name']][] = $data['pattern_target'];
} else if ($data['pattern_cat'] == "alias") {
$this->alias_pattern[$data['rewrite_name']][$data['pattern_source']] = $data['pattern_target'];
}
}
}
}
}
/**
* Checks if there is any matching Alias or not
* This function will check if there is any matching Alias for the URI or not.
*
* @access private
*/
private function checkAlias() {
// Check if there is any Alias matching the current URI
$query = "SELECT * FROM ".DB_PERMALINK_ALIAS." WHERE alias_url=:url LIMIT 1";
$result = dbquery($query, [':url' => stripinput($this->requesturi)]);
$this->queries[] = $query;
if (dbrows($result)) {
$aliasdata = dbarray($result);
// If Yes, then Exploded the corresponding php_url and render the page
if ($aliasdata['alias_php_url'] != "") {
$alias_url = $this->getAliasUrl($aliasdata['alias_url'], $aliasdata['alias_php_url'], $aliasdata['alias_type']);
$url_info = $this->explodeUrl($alias_url);
// File Path (Example: news.php)
$this->file_path = $url_info[0];
if (isset($url_info[1])) {
foreach ($url_info[1] as $paramkey => $paramval) {
$this->get_parameters[$paramkey] = $paramval; // $this->get_parameters['thread_id'] = 1
}
}
// Call the function to set server variables
$this->setVariables();
$this->setWarning(7, $this->requesturi); // Alias Found
return TRUE;
}
} else {
$this->setWarning(1, $this->requesturi); // Alias not found
return FALSE;
}
return NULL;
}
/**
* Get Alias URL for Router
* This function will return an Array of 2 elements for a specific Alias:
* 1. The Permalink URL of Alias
* 2. PHP URL of the Alias
*
* @param string $url The Permalink URL (incomplete)
* @param string $php_url The PHP URL (incomplete)
* @param string $type Type of Alias
*
* @access private
* @return mixed|string
*/
private function getAliasUrl($url, $php_url, $type) {
if (isset($this->alias_pattern) && isset($this->alias_pattern[$type]) && is_array($this->alias_pattern[$type])) {
foreach ($this->alias_pattern[$type] as $search => $replace) {
$search = str_replace("%alias%", $url, $search);
$replace = str_replace("%alias_target%", $php_url, $replace);
if ($search == $this->requesturi) {
return $replace;
}
}
}
return $php_url;
}
/**
* Explodes a URL into Filename and Get Parameters
* This function will explode the URL into its Filename and Get Parameters
* Example: viewthread.php?thread_id=1&rowstart=20
* then :
* array[0] => viewthread.php
* array[1] => array(
* [thread_id] => 1
* [rowstart] => 20
* )
*
* @param string $url The URL
*
* @access private
* @return array
*/
private function explodeUrl($url) {
$param_delimiter = "&";
$url_info = [];
// Explode URL
$pathinfo = explode("?", $url);
// Save the File path in 1st position of array
$url_info[0] = $pathinfo[0];
if (isset($pathinfo[1])) {
// Now calculate the query parameters
$params = explode($param_delimiter, $pathinfo[1]); // 0=>thread_id=1, 1=>pid=25
// Now again explode it with '='
foreach ($params as $paramval) { // 0=>thread_id=1, 1=>pid=25
// bug fix. Sometimes just ?create.
if (strpos($paramval, '=')) {
$get_params = explode("=", $paramval); // thread_id => 1, pid => 25
$url_info[1][$get_params[0]] = $get_params[1];
} else {
$url_info[1][$paramval] = ''; // void all values since there is no value.
}
}
}
return $url_info;
}
/**
* Call the Functions to Set GET Parameters and Query String
* This function will call the functions to set Server GET parameters
* and the QUERY_STRING.
*
* @access private
*/
private function setVariables() {
$this->setServerVars();
$this->setQueryString();
}
/**
* Set the PHP_SELF and SCRIPT_NAME to the suitable filepath.
* This function will set the values of PHP_SELF and SCRIPT_NAME to the suitable
* file name. The filename will be searched in the $pattern_replace array.
* The php filename is found before '?' in the pattern.
*
* @access private
*/
public function setServerVars() {
if (!empty($this->file_path)) {
$_SERVER['PHP_SELF'] = preg_replace("/index\.php/", $this->file_path, $_SERVER['PHP_SELF'], 1);
$_SERVER['SCRIPT_NAME'] = preg_replace("/index\.php/", $this->file_path, $_SERVER['SCRIPT_NAME'], 1);
}
}
/**
* Set the new QUERY_STRING
* This function will set the values of QUERY_STRING to new value
* which is calculated in buildParams().
*
* @access private
*/
public function setQueryString() {
if (!empty($_SERVER['QUERY_STRING'])) {
$_SERVER['QUERY_STRING'] = $_SERVER['QUERY_STRING']."&".$this->buildParams();
} else {
$_SERVER['QUERY_STRING'] = $this->buildParams();
}
}
/**
* Builds the $_GET parameters
* This function will build the GET parameters and also the Query String.
*
* @access private
*/
private function buildParams() {
$total = count($this->get_parameters);
$i = 1;
$query_str = "";
foreach ($this->get_parameters as $key => $val) {
$_GET[$key] = $val; // This is where $_GET is produced.
$query_str .= $key."=".$val;
if ($i < $total) {
$query_str .= "&";
}
$i++;
}
return $query_str;
}
/**
* Checks if there is any matching Alias Pattern or not
* This function will check if there is any matching Alias Pattern for the URI or not.
* Example: If a Thread request is like: "post-your-site-rowstart-20", then it will
* try to find any pattern like: "post-your-site-rowstart-%thread_rowstart%"
*
* @access private
*/
private function checkAliasPatterns() {
if (is_array($this->alias_pattern)) {
$match_found = FALSE;
$alias = "";
foreach ($this->alias_pattern as $type => $alias_patterns) {
foreach ($alias_patterns as $search => $replace) {
$search_pattern = $search;
$search = $this->makeSearchRegex($search, $type);
$search = str_replace("%alias%", "(.*?)", $search);
if (preg_match($search, $this->requesturi, $matches)) {
$alias_pos = $this->getTagPosition($search_pattern, "%alias%");
if ($alias_pos != 0) {
// The Alias is Detected !
$alias = $matches[$alias_pos];
// Now search for this Alias in Database
$query = "SELECT * FROM ".DB_PERMALINK_ALIAS." WHERE alias_url=:url LIMIT 1";
$result = dbquery($query, [':url' => $alias]);
$this->queries[] = $query;
if (dbrows($result)) {
$aliasdata = dbarray($result);
// Replace the %alias_target% in the Replacement pattern
$replace = str_replace("%alias_target%", $aliasdata['alias_php_url'], $replace);
//$replace_with = $replace;
// Replacing Tags with their suitable matches
$replace = $this->replaceOtherTags($type, $search_pattern, $replace, $matches, -1);
$url_info = $this->explodeUrl($replace);
// File Path (Example: news.php)
$this->file_path = $url_info[0];
if (isset($url_info[1])) {
foreach ($url_info[1] as $paramkey => $paramval) {
$this->get_parameters[$paramkey] = $paramval; // $this->get_parameters['thread_id'] = 1
}
}
// Call the function to set server variables
$this->setVariables();
$match_found = TRUE;
break;
}
}
}
}
}
if ($match_found == TRUE) {
$this->setWarning(8, $alias); // Alias Pattern Found
return TRUE;
} else {
$this->setWarning(2, $this->requesturi); // Alias Pattern Not Found
return FALSE;
}
}
return FALSE;
}
/**
* Builds the Regex pattern for a specific Type string
* This function will build the Regex pattern for a specific string, which is
* passed to the function.
*
* @param string $pattern
* @param string $type
*
* @return string
*/
protected function makeSearchRegex($pattern, $type) {
$regex = $pattern;
if (isset($this->rewrite_code[$type]) && isset($this->rewrite_replace[$type])) {
$regex = str_replace($this->rewrite_code[$type], $this->rewrite_replace[$type], $regex);
}
$regex = $this->cleanRegex($regex);
return "/^".$regex."$/";
}
/**
* Builds the Regular Expressions Patterns
* This function will create the Regex patterns and will put the built patterns
* in $patterns_regex array. This array will then be used in preg_match function
* to match against current request.
* Note: Using ^ and $ made us to match exact string so that it doesn't match sub-patterns
*
* @access private
*/
private function checkPattern() {
$match_found = FALSE;
if (is_array($this->pattern_search)) {
foreach ($this->pattern_search as $type => $RawSearchPatterns) {
if (!empty($RawSearchPatterns) && is_array($RawSearchPatterns)) {
foreach ($RawSearchPatterns as $key => $search) {
if ($match_found == FALSE) {
if (isset($this->pattern_replace[$type][$key]) && isset($this->pattern_search[$type][$key])) {
$search_pattern = $this->pattern_search[$type][$key];
$replace_pattern = $this->pattern_replace[$type][$key];
if (isset($this->rewrite_code[$type]) && isset($this->rewrite_replace[$type])) {
$search = str_replace($this->rewrite_code[$type], $this->rewrite_replace[$type], $search);
}
$search = "~".$this->cleanRegex($search)."$~i";
if (preg_match($search, $this->requesturi, $matches)) {
$url_info = $this->explodeUrl($replace_pattern);
$this->file_path = str_replace("../", "", $url_info[0]);
preg_match_all($search, $this->requesturi, $url_matches, PREG_SET_ORDER);
if (isset($url_info[1])) { // indicate has $_GET request
/**
* File path is in search pattern
*/
preg_match_all("~%(.*?)%~i", $search_pattern, $tag_matches);
$tag_values = [];
if (!empty($tag_matches[0])) {
$tagData = array_combine(range(1, count($tag_matches[0])), array_values($tag_matches[0]));
$tagRequests = array_combine(range(1, count($tag_matches[0])), array_values($tag_matches[1]));
foreach ($tagData as $tagKey => $tagVal) {
$tag_values[$tagRequests[$tagKey]] = $matches[$tagKey];
}
$this->data_statements[$type][] = $tag_values;
$urlParams = array_combine(array_values($tagData), array_values($tag_values));
}
/**
* Read the Request URL pattern
*/
if (isset($url_info[1]) && !empty($urlParams)) {
$parameters = [];
foreach ($url_info[1] as $paramKey => $paramVal) {
if (!$paramVal) {
$paramVal = TRUE;
}
$value = (isset($tag_values[$paramKey])) ? $tag_values[$paramKey] : $paramVal;
// If key is not val, (i.e. such as post_id=%thread_id%) the below will find and insert value
if (stristr($value, "%")) {
$value = (isset($urlParams[$value])) ? $urlParams[$value] : $paramVal;
}
$parameters[$paramKey] = $value;
}
$this->get_parameters = $parameters;
}
$this->setVariables();
$this->setWarning(9, $this->requesturi); // Regex pattern found
break;
}
}
} else {
die("You do not have correct Permalink rules Driver. See that your driver is properly set!");
}
} else {
$this->setWarning(3, $this->requesturi); // Regex pattern not found
}
}
}
}
}
}
/**
* Show Warnings or Debugging Information
* This function will show the Warnings or Debugging Information
* if Warnings are enabled or if Debug Mode is enabled.
*
* @access private
*/
private function displayWarnings() {
echo "<div class='rewrites-queries' style='padding: 10px 10px 10px 10px; border: 1px double #000; background-color: #fbfbfb; line-height: 15px;'>\n";
echo "<strong>Queries which were made for Rewriting:</strong><br /><br />\n";
foreach ($this->queries as $query) {
echo $query.";<br />\n";
}
add_to_footer("<script type='text/javascript'>
var el = document.getElementById('rewritestoggledebugdiv');
if (el) {
el.addEventListener('click', function() {
var el2 = document.getElementById('rewritesdebuginfo');
if (el2.style.display === 'none') {
el2.style.display = 'block';
} else {
el2.style.display = 'none';
}
});
}
</script>");
echo "<input type='button' class='btn btn-default btn-sm' value='Toggle Rewriting Debug Information' id='rewritestoggledebugdiv' />\n<br />";
echo "Path to File: <strong>".$this->file_path."</strong><br />";
echo "Request URI: <strong>".$this->requesturi."</strong><br />";
echo "<div id='rewritesdebuginfo' style='display: ".($this->file_path != "" ? "none" : "block").";'>\n";
foreach ($this->warnings as $key => $val) {
echo (intval($key) + 1).". ".$val."<br />\n";
}
echo "<hr style='border-color:#000;' />\n";
echo "Handlers Stack = Array (<br />";
foreach ($this->handlers as $key => $name) {
echo " [".$key."] => ".$name."<br />";
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Alias Patterns = Array (<br />";
foreach ($this->alias_pattern as $type => $tag) {
echo " [".$type."] => Array (<br />";
foreach ($tag as $key => $val) {
echo " [".$key."] => ".$val."<br />";
}
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Rewrite Codes = Array (<br />";
foreach ($this->rewrite_code as $type => $tag) {
echo " [".$type."] => Array (<br />";
foreach ($tag as $key => $val) {
echo " [".$key."] => ".$val."<br />";
}
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Rewrite Replace = Array (<br />";
foreach ($this->rewrite_replace as $type => $tag) {
echo " [".$type."] => Array (<br />";
foreach ($tag as $key => $val) {
echo " [".$key."] => ".$val."<br />";
}
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Pattern Search = Array (<br />";
foreach ($this->pattern_search as $type => $tag) {
echo " [".$type."] => Array (<br />";
foreach ($tag as $key => $val) {
echo " [".$key."] => ".$val."<br />";
}
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Pattern Replace = Array (<br />";
foreach ($this->pattern_replace as $type => $tag) {
echo " [".$type."] => Array (<br />";
foreach ($tag as $key => $val) {
echo " [".$key."] => ".$val."<br />";
}
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Pattern Regex = Array (<br />";
foreach ($this->patterns_regex as $type => $tag) {
echo " [".$type."] => Array (<br />";
foreach ($tag as $key => $val) {
echo " [".$key."] => ".$val."<br />";
}
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Module Property Information:<br />";
foreach ($this->pattern_tables as $type => $val) {
$propHTML = '';
foreach ($val as $val_pattern => $arrayProp) {
$propHTML .= " - $val_pattern will be translated on ".$arrayProp['table']." WHERE ".$arrayProp['primary_key']." is equals to $val_pattern<br/>\n";
}
echo " ".$type." rewrite module <br/>".$propHTML."<br />";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "Data Cache = Array (<br />";
foreach ($this->data_statements as $type => $info) {
echo " [".$type."] => Array (<br />";
foreach ($info as $id => $dbinfo) {
echo " [".$id."] => Array (<br />";
foreach ($dbinfo as $colname => $colvalue) {
echo " [".$colname."] => ".$colvalue."<br />";
}
echo " )<br />\n";
}
echo " )<br />\n";
}
echo ");<br />\n";
echo "<hr style='border-color:#000;' />\n";
echo "\$_GET Parameters = Array (<br />";
foreach ($this->get_parameters as $key => $val) {
echo " [".$key."] => ".$val."<br />";
}
echo ");<br />\n";
echo "</div>\n";
echo "</div>\n";
}
/**
* Index Include File
* Returns the filename of the php file which will be included.
* If this file is blank, index.php will redirect to 404 error page
*
* @access public
*/
public function getFilePath() {
return $this->file_path;
}
/**
* Get the Current URL
*
* @return string
*/
public function getCurrentURL() {
return $this->file_path.(!empty($this->get_parameters) ? '?'.http_build_query($this->get_parameters, '', '&') : '');
}
/**
* @return array
* @deprecated use getFileParams()
*/
public function get_FileParams() {
return $this->get_parameters;
}
}