<?
///Used to handle requests by parsing the uri path piece by piece and applying rules
/**
Various rule calling logic:
- start from base token and work to end token: bob/bill/sue bob -> bill -> sue
- look for rules.php from base till end or until self::$stopRules set to true
- Each time the parser runs through all rules with no success, it will try to move one level up by using the current token as the next directory and looking for a "rules.php" until it finds no more directories to traverse.
- Each time a rule is parsed and matches, the parser reparses all rules unless there are negation flags
@note if you get confused about what is going on with the rules, you can print out both self::$matchedRules and self::$ruleFiles at just about any time
@note if you want to fabricate a request, just modify $_SERVER['REQUEST_URI']; for instance, if you want to run the framework through console, just make sure to set that variable in a script resembling the index.php
Various controller calling logic:
- start from base token and work to end token: bob/bill/sue bob -> bill -> sue
- look for controller.php from base till end or until self::$stopControls set to true
- look for TOKEN.php on final token
- if not on last token and no more directories to traverse, look for CURRENT_TOKEN.php
RequestHandler rules are not meant to change $_GET or $_POST variables, and if such is to be done, it is not done by this file.
*/
class RequestHandler{
static $stopControls;///<stops more controllers from being called; use within controller file
static $stopRules;///<stops more rules from being called; use within rule file
static $urlTokens;///<an array of url path parts; rules can change this array
static $realUrlTokens;///<the original array of url path parts
static $parsedUrlTokens = array();///<used internally
static $unparsedUrlTokens;///<used internally
static $rules;///<used internally
static $ruleFiles;///<files containing rules that have been included, along with rules. Potentially useful for debugging
static $matchedRules;///<list of rules that were matched
static $urlBase;///<the string, untokenized, path of the url. Use $_SERVER to get actual url
static $currentToken;///<used internally; serves as the item compared on token compared rules
///parses url, routes it, then calls off all the controllers until no more or told to stop
static function handle(){
self::parseRequest();
//url corresponds to public file directory, provide file
if(self::$urlTokens[0] == Config::$x['urlInstanceFileToken']){
self::sendFile('instance');
}elseif(self::$urlTokens[0] == Config::$x['urlSystemFileToken']){
self::sendFile('system');
}
self::routeRequest();
Config::loadUserFiles(Config::$x['requestHandlers'],'requestHandling');
self::$unparsedUrlTokens = self::$urlTokens;
//First, see if there is a main site controller
files::inc(config::$x['instanceFolder'].'controllers/controller.php');
//get the optional section controllers
while(self::$unparsedUrlTokens && !self::$stopControls){
if(!self::getTokenFile('controllers','controller')){
if(Config::$x['pageNotFound']){
Config::loadUserFiles(Config::$x['pageNotFound'],'controllers');
exit;
}else{
Debug::throwError('Request handler encountered unresolvable token at controller level.'."\nCurrent token: ".self::$currentToken."\nTokens parsed".print_r(self::$parsedUrlTokens,true));
}
}
}
//get the page controller
}
///internal use. initial breaking apart of url
private static function parseRequest(){
$argStartPos = strpos($_SERVER['REQUEST_URI'],'?');
self::$realUrlTokens = explode('?',$_SERVER['REQUEST_URI'],2);
self::tokenize(self::$realUrlTokens[0]);
//Potentially, the urlTokens will change according to routes, but the real ones may be referenced
self::$realUrlTokens = self::$urlTokens;
}
/// internal use. tokenizes url
/** splits url path on "/"
@param urlDir str path part of url string
*/
private static function tokenize($urlDir){
$tokens = explode('/',$urlDir);
$nonEmptyTokens = array();
foreach($tokens as $token){
if($token){
$nonEmptyTokens[] = $token;
}
}
self::$urlTokens = $nonEmptyTokens;
self::$urlBase = implode('/',self::$urlTokens);
}
///internal use. Parses all current files and rules
/** adds file and rules to ruleFiles and parses all active rules in current file and former files
@param file str file location string
*/
private static function parseRules($file){
global $rules;
self::$ruleFiles[] = array('file'=>$file,'rules'=>$rules);
unset($rules);
//lc = lower case
$lcUrlBase = strtolower(self::$urlBase);
$lcCurrentToken = strtolower(self::$currentToken);
foreach(self::$ruleFiles as $kF=>&$file){
if($file['ignore']){
$file['ignore'] = false;
continue;
}
if($file['rules']){
foreach($file['rules'] as $kR=>&$rule){
unset($matched);
$flags = $rule[2] ? explode(',',$rule[2]) : array();
//parse flags for determining match string
if(!$rule['match']){
if(in_array('regex',$flags)){
$rule['regex'] = true;
$rule['match'] = Tool::pregDelimit($rule[0]);
if(in_array('insensitive',$flags)){
$rule['match'] .= 'i';
}
}else{
if(in_array('insensitive',$flags)){
$rule['match'] = strtolower($rule[0]);
}else{
$rule['match'] = $rule[0];
}
}
if(in_array('token',$flags)){
$rule['token'] = true;
}
}
//determine subject string
if($rule['token']){
$subject = self::$currentToken;
}else{
$subject = self::$urlBase;
}
//test match
if($rule['regex']){
if(preg_match($rule['match'],$subject)){
$matched = true;
}
}else{
if($rule['match'] == $subject){
$matched = true;
}
}
if($matched){
self::$matchedRules[] = $rule;
if($rule['regex']){
$replacement = preg_replace($rule['match'],$rule[1],$subject);
}else{
$replacement = $rule[1];
}
//remake url with replacement
self::tokenize($replacement);
self::$parsedUrlTokens = null;
self::$unparsedUrlTokens = self::$urlTokens;
//apply parse flag
if(in_array('ignore',$flags)){
unset($file['rules'][$kR]);
}elseif(in_array('last',$flags)){
unset(self::$ruleFiles[$kF]);
}elseif(in_array('veryLast',$flags)){
self::$unparsedUrlTokens = array();
}elseif(in_array('nextFile',$flags)){
$file['ignore'] = true;
}
return;
}
}
}
}
unset($rules);
}
///internal use. Gets files and then applies rules for routing
private static function routeRequest(){
if(Files::inc(Config::$x['instanceFolder'].'requestHandling/rules.php',array('rules'))){
global $rules;
self::parseRules(Config::$x['instanceFolder'].'requestHandling/rules.php');
}
while(self::$unparsedUrlTokens && !self::$stopRules){
$file = self::getTokenFile('requestHandling','rules',array('rules'));
if(!$file){
#no more rules
break;
}
self::parseRules($file);
}
self::$parsedUrlTokens = null;
}
///internal use. Gets a file based on next token in the unparsedUrlTokens variable
private static function getTokenFile($prefix,$defaultName,$globalize=null){
self::$parsedUrlTokens[] = self::$currentToken = array_shift(self::$unparsedUrlTokens);
$path = $prefix.'/'.implode('/',self::$parsedUrlTokens);
//if path not directory, possibly is file
if(!is_dir(Config::$x['instanceFolder'].$path)){
$file = Config::$x['instanceFolder'].$path.'.php';
$return = files::inc($file,$globalize);
$return = $return ? $file : $return;
}else{
$file = $return = Config::$x['instanceFolder'].$path.'/'.$defaultName.'.php';
files::inc($file,$globalize);
}
return $return;
}
///internal use. attempts to find non php file and send it to the browser
private static function sendFile($type){
array_shift(self::$urlTokens);
$path = Config::$x['instanceLocation'].'/public/'.$type.'/'.escapeshellcmd(implode('/',self::$urlTokens));
Display::sendFile($path);
}
}