PHP Classes

File: controllers/controller.url.php

Recommend this page to a friend!
  Classes of Kristo Vaher  >  Wave Framework  >  controllers/controller.url.php  >  Download  
File: controllers/controller.url.php
Role: Class source
Content type: text/plain
Description: URL Controller
Class: Wave Framework
MVC framework for building Web sites and APIs
Author: By
Last change: It is now possible to define a sitemap URL that does not return a View or even HTML. This is useful for API requests that require a specific target URL without GET parameters. It is now also possible to synchronize timestamps when creating an API session. Factory MVC loading now uses a wrapper method that reduces near-duplicate code. Version numbers of API and system version are now consistent with one another.
Date: 8 years ago
Size: 20,431 bytes
 

Contents

Class file image Download
<?php

/**
 * Wave Framework <http://www.waveframework.com>
 * URL Controller
 *
 * Wave Framework comes with a URL Controller and a sitemap system that is used to build a 
 * website on Wave Framework. This URL controller is entirely optional and can be removed 
 * from a system if you plan to implement your own URL Controller or simply use Wave 
 * Framework for API, without a website.
 *
 * @package    Tools
 * @author     Kristo Vaher <kristo@waher.net>
 * @copyright  Copyright (c) 2012, Kristo Vaher
 * @license    GNU Lesser General Public License Version 3
 * @tutorial   /doc/pages/guide_url.htm
 * @since      1.0.0
 * @version    3.7.0
 */

class WWW_controller_url extends WWW_Factory {

