<?php
/**
* PHP Controller
*
* PHP versions 5
*
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to hide@address.com so we can mail you a copy immediately.
*
*
* @category Controller
* @package PHP-Controller
* @author Eddie Tejeda <hide@address.com>
* @copyright 2005 Visudo LLC
* @version 0.5
*/
require_once 'PEAR.php';
require_once 'XML/Unserializer.php'; //TODO: Maybe use SimpleXML instead?
/**
*
*/
class Controller extends PEAR{
/**
* @see Controller::getDomainName
* @access private
* @var string contains domain information from controller
*/
var $domain = null;
/**
* @access private
* @var string contains the path to the web classes
*/
var $pagesPath= null;
/**
* @access private
* @var array contains associative array with pages from controller
*/
var $pages= null;
/**
* @access private
* @var string contains the working url of the application
*/
var $currentUrl= null;
/**
* @access private
* @var string contains the original url, before controller mangles it
*/
var $originalUrl= null;
/**
* @access private
* @var any contains the datastructure of content that will be passed back to application
*/
var $result= null;
/**
* @access private
* @var any contains the datastructure of navigation information that will be passed back to application
*/
var $navigationState= null;
/**
* @access private
* @var array contains an array of the aut
*/
var $authentication= null;
/**
* @access private
* @var string contains class1 information
*/
var $request= null;
/**
*
* @access private
* @var int contains class1 information
*/
var $debugLevel = 0;
/**
* @access private
* @var string contains class1 information
*/
var $startTime= 0;
/**
* @access private
* @var string contains class1 information
*/
var $endTime= 0;
/**
* The contructor looks for controller.xml and checks to see
* if modrewrite is enabled and loads contents of file. it also
* gets information about the URL
* @access public
* @param string filename controller.xml
*/
function Controller($filename = 'controller.xml'){
$this->startTime = $this->_execTime();
if( !file_exists($filename) ){
return $this->raiseError('XmlData class constructor did not recieve a file');
}
$htcontent = null;
if(file_exists(".htaccess")){
$htcontent = file_get_contents(".htaccess");
}
if( !file_exists(".htaccess") || !stristr($htcontent, "RewriteEngine on") ){
$errorMessage =
"<h2>Mod-Rewrite is required. Please enable Mod-Rewrite in the file called <i>.htaccess</i><br /></h2>".
"Example:<br>".
"<pre>".
"RewriteEngine on<br>".
"RewriteRule !\.(png|gif|jpg)$ ".getcwd()."/index.php".
"</pre>";
return $this->raiseError($errorMessage); //or die?
}
// load xml controller
$option = array('complexType' => 'array', 'parseAttributes' => TRUE);
$unserialized = new XML_Unserializer($option);
$result = $unserialized->unserialize($filename, true);
if (PEAR::isError($result)) {
die($result->getMessage());
}
// load contents from xml file
$data = $unserialized->getUnserializedData();
// domain path, required
if(isset($data['domain']) && preg_match ( '/\/$/', $data['domain'])){
$this->domain = $data['domain'];
}
else{
die("<domain> not defined in controller or does not end in a forward slash (/)");
}
// pages path, required
if(isset($data['pages']) && preg_match ( '/\/$/', $data['pages'])){
$this->pagesPath = $data['pages'];
}
else{
die("<pages> not defined in controller or does not end in a forward slash (/)");
}
// authentication, not required
if(isset($data['authentication']) ){
$this->authentication = $data['authentication'];
}
// pages, required
if(isset($data["page"]) ){
if(!isset($data['page'][0])){
$name = $data['page']['name'];
$this->pages[$name] = $data['page'];
}
else{
for($i = 0; $i < count($data['page']); $i++ ){
$name = $data['page'][$i]['name'];
$this->pages[$name] = $data['page'][$i];
}
}
}
else{
die("<page> not defined in controller");
}
// aliases, not required
if(isset($data['alias']) ){
if(!isset($data['alias'][0])){
$name = $data['alias']['name'];
$this->aliases[$name] = $data['alias'];
}
else{
for($i = 0; $i < count($data['alias']); $i++ ){
$name = $data['alias'][$i]['name'];
$this->aliases[$name] = $data['alias'][$i];
}
}
}
$this->originalUrl = "http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
// the following is URI mangling
// get the request variables from browser
$this->currentUrl = "http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$requestVariables = strstr( $_SERVER['REQUEST_URI'], "?" );
$domainLen = strlen( $this->domain);
$allRequestSection = substr($this->currentUrl, $domainLen);
$getPosition = strpos($allRequestSection, "?");
$getPosition = ($getPosition > 0) ? $getPosition : strlen($allRequestSection);
$requestSection = substr($allRequestSection, 0, $getPosition );
$get = substr($allRequestSection, $getPosition+1 );
$requestSection = preg_replace('/\/$/', "", $requestSection);
if( $alias = $this->getAlias($requestSection) ){
$this->currentUrl = $this->domain.$alias["page"];
// we append the variables from controller to current variables
$seperator = strpos($alias["page"], "?") > 0 ? "&" : "?";
$seperator = count($_GET) > 0 ? $seperator : "";
$this->currentUrl = $this->domain.$alias["page"].$seperator.$get;
}
$this->endTime = $this->_execTime();
}
/**
* Load the proper class name, execute the default method in that object
* if no method is defined in url. if method is defined in URL, execute
* that method and save contents to private variable.
* @access public
*/
function performAction(){
if($this->debugLevel > 0){
$this->_debug();
}
if( $this->isPrivilegedUser() && !(isset($_POST['username']) && isset($_POST['password']) )){
// detect aliases
$className = $this->getClassName();
$classFile = $this->pagesPath.$className.".class.php";
if(!file_exists($classFile)){
return $this->raiseError("$className.class.php not found in ".$this->pagesPath);
}
require_once($classFile);
$page = new $className;
// this is an optional function a developer can implement if developer wants
// to keep track of navigation state
if( method_exists( $page, "navigationState" )){
$len = strlen($this->domain);
$this->navigationState = $page->navigationState(substr($this->currentUrl, $len, strlen($this->currentUrl)));
}
// this is the dynamic method
$webmethod = $this->getMethodName();
//die($webmethod);
if( method_exists( $page, $webmethod )){
$get = $this->getGetVariables();
$args = array("url"=> $this->currentUrl, "get" => $get, "post"=> $_POST, "cookie" => $_COOKIE );
$this->result = call_user_func_array(array(&$page, $webmethod),$args);
$section = $this->getSectionName();
// redirect the user if this is a form that has a forward option
if( isset( $this->pages[$section]['form-forward'] ) ){
if( isset( $this->pages[$section]['form-forward'][0] ) ){
for($i = 0; $i < count($this->pages[$section]['form-forward']); $i++){
if($this->pages["$section"]['form-forward'][$i]['method-name'] == $webmethod){
header("Location: ".$this->domain.$this->pages[$section]['form-forward'][$i]['_content']);
}
}
}
else{
if($this->pages["$section"]['form-forward']['method-name'] == $webmethod){
header("Location: ".$this->domain.$this->pages[$section]['form-forward']['_content']);
}
}
}
}
else{
return PEAR::raiseError("The specified method is not implemented in this class");
}
}
// The user is not privilaged, but they are trying to login
else if(isset($_POST['username']) && isset($_POST['password'])){
$className = $this->authentication['class'];
$authMethod = $this->authentication['login-forward']['method-name'];
$classFile = $this->pagesPath.$className.".class.php";
if(!file_exists($classFile)){
return $this->raiseError("$className.class.php not found in ".$this->pagesPath);
}
require_once($this->pagesPath.$className.".class.php");
$page = new $className;
if( method_exists( $page, $authMethod )){
$args = array("post"=> $_POST );
$this->result = call_user_func_array(array(&$page, $authMethod),$args);
}
else{
return PEAR::raiseError("Authentication class is required");
}
if(isset($this->authentication['login-forward'])){
//we are forwared to the success page even we fail login. the login form will just reappear
header("Location: ".$this->domain.$this->authentication['login-forward']['_content']);
}
// we need to
return $this->raiseError("A section needs to be defined on successful login");
}
else{// we are loading up the login form
$className = $this->authentication['class'];
$authMethod=$this->getMethodName();
$classFile = $this->pagesPath.$className.".class.php";
if(!file_exists($classFile)){
return PEAR::raiseError("$className.class.php not found in ".$this->pagesPath);
}
require_once($this->pagesPath.$className.".class.php");
$page = new $className;
//we are loggin out
if(!PEAR::isError($authMethod) ){
$args = array();
if($authMethod){
call_user_func_array(array(&$page, $authMethod),$args);
header("Location: ".$this->domain.$this->authentication['logout-forward']['_content']);
}
}
if(isset($_SESSION['role'])){
if(isset($this->authentication['login-forward']['_content'])){
// we are forwared to the success page even we fail login. the login form will just reappear
header("Location: ".$this->domain.$this->authentication['login-forward']['_content']);
}
else{
return PEAR::raiseError("A section needs to be defined on successful login");
}
}
//header("Location: ".$this->domain.$this->authentication['name']);
}
}
/**
* @return string base path domain
* @access public
*/
function getDomainName(){
return $this->domain;
}
/**
* Checks the current class and returns name of the template defined by controller.xml
* for this class
* @access public
* @return string the name of the template defined by controller.xml for this class
*/
function getTemplateName(){
if($this->isPrivilegedUser() ){
$section = $this->getSectionName();
$className = $this->getClassName();
if(isset($this->pages[$section]['name'])){
if($this->pages[$section]['class'] == $className){
return $this->pages[$section]['template'];
}
}
}
else{
return $this->authentication["template"];
}
}
/**
* Collects the GET variables for this class and loads any preexisting
* variables that might be defined by alias
* @access public
* @return string of GET variables
*/
function getGetVariables(){
// extract the GET variables from URI
$requestArray = isset($_GET) ? $_GET : null;
// extract GET variables from alias tag if defined in controller
$requests = null;
if($alias = $this->getAlias( $this->getSectionName() ) ){
$pos = strpos($alias["page"], "?") > 0 ? strpos($alias["page"], "?")+1: strlen($alias["page"]);
$req = substr( $alias["page"], $pos, (strlen($alias["page"]) - $pos) );
$tempRequests = null;
if(strpos($req, "&")){
$tempRequests = explode("&",$req);
}
if(isset($tempRequests) ){
foreach($tempRequests as $tmp){
$requests[] = $tmp;
}
}
if(!isset($tempRequests) && isset($req)) {
$requests[] = $req;
}
}
//appending variables from controller to GET
if(is_array($requests)){
foreach($requests as $request){
$var = explode("=",$request);
if(is_array($var) && isset($var[1]) ){
$requestArray[$var[0]] = $var[1];
}
}
}
return $requestArray;
}
/**
* Get the method name for the current section. It returns the second item in the
* request i.e example.org/className/action?param=true would return "action"
* @param string optional string to determine the method of this section
* @return string method name of current class
*/
function getMethodName($sectionName=""){
// we get sectionName from either a parameter if defined, or get the default sectionName if defined
$sectionName = (strlen($sectionName) > 0) ? $sectionName : $this->getSectionName();
$len = strlen( $this->domain);
$request = substr($this->currentUrl, $len);
$start = strpos($request, "/") > 0 ? strpos($request, "/")+1 : strlen($request);
$end = strpos($request, "?") > 0 ? strpos($request, "?"): strlen($request);
$methodname = substr($request, $start, ($end-$start));
// NOTICE: i am not sure i want this functionality.
//the problem is that i want authentication class to just load without
//calling a method.. but if we let this continue it call show a method
//from
if(!$this->isPrivilegedUser()){
if (isset($this->authentication['logout-forward']['method-name'])){
if($this->authentication['logout-forward']['method-name'] == $methodname ){
return $methodname;
}
}
if(isset($this->authentication['login-forward']['method-name'])){
if($this->authentication['login-forward']['method-name']){
return $methodname;
}
}
else{
return false; //NOTICE: is this what i want to return?
}
}
// we have the section name from just looking at the URL
if( $methodname ){
return $methodname;
}
// we are now looking in the xml file
if(isset($this->pages[$sectionName])){
if(isset($this->pages[$sectionName]['name']) ){
return $this->pages[$sectionName]['default-method'];
}
}
if(isset($this->aliases)){
if(isset($this->aliases[$sectionName]['name'])){
$start = strpos($this->aliases[$sectionName]['page'], "/") > 0 ? strpos($this->aliases[$sectionName]["page"], "/")+1 : strlen($this->aliases[$sectionName]["page"]);
$end = strpos($this->aliases[$sectionName]['page'], "?") > 0 ? strpos($this->aliases[$sectionName]["page"], "?"): strlen($this->aliases[$sectionName]["page"]);
$methodname = substr($this->aliases[$sectionName]["page"], $start, ($end- $start));
//the following assumes that if no method name is parsed from xml file then we should just use the default methodname
return strlen($methodname) ? $methodname : $this->getMethodName($this->aliases[$sectionName]["page"]);
}
}
return PEAR::raiseError("Method does not exists");
}
/**
* Get the class name for the current section. it checks the page names in xml
* file then it looks at aliases then looks for a default if it doesn't find
* the previous two or false;
* @return string the class name without any extension, php or class.php
*/
function getClassName(){
$sectionName = $this->getSectionName();
$privileged = $this->isPrivilegedUser();
if($privileged){
if(strlen( $sectionName) > 0 ){
if(isset($this->pages[$sectionName]['name'])){
return $this->pages[$sectionName]['class'];
}
}
if(isset($this->aliases)) {
if( isset($this->aliases[$sectionName]['name'])){
$len = strpos($this->aliases[$sectionName]["page"], "/");
$len = isset($len) ? strlen($this->aliases[$sectionName]["page"]) : $len;
return substr( $this->aliases[$sectionName]['page'], 0, $len);
}
}
if(isset($this->authentication['name'])) {
if($this->authentication['name'] == $sectionName) {
return $this->authentication['name'];
}
}
//if all fail, show default
if(isset($sectionName)){
foreach($this->pages as $page){
if($page['default-page'] == 1 ){
return $page['class'] ;
}
}
}
}
else{
if(isset($this->authentication)){
if($this->authentication['name']){
return $this->authentication['class'];
}
}
}
return false;
}
/**
* Get the section name from the URI
* @return string the name of the section we are in (i.e it exists in xml file) or default if none is specified in URI
*/
function getSectionName(){
$requestStart = strlen($this->domain);
$request = substr($this->currentUrl, $requestStart, strlen($this->currentUrl) );
$sectionStart = (strpos($request, "/")) ? strpos($request, "/") : strlen($request);
$sectionName = substr($request, 0, $sectionStart );
//pages
if(isset ($sectionName) ){
if(isset($this->pages) ){
if(isset($this->pages[$sectionName]['name'])){
return $sectionName;
}
}
}
//aliases
if(isset ($sectionName) ){
if(isset($this->aliases) ){
if(isset($this->aliases[$sectionName]['name'])){
return $sectionName;
}
}
}
//auth forms
if( isset ( $sectionName) ){
if(isset($this->authentication) ){
if($this->authentication['name'] == $sectionName){
return $sectionName;
}
}
}
// user specifies a url and we're here, its not found
if(strlen($sectionName) > 0){
// TODO: return PEAR::raiseError("Section is not found")";
die("Page not found");
}
// Now are looking for default "pages" or "aliases" if nothing is specified in URL
foreach($this->pages as $page){
if(isset($page['default-page']) && ($page['default-page'] == 1) ){
return $page['name'];
}
}
foreach($this->aliases as $alias){
if($alias['default-page'] == 1){
return $alias['name'];
}
}
return PEAR::raiseError("Section is not found");
}
/**
* Detects the current section and checks if the current user is allowed to view
* that section. it determines this by looking at the SESSION variable "role" and
* compares it to the one in the <page> tag in controller. NOTICE: that no role defined
* in the xml file makes it public by default.
* @todo: allow support for more then one group per section
* @return bool true if the SESSION['role'] is set and matches the controller role
* @return bool false if SESSION['role'] is not set or does match the controller role
*/
function isPrivilegedUser(){
$sectionName = $this->getSectionName();
$page = null;
// look at the pages in xml file
if(isset($this->pages)){
if(isset($this->pages[$sectionName]["name"])){
$page = $this->pages[$sectionName];
}
}
// now check aliases... there are more than one alias defined
if(isset($this->aliases)){
if(isset($this->aliases[$sectionName]['name'])){
$len = strpos($this->aliases[$sectionName]["page"], "/");
$len = isset($len) ? strlen($this->aliases["$sectionName"]["page"]) : $len;
$pageName = substr( $this->aliases[$sectionName]["page"], 0, $len);
$page = $this->pages[$pageName];
}
}
if( $this->authentication['name'] == $sectionName){
return false;
}
// no role assigned, it's public
$role = isset($_SESSION['role']) ? $_SESSION['role']: null;
if ( !isset ( $page['role'] )){
return true;
}
else if( $page['role'] == $role ){
return true;
}
else{
return false;
}
}
/**
* Get the method name from XML filr for the current section we are in
* @param string sectionName which is the path in URL
* @return string alias of the page
* @return bool false if alias is not found
*/
function getAlias($sectionName){
$requestStart = strlen($this->domain);
$request = substr($this->originalUrl, $requestStart, strlen($this->originalUrl) );
$sectionStart = (strpos($request, "?")) ? strpos($request, "?") : strlen($request);
$requestMark = substr($request, 0, $sectionStart );
$request= preg_replace('/\/$/',"", $requestMark);
//var_dump( $requestMark );
if(isset($this->aliases)){
if(isset($this->aliases[$request]['page'])){
return $this->aliases[$request];
}
}
return false;
}
/**
* @return mixed anycontent, you have to know what the class method results
*/
function getContent(){
return $this->result;
}
/**
* @return mixed anycontent, you have to know what the class method results
*/
function getNavigationState(){
return $this->navigationState;
}
/**
* @see Controller::setDebugLevel
* @access public
*/
function setDebugLevel($int){
$this->debugLevel = $int;
}
/**
* @see Controller::setDebugLevel
* @access private
*/
function _debug(){
echo "<div id=\"javscriptlayer\" style=\"position: absolute; visibility: hidden;\">
<script language=\"javascript\" type=\"text/javascript\">
//Thank you quirksmode!
//http://www.quirksmode.org/js/cookies.html
function createCookie(name,value,days)
{
if (days)
{
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = \"; expires=\"+date.toGMTString();
}
else var expires = \"\";
document.cookie = name+\"=\"+value+expires+\"; path=/\";
}
function readCookie(name)
{
var nameEQ = name + \"=\";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++)
{
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function debugStatus(){
var status = readCookie('php_controller_debug');
if(status == 'debug_false'){
document.getElementById('debug_window').style.visibility='hidden';
document.getElementById('close_window').style.visibility='hidden';
document.getElementById('show_window').style.visibility='visible';
}
else{
document.getElementById('debug_window').style.visibility='visible';
document.getElementById('close_window').style.visibility='visible';
}
}
</script>
</div>
<div id=\"show_window\" style=\"bottom: 0px; z-index:100; right: 0px; position: fixed; visibility: hidden; background-color: #ff6\"><a style=\"text-decoration: none; font-family: courier; font-size: 12px;\" href=\"#\" onclick=\"document.getElementById('debug_window').style.visibility='visible'; document.getElementById('close_window').style.visibility='visible'; document.getElementById('show_window').style.visibility='hidden'; createCookie('php_controller_debug', 'debug_true', 1);\">debug</a></div>
<div id=\"debug_window\" style=\"opacity: .75; z-index:100; padding-left: 10px; padding-top: 10px; position: fixed; font-size: 10px; width: 500px; bottom: 0px; right: 0px; background-color:#ff6\">
<div id=\"close_window\" style=\"visibility:visible; float: right; top:0px; right:5px; position:relative;\"><a href=\"#\" style=\"text-decoration: none; font-size: 12px; font-family: courier;\" onclick=\"document.getElementById('close_window').style.visibility='hidden';document.getElementById('debug_window').style.visibility='hidden';document.getElementById('show_window').style.visibility='visible'; createCookie('php_controller_debug', 'debug_false', 1);\">X</a></div>
<b style=\"color:black; font-size: 18px\">Debugger Enabled</b>\n
<pre>";
if($this->debugLevel > 0){
echo "Internal URI = ".$this->currentUrl."\n";
echo "Section Name = ".$this->getSectionName()."\n";
echo "Class Name = ".$this->getClassName()."\n";
echo "Method Name = ".$this->getMethodName()."\n";
if( $alias = $this->getAlias($this->getSectionName())){
echo "Alias = true - ";
print_r($alias)."\n";
}
else{
echo "Alias = Not Alias\n";
}
echo "Template Name = ".$this->getTemplateName()."\n";
}
if($this->debugLevel > 1){
$totalTime = substr($this->endTime - $this->startTime, 0, 5);
echo "Constructor overhead $totalTime\n";
}
if($this->debugLevel > 2){
$status = null;
if($this->isPrivilegedUser())
$status = "true";
else
$status = "false";
echo "Privileged User = ".$status."\n";
echo "Logged in ";
if(isset( $_SESSION['role']))
echo " with the role of '".$_SESSION['role']."'\n";
else
echo ": false\n";
}
if($this->debugLevel > 3){
echo "Post Varibles = ";
print_r($_POST);
echo "Get Variables = ";
print_r($_GET);
}
if($this->debugLevel > 4){
if(isset($this->pages[$this->getSectionName()])){
echo "Page controller settings ";
print_r($this->pages[$this->getSectionName()]);
}
if(isset($this->aliases[$this->getSectionName()])){
echo "Alias controller settings ";
print_r($this->aliases[$this->getSectionName()]);
}
}
echo "</pre>\n<script language=\"javascript\" type=\"text/javascript\">window.onload=debugStatus();</script></div>\n";
}
/**
* @see Controller::setDebugLevel
* @access private
*/
function _execTime(){
$microTime = microtime();
$microTime = explode(" ",$microTime);
$microTime = $microTime[1] + $microTime[0];
return $microTime;
}
}
?>