	/**
	 * This method is called by Data Handler to find the View that is being requested based 
	 * on the URL that (usually) comes from the user agent request URL.
	 *
	 * @param array $input input data sent to controller
	 * @input [url] This is URL to be solved
	 * @return array through returnViewData method
	 * @output [controller] controller to use for View
	 * @output [controller-method] what controller method to use
	 * @output [view-method] what View method to use
	 * @output [subview] subview variable
	 * @output [hidden] if the page should be hidden from menu lists
	 * @output mixed might include many other View-specific variables
	 */
	public function solve($input){
		
		// Default view is loaded from State (this is loaded when no URL is defined)
		$view404=$this->getState('404-view');
		
		// Custom request URL can be used, this is required
		if(isset($input['url'])){
			// Request string is loaded from input
			$request=$input['url'];
		} else {
			// Formatting and returning the expected result array
			return $this->returnViewData(array('view'=>$view404,'header'=>'HTTP/1.1 404 Not Found'));
		}
		
		// Web root is the base directory of the website
		$webRoot=$this->getState('url-web');
		
		// This setting will force that even the first language (first in languages array) has to be represented in URL
		$enforceSlash=$this->getState('enforce-url-end-slash');
		// This setting means that URL has to end with a slash
		$enforceLanguageUrl=$this->getState('enforce-first-language-url');
		
		// Default language is loaded from State
		$language=$this->getState('language');
		// List of defined languages is loaded from State
		$languages=$this->getState('languages');
		
		// Default view is loaded from State (this is loaded when no URL is defined)
		$viewHome=$this->getState('home-view');
		// By default it is assumed that home view is used
		$view=$viewHome;
		
		// To solve the request GET is separated from URL nodes
		$requestNodesRaw=explode('?',$request,2);
		
		// This array stores all the URL nodes that will be matched against sitemap
		$urlNodes=array();
		
		// This is used for testing if the returned URL should be home or not
		$returnHome=false;
		
		// If there is no request URL set
		if($requestNodesRaw[0]=='' || $requestNodesRaw[0]=='/'){
		
			// If first language code has to be defined in URL, system redirects to URL that has it, otherwise returns home view data
			if($enforceLanguageUrl==true){
				// User agent is redirected to URL that has just the language node set
				if(isset($requestNodesRaw[1])){
					return array('www-permanent-redirect'=>$webRoot.$language.'/?'.$requestNodesRaw[1]);
				} else {
					return array('www-permanent-redirect'=>$webRoot.$language.'/');
				}
			} else {
				// Expecting to return Home view
				$returnHome=true;
			}
			
		} else {
		
			// Request is exploded into an array that will be looped to find proper view
			$requestNodes=explode('/',urldecode($requestNodesRaw[0]));
			// If slash is enforced at the end of the URL then user agent is redirected to such an URL
			if($enforceSlash==true && end($requestNodes)!=''){
				// If GET variables were set, system redirects to proper URL that has a slash in the end and appends the GET variables
				if(isset($requestNodesRaw[1])){
					return array('www-permanent-redirect'=>$webRoot.$requestNodesRaw[0].'/?'.$requestNodesRaw[1]);
				} else {
					return array('www-permanent-redirect'=>$webRoot.$requestNodesRaw[0].'/');
				}
			}
			
			// Looping through all the URL nodes from the request
			foreach($requestNodes as $nodeKey=>$node){
			
				// As long as the URL node in the request is not empty, it is taken into account for finding the proper view
				if($node!='' || !isset($requestNodes[$nodeKey+1])){
					
					// If this is the first request node and it is found in languages array
					if($nodeKey==0 && in_array($node,$languages)){
					
						// Language was found and is defined as the request language
						$language=$node;
						// If this is the first language and language node is not required in URL, user agent is redirected to a URL without it
						if($enforceLanguageUrl==false && $language==$languages[0]){
							// We unset the first node, as it was not required
							unset($requestNodes[$nodeKey]);
							// If GET variables were set, system redirects to URL without the language and appends the GET variables
							if(isset($requestNodesRaw[1])){ 
								return array('www-permanent-redirect'=>$webRoot.implode('/',$requestNodes).'?'.$requestNodesRaw[1]);
							} else {
								return array('www-permanent-redirect'=>$webRoot.implode('/',$requestNodes));
							}
						}
						
					} else {
					
						// If language node is required in URL and the first request node was not a language, it is added and user agent is redirected
						if($nodeKey==0 && $enforceLanguageUrl==true){
							// User agent is redirected to the same URL as before, but with the default language node added
							if(isset($requestNodesRaw[1])){
								return array('www-permanent-redirect'=>$webRoot.$language.'/'.$requestNodesRaw[0].'?'.$requestNodesRaw[1]);
							} else {
								return array('www-permanent-redirect'=>$webRoot.$language.'/'.$requestNodesRaw[0]);
							}
						} else {
							// If language node is set in request, but has no second URL node set, it is also assumed that it is default view
							if($requestNodes[0]==$language && $nodeKey==1 && (!isset($requestNodes[1]) || $requestNodes[1]=='')){
								// Expecting to return Home view
								$returnHome=true;
							} else {
								// Every other request node is added to the array that looks for matching URL from URL Map
								if($node!=''){
									$urlNodes[]=$node;
								}
							}
						}
						
					}
					
				} else {
					// Formatting and returning the expected result array
					return $this->returnViewData(array('view'=>$view404,'language'=>$language,'header'=>'HTTP/1.1 404 Not Found'));
				}

			}
			
		}
		
		// All nodes of URL's that were not found as modules are stored here
		$dynamicUrl=array();
		
		// Number of URL nodes
		$urlNodeCount=count($urlNodes);
		
		// URL Map is stored in this array
		$siteMap=$this->getSitemapRaw($language);
		if(!$siteMap){
			// Formatting and returning the expected result array
			return $this->returnViewData(array('view'=>$view404,'language'=>$language,'header'=>'HTTP/1.1 404 Not Found'));
		}
		
		// Array that stores information from sitemap file
		if(isset($siteMap[$viewHome])){
			$siteMapInfo=$siteMap[$viewHome];
		} else {
			trigger_error('Configuration is incorrect, cannot find Home view sitemap data',E_USER_ERROR);
		}
		
		// If home is not expected to be returned
		if(!$returnHome){
		
			// This will be the actual URL match
			$match=false;
			
			// System loops through URL nodes and attempts to find a match in URL Map
			foreach($siteMap as $key=>$settings){
			
				// URL length needs to match the URL declaration in Sitemap
				if($urlNodeCount!=count($settings['nodes'])){
				
					// Unsetting incompatible node
					unset($siteMap[$key]);
					
				} else {
				
					// Testing every URL node
					for($i=1;$i<=$urlNodeCount;$i++){
					
						// This is the actual URL node value
						$matchKey=$i-1;
				
						// If the node is not dynamic
						if($settings['nodes'][$matchKey][0]!=':'){
							if(!preg_match('/^'.preg_quote($settings['nodes'][$matchKey],'/').'$/ui',$urlNodes[$matchKey])){
								unset($siteMap[$key]);
								break;
							}
						} else {
							// If this is set to non-false, then dynamic URL value will be added
							$dynamicAdd=false;
							// Matching the dynamic URL's
							$matched=explode(':',$settings['nodes'][$matchKey],3);
							switch($matched[1]){
								case 'numeric':
									if($matched[2]==''){
										if(!preg_match('/^[0-9]*$/i',$urlNodes[$matchKey])){
											unset($siteMap[$key]);
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									} else {
										// Finding the match parameters
										$parameters=explode('-',$matched[2]);
										if(!preg_match('/^[0-9\-\_]*$/i',$urlNodes[$matchKey]) || ($parameters[0]!='*' && intval($urlNodes[$matchKey])<$parameters[0]) || ($parameters[1]!='*' && intval($urlNodes[$matchKey])>$parameters[1])){
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									}
									break;
								case 'alpha':
									if($matched[2]==''){
										if(!preg_match('/^[[:alpha:]\-\_]*$/ui',$urlNodes[$matchKey])){
											unset($siteMap[$key]);
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									} else {
										// Finding the match parameters
										$parameters=explode('-',$matched[2]);
										if(!preg_match('/^[[:alpha:]\-\_]*$/ui',$urlNodes[$matchKey]) || ($parameters[0]!='*' && strlen($urlNodes[$matchKey])<$parameters[0]) || ($parameters[1]!='*' && strlen($urlNodes[$matchKey])>$parameters[1])){
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									}
									break;
								case 'alphanumeric':
									if($matched[2]==''){
										if(!preg_match('/^[[:alnum:]\-\_]*$/ui',$urlNodes[$matchKey])){
											unset($siteMap[$key]);
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									} else {
										// Finding the match parameters
										$parameters=explode('-',$matched[2]);
										if(!preg_match('/^[[:alnum:]\-\_]*$/ui',$urlNodes[$matchKey]) || ($parameters[0]!='*' && strlen($urlNodes[$matchKey])<$parameters[0]) || ($parameters[1]!='*' && strlen($urlNodes[$matchKey])>$parameters[1])){
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									}
									break;
								case 'fixed':
									if($matched[2]!=''){
										// Finding the match parameters
										$matches=explode(',',$matched[2]);
										if(!in_array($urlNodes[$matchKey],$matches)){
											unset($siteMap[$key]);
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									} else {
										unset($siteMap[$key]);
									}
									break;
								case 'any':
									// Any character is accepted
									if($matched[2]!=''){
										// In case the regular expression includes a colon symbol
										if(isset($matched[3])){
											$matched[2].=$matched[3];
										}
										// Testing for regular expression match
										if(!preg_match($matched[2],$urlNodes[$matchKey])){
											unset($siteMap[$key]);
											break;
										} else {
											$dynamicAdd=$urlNodes[$matchKey];
										}
									} else {
										$dynamicAdd=$urlNodes[$matchKey];
									}
									break;
							}
							// If a new dynamic node was found
							if($dynamicAdd!==false){
								$siteMap[$key]['dynamic-url'][$matchKey]=$dynamicAdd;
							} else {
								break;
							}
						}
						
						// If the cycle has not broken, then match has been found
						if($i==$urlNodeCount){
							// If user agent setting is set then this is checked, otherwise match is found
							if(isset($siteMap[$key]['user-agent'])){
								if(!preg_match($siteMap[$key]['user-agent'],$this->getState('client-user-agent'))){
									unset($siteMap[$key]);
									break;
								} else {
									$match=$siteMap[$key];
								}
							} else {
								$match=$siteMap[$key];
							}
							break;
						}
						
					}
					
				}
				
				// Match has been found
				if($match){
					break;
				}
				
			}
			
			// If all URL nodes have been matched and there's still a URL in the Sitemap array
			if($match){
				// Getting sitemap info from matched sitemap node
				$siteMapInfo=$match;
				// Resetting the indexes in the dynamic URL array
				if(isset($siteMapInfo['dynamic-url'])){
					$siteMapInfo['dynamic-url']=array_values($siteMapInfo['dynamic-url']);
				}
				// If sitemap has defined the view
				if(isset($siteMapInfo['view'])){
					$view=$siteMapInfo['view'];
				} else {
					$view='';
				}
			} else {
				// Formatting and returning the expected result array
				return $this->returnViewData(array('view'=>$view404,'language'=>$language,'header'=>'HTTP/1.1 404 Not Found'));
			}
			
			// If the found view is home view, then we simply redirect to home view without the long url
			if(empty($siteMapInfo['dynamic-url']) && $view==$viewHome){
			
				// If first language is used and it is not needed to use language URL in first language
				if($enforceLanguageUrl==false && $language==$languages[0]){
					// If request nodes are set in the URL
					if(isset($requestNodesRaw[1])){
						return array('www-permanent-redirect'=>$webRoot.'?'.$requestNodesRaw[1]);
					} else {
						return array('www-permanent-redirect'=>$webRoot);
					}
				} else {
					// If request nodes are set in the URL
					if(isset($requestNodesRaw[1])){
						return array('www-permanent-redirect'=>$webRoot.$language.'/?'.$requestNodesRaw[1]);
					} else {
						return array('www-permanent-redirect'=>$webRoot.$language.'/');
					}
				}
			
			}
		
		}
				
		// Populating sitemap info with additional details
		$siteMapInfo['request-url']='/'.$requestNodesRaw[0];
		$siteMapInfo['url-base']=$this->getState('url-base');
		$siteMapInfo['url-web']=$webRoot;
		$siteMapInfo['url-absolute']=$siteMapInfo['url-base'].$requestNodesRaw[0];
		$siteMapInfo['url-relative']=$webRoot.$requestNodesRaw[0];
		if(isset($requestNodesRaw[1])){
			$siteMapInfo['request-parameters']=$requestNodesRaw[1];
		} else {
			$siteMapInfo['request-parameters']='';
		}
		$siteMapInfo['language']=$language;
		
		// It is possible to assign temporary or permanent redirection in Sitemap, causing 302 or 301 redirect
		if(isset($siteMapInfo['temporary-redirect']) && $siteMapInfo['temporary-redirect']!=''){
		
			// Dynamic parts of the URL can also be redirected
			if(!empty($siteMapInfo['dynamic-url'])){
				foreach($siteMapInfo['dynamic-url'] as $key=>$bit){
					$siteMapInfo['temporary-redirect']=str_replace(':'.$key.':',$bit,$siteMapInfo['temporary-redirect']);
				}
			}
			
			// Query string is also sent, if it has been defined
			if(isset($requestNodesRaw[1]) && strpos($siteMapInfo['temporary-redirect'],'?')===false){
				return array('www-temporary-redirect'=>$siteMapInfo['temporary-redirect'].'?'.$requestNodesRaw[1]);
			} else {
				return array('www-temporary-redirect'=>$siteMapInfo['temporary-redirect']);
			}
			
		} elseif(isset($siteMapInfo['permanent-redirect']) && $siteMapInfo['permanent-redirect']!=''){
		
			// Dynamic parts of the URL can also be redirected
			if(!empty($siteMapInfo['dynamic-url'])){
				foreach($siteMapInfo['dynamic-url'] as $key=>$bit){
					$siteMapInfo['permanent-redirect']=str_replace(':'.$key.':',$bit,$siteMapInfo['permanent-redirect']);
				}
			}
		
			// Query string is also sent, if it has been defined
			if(isset($requestNodesRaw[1]) && strpos($siteMapInfo['permanent-redirect'],'?')===false){
				return array('www-permanent-redirect'=>$siteMapInfo['permanent-redirect'].'?'.$requestNodesRaw[1]);
			} else {
				return array('www-permanent-redirect'=>$siteMapInfo['permanent-redirect']);
			}
			
		}
		
		// Returning custom data if no view setting was declared in sitemap URL file
		if(!isset($siteMapInfo['view']) && !isset($siteMapInfo['return-type'])){
			return $this->returnViewData(array('view'=>$view404,'language'=>$language,'header'=>'HTTP/1.1 404 Not Found'));
		}
			
		// Formatting and returning the expected result array
		return $this->returnViewData($siteMapInfo);
		
	}
	
	/**
	 * This function formats and returns View data for View Controller
	 *
	 * @param array $data data from Sitemap
	 * @return array
	 */
	private function returnViewData($data){
	
		// VIEW DEFAULTS
			
			$data+=array(
				'controller'=>'view',
				'controller-method'=>'load',
				'view-method'=>'render',
				'subview'=>'',
				'hidden'=>0,
				'return-type'=>'html'
			);
	
		// DEFAULTS FOR VIEW DATA
			
			// If dynamic URL is assigned as part of cache tag
			if(isset($data['cache-tag'],$data['cache-tag-dynamic']) && $data['cache-tag-dynamic']==1 && !empty($data['dynamic-url'])){
				$data['cache-tag'].='-'.implode('-',$data['dynamic-url']);
			}
			
			// If project title is not set by Sitemap, system defines the State project title as the value
			if(!isset($data['project-title'])){
				$data['project-title']=$this->getState('project-title');
			}
			
			// Robots data is also returned to views
			if(!isset($data['robots'])){
				$data['robots']=$this->getState('robots');
			}
			
		// HEADERS
		
			// These headers will be set by API
			$data['www-set-header']=array();
			
			if(isset($data['header'])){
				$data['www-set-header'][]=$data['header'];
			}
			
			// Writing robots data to header
			if($data['robots']!=''){
				// This header is not 'official HTTP header', but is widely supported by Google and others
				$data['www-set-header'][]='Robots-Tag: '.$data['robots'];
			}
			
			// Robots data is also returned to views
			if(isset($data['frame-permissions']) && $data['frame-permissions']){
				$data['www-set-header'][]='Frame-Options: '.$data['frame-permissions'];
			} elseif($tmp=$this->getState('frame-permissions')){
				$data['www-set-header'][]='Frame-Options: '.$tmp;
			}
			
			// Content security policy headers
			if(isset($data['content-security-policy']) && $data['content-security-policy']){
				$data['www-set-header'][]='Content-Security-Policy: '.$data['content-security-policy'];
			} elseif($tmp=$this->getState('content-security-policy')){
				$data['www-set-header'][]='Content-Security-Policy: '.$tmp;
			}
			
		// USER PERMISSIONS CHECKS
		
			// This is the best place to build your authentication module for web views
			// But the commands that it uses are not shown here, so it is commented out
			// This is essentially the boilerplate startpoint for you to implement authentication, as the actual login redirection is turned off
			// Attempting to get user session
			// if(isset($data['permissions'])){
			
				// Permissions are exploded into an array from comma separated string in sitemap file
				// $data['permissions']=explode(',',$data['permissions']);
				// This method automatically removes all empty entries from the array
				// $data['permissions']=array_filter($data['permissions']);
				
				// This flag, if changed, will redirect user to log-in screen
				// $failed=false;
				
				// Testing if user session exists
				// $user=$this->getUser();
				// if($user){
					// Double-checking user account validity from database
					// $userData=$this->dbSingle('SELECT * FROM users WHERE id=? AND deleted=0',array($user['id']));
					// if($userData){
						// Updating user session data from database (good if it has changed)
						// $this->setUser($userData);
						// Setting user permissions based on most recent information from database
						// $this->setPermissions(explode(',',$userData['permissions']));
						// Testing if permissions are included
						// if(!empty($data['permissions']) && !$this->checkPermissions($data['permissions'])){
							// $failed=true;
						// }
					// } else {
						// $failed=true;
					// }
				// } else {
					// $failed=true;
				// }
				
				// if($failed){
					// $siteMapReference=$this->getSitemap();
					// Success array is used since technically URL solving has been a 'success'
					// return $this->resultFalse('Authentication required',array('www-temporary-redirect'=>$siteMapReference['login']['url']));
				// }
				
			// }
		
		// Data about the view is returned
		return $data;
		
	}

}
	
?>
For more information send a message to info at phpclasses dot org.