<?php

/**
 * Picoraâ„¢ : PHP Micro Framework
 * http://livepipe.net/projects/picora/
 * 
 * Copyright (c) 2007 LivePipe LLC
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * @author Ryan Johnson <ryan@livepipe.net>
 * @copyright 2007 LivePipe LLC
 * @package Picora
 * @version 1.0 Beta 3
 * @license MIT
 */

if(!function_exists('__autoload')){
    
/**
     * Defines the hook for the AutoLoader class to run.
     */
    
function __autoload($class_name){
        return 
PicoraAutoLoader::load($class_name);
    }
}

/**
 * Provides a nicer print out of the stack trace when an exception is thrown. You can use this funciton in your own custom exception handler by using the return parameter.
 *
 * This function is automatically set as the default exception handler. If you set one before including Picora in your application, just call restore_exception_handler() right after including it.
 * @param Exception $e Exception object.
 * @param boolean $return Return the stack trace as a string, or print it out.
 */
function picora_exception_handler($e,$return false){
    
$s '<style>ul,li,pre {font-family:\'Lucida Grande\',Verdana; color:#333;} pre {margin-left:25px;}</style>';
    
$s .= '<p style=" font-family:\'Lucida Grande\',Verdana; color:#911; font-weight:bold; font-size:12px; line-height:28px; padding:0; margin:0 0 5px 10px; float:none; border:none; background-image:none;">Uncaught '.get_class($e).'</p>';
    if(
method_exists($e,'getTitle')){
        
$s .= '<h1 style="color:#333; font-family:Verdana; font-size:24px; font-weight:bold; background-image:none; border:none; float:none; padding:0; margin:10px 0 15px 10px;">'.$e->getTitle().'</h1>';
        
$s .= '<p style=" font-family:\'Lucida Grande\',Verdana; color:#333; font-size:16px; line-height:28px; padding:0; margin:0 0 15px 10px; float:none; border:none; background-image:none;">'.$e->getMessage().'</p>';
    }else{
        if(!
is_object($e)){
            
ob_start(); var_dump($e); $r ob_get_clean();
            die(
'Unknown Error: Exception handler was not passed an Exception object. Was passed : '.$r.'<br/>Stack Trace:<br/><pre>'.print_r(debug_backtrace(),true).'</pre>');
        }
        
$s .= '<h1 style="color:#333; font-family:Verdana; font-size:24px; font-weight:bold; background-image:none; border:none; float:none; padding:0; margin:10px 0 15px 10px;">'.$e->getMessage().'</h1>';
    }
    
$max 64;
    
$traceArr $e->getTrace();
    if(
sizeof($traceArr) == 1)
        
$s.= '<p style="font-family:\'Lucida Grande\',Verdana; font-size:12px; color:#444; text-align:left; line-height:24px; margin:0 0 0 10px; padding:0;"><b>The exception was thrown on line '.$e->getLine().' in '.$e->getFile().'</b></p>';
    else {
        
//printout code from php.net comments on debug_backtrace()
        
$s .= '<p style="font-family:\'Lucida Grande\',Verdana; font-size:12px; color:#444; text-align:left; line-height:24px; margin:0 0 0 10px; padding:0;"><b>Before the Exception was thrown, the script called the following functions in this order:</b><br>';
        
$totalTabs sizeof($traceArr) - 1;
        
$usedTabs 0;
        foreach(
array_reverse($traceArr) as $arr){
            ++
$usedTabs;
            
$s.= '&nbsp;&nbsp;&nbsp;';
            if (isset(
$arr['class'])) $s .= '<b>'.$arr['class'].'</b>&rarr;';
            
$args = array();
            if(!empty(
$arr['args'])) foreach($arr['args'] as $v){
                if (
is_null($v)) $args[] = 'null';
                else if (
is_array($v)) $args[] = 'Array['.sizeof($v).']';
                else if (
is_object($v)) $args[] = get_class($v).' Object';
                else if (
is_bool($v)) $args[] = $v 'true' 'false';
                else if (
is_int($v)) $args[] = $v;
                else{
                    
$v = (string) @$v;
                    
$str htmlspecialchars(substr($v,0,$max));
                    if (
strlen($v) > $max$str .= '...';
                    
$args[] = "\"".$str."\"";
                }
            }
            
$s .= '<b>'.$arr['function'].'(</b>'.implode(', ',$args).'<b>)</b>';
            
$Line = (isset($arr['line'])? $arr['line'] : "unknown");
            
$File = (isset($arr['file'])? $arr['file'] : "unknown");
            
$s .= sprintf("&nbsp;&nbsp;&nbsp;<span style=\"font-size:18px;\">&raquo;</span>&nbsp;&nbsp;called on line %d in %s",$Line,$File).'<br/>';
            
$s .= str_repeat('&nbsp;&nbsp;&nbsp;',$usedTabs);
        }
        
$s .= '&nbsp;&nbsp;&nbsp;<b>The exception was thrown on line '.$e->getLine().' in '.$e->getFile().'</b>';
        
$s .= '</p>';
    }
    if(
$return)
        return 
$s;
    else
        print 
$s;
}
set_exception_handler('picora_exception_handler');

/**
 * The AutoLoader class is an object oriented hook into PHP's __autoload functionality. You can add
 * 
 * - Single Files PicoraAutoLoader::addFile('PageController','controllers/PageController.php');
 * - Multiple Files PicoraAutoLoader::addFile(array('class'=>'file','class'=>'file'));
 * - Whole Folders PicoraAutoLoader::addFolder('path');
 *
 * When adding a whole folder each file should contain one class named the same as the file sans ".php" (PageController => PageController.php)
 *
 * __autoload is defined in the functions.php file.
 */
class PicoraAutoLoader {
    static protected 
$files = array();
    static protected 
$folders = array();
    
    
/**
     * PicoraAutoLoader::addFile('Controller','/path/to/Controller.php');
     * PicoraAutoLoader::addFile(array('Controller'=>'/path/to/Controller.php','View'=>'/path/to/View.php'));
     * @param mixed $class_name string class name, or array of class name => file path pairs.
     * @param mixed $file Full path to the file that contains $class_name.
     */
    
static public function addFile($class_name,$file false){
        if(!
$file && is_array($class_name))
            foreach(
$class_name as $key => $value)
                
self::addFile($key,$value);
        else
            
self::$files[$class_name] = $file;
    }
    
    
/**
     * PicoraAutoLoader::addFolder('/path/to/my_classes/');
     * PicoraAutoLoader::addFolder(array('/path/to/my_classes/','/more_classes/over/here/'));
     * @param mixed $folder string, full path to a folder containing class files, or array of paths.
     */
    
static public function addFolder($folder){
        if(
is_array($folder))
            foreach(
$folder as $f)
                
self::addFolder($f);
        else
            
self::$folders[] = $folder;
    }
    
    static public function 
load($class_name){
        foreach(
self::$files as $name => $file){
            if(
$class_name == $name){
                require_once(
$file);
                return 
true;
            }
        }
        foreach(
self::$folders as $folder){
            if(
substr(0,-1) != DIRECTORY_SEPARATOR)
                
$folder .= DIRECTORY_SEPARATOR;
            if(
file_exists($folder.$class_name.'.php')){
                require_once(
$folder.$class_name.'.php');
                return 
true;
            }
        }
        return 
false;
    }
}

/**
 * The PicoraController class should be the parent class of all of your PicoraController sub classes that contain the business logic of your application (render a blog post, log a user in, delete something and redirect, etc).
 *
 * In the Dispatcher class you can define what urls / routes map to what Controllers and methods. Each method can either:
 *
 * - return a string response
 * - redirect to another method
 *
 * If the method returns null or false, the PicoraDispatcher will keep looking for another method that returns a response or redirects, calling the error callback if no method responds to the requested URL.
 *
 * Each controller can have a beforeCall() and afterCall() method which will be called before a method inside the Controller is called (even if no method returns a valid response), and after a method inside the PicoraController is called (only if the method returns a valid response).
 * 
 * When render() is used, the variable $controller containing the current Controller object is available as a local variable inside the View object being rendered.
 * 
 * Additionally, you can flash() variables that will appear as local variables if a View object is rendered on the next request.
 */
abstract class PicoraController {
    
//used internall by Dispatcher to create a new PicoraController instance and call the requested method
    
static public function call($class_and_method,$parameters,$arguments false){
        
$arguments = ($arguments) ? $arguments : array();
        
$instance = new $class_and_method[0];
        
$instance->params $parameters;
        
$instance->beforeCall($class_and_method[1],$arguments);
        
$response call_user_func_array(array($instance,$class_and_method[1]),$arguments);
        if(
$response){
            
$callback_response $instance->afterCall($class_and_method[1],$arguments,$response);
            if(!
is_null($callback_response))
                
$response $callback_response;
        }
        return 
$response;
    }
    
    
/**
     * This is a callback function that will be called each time any method of the controller is called. This method may be called multiple times while the dispatcher is searching for a method that returns a response. It is designed to be overriden by a subclass and does nothing by default. 
     * @param string $method_name
     * @param array $arguments Arguments that will be passed to $method_name.
     */
    
protected function beforeCall($method_name,$arguments){}
    
    
/**
     * After a method sucessfully responds to a requested url, this method is called. It is designed to be overriden by a subclass and does nothing by default.
     * @param string $method_name
     * @param array $arguments Arguments that were passed to $method_name.
     * @param string $response The response that was returned from $method_name.
     * @return mixed Method should return null to leave $response untouched, otherwise the return value from the method becomes the response.
     */
    
protected function afterCall($method_name,$arguments,$response){}
    
    
/**
     * $this->render('views/blog/post.php',array('post'=>$post));
     * @param string $file Path to the file to render. Path will be relative to the file that handles the request.
     * @param mixed $local_variables Key => value pairs to pass to the View object that is rendered.
     */
    
public function render($file,$local_variables false){
        return new 
PicoraView($this,$file,$local_variables);
    }
    
    
/**
     * Renders $data_to_encode as JSON with the appropriate headers, outputs this to the browser and terminates the current request.
     * @param mixed $data_to_encode
     */
    
protected function renderJSON($data_to_encode,$use_standard false){
        if(
function_exists('json_encode'))
            
$output json_encode($data_to_encode);
        elseif(
class_exists('JSON'))
            
$output JSON::encode($data_to_encode);
        else
            throw new 
Exception('No function or class found to render JSON data.');
        if(
$use_standard){
            
header('X-JSON: ('.$output.')');
            print 
' ';
        }else
            print 
'('.$output.')';
        exit;
    }
    
    
/**
     * Renders an RSS feed from an array of data. Note that this is not an all in one cure all solution for RSS, it is merely a way to quickly create an RSS feed that fits most basic uses. $feed should contain the following keys:
     *
     * - string "title"
     * - string "link"
     * - timestamp "date"
     * - optional string "description"
     * - optional string "language"
     * - array "items"
     * 
     * Each item in "items" should contain:
     *
     * - string "title"
     * - string "link"
     * - optional string "description"
     * - optional timestamp "date"
     * - optional string "guid"
     *
     * @param string $feed Array of RSS data.
     * @param boolean $print Defaults to true. Print the feed complete with the appropriate header.
     * @return string RSS feed, or null if $print is true.
      */
    
function renderRSS($feed,$print true){
        function 
rss_from_json_tag($tag,$value,$tabs 1){
            return 
str_repeat(chr(9),$tabs).'<'.$tag.'>'.$value.'</'.$tag.'>'.chr(10);
        }
        
$output '<?xml version="1.0" encoding="UTF-8"?>'.chr(10);
        
$output .= '<rss version="2.0">'.chr(10).
            
chr(9).'<channel>'.chr(10).
            
rss_from_json_tag('title',$feed['title'],2).
            
rss_from_json_tag('link',$feed['link'],2).
            
rss_from_json_tag('lastBuildDate',gmdate('D, d M Y H:i:s',$feed['date']),2).
            (isset(
$feed['description']) ? rss_from_json_tag('description',$feed['description'],2) : '').
            (isset(
$feed['language']) ? rss_from_json_tag('language',$feed['language'],2) : '')
        ;
        foreach(
$feed['items'] as $item){
            
$output .= chr(9).'<item>'.chr(10).
                
rss_from_json_tag('title',$item['title'],3).
                
rss_from_json_tag('link',$item['link'],3).
                (isset(
$item['description']) ? rss_from_json_tag('description','<![CDATA['.$item['description'].']]>',3) : '').
                (isset(
$item['date']) ? rss_from_json_tag('pubDate',gmdate('D, d M Y H:i:s',$item['date']),3) : '').
                (isset(
$item['guid']) ? chr(9).chr(9).chr(9).'<guid isPermaLink="false">'.$item['guid'].'</guid>'.chr(10) : '').
            
chr(9).chr(9).'</item>'.chr(10);
        }
        
$output .= chr(9).'</channel>'.chr(10).'</rss>';
        if(
$print){
            
header('Content-type: application/rss+xml');
            print 
$output;
        }else
            return 
$output;
    }
    
    
/**
     * Redirects to another method, and terminates the current request.
     *
     * Putting the word "Controller" at the end of each controller name is optional.
     *
     * $this->redirect('about');
     * $this->redirect(array('BlogController','post'),array('post_id'=>5));
     * $this->redirect(array('Blog','post'),array('post_id'=>5));
     * @param mixed $controller_and_method String method name if redirecting to a method in the current controller or array('ControllerName','methodName') if redirecting to a method in another controller.
     * @param mixed $arguments Array arguments to resolve the route,or boolean false.
     */
    
protected function redirect($controller_and_method,$arguments false){
        
header('Location: '.PicoraDispatcher::getUrl($controller_and_method,$arguments));
        exit;
    }
    
    
/**
     * Serializes $value and makes it available as a local variable with $key name on the next request in the rendered view.
     * $this->flash('message','Your post has been saved.');
     * $this->redirect('post',array('post_id'=>$post_id));
     * //in the view file that the method post renders... 
     * <?php if(isset($message)):?><p class="message"><?php print $message;?></p><?php endif;?>
     * @param string $key
     * @param mixed $value Can be any data type or object that is serializable.
     * @param boolean $now Flash the value during the current request only? Defaults to false (so it will be available on this request, and the next).
     */
    
protected function flash($key,$value,$now false){
        
$_SESSION['__flash__']['values'][$key] = serialize($value);
        
$_SESSION['__flash__']['gc'][$key] = ($now 1);
    }
    
    
/**
     * @return string "get","post" or "ajax", depending on a request type. An empty POST request will resolve as a GET request.
     */
    
protected function getRequestMethod(){
        return (isset(
$_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')
            ? 
'ajax'
            
: (count($_POST) === 'get' 'post')
        ;
    }
    
    
/**
     * Alias for Dispatcher::getUrl();
     *
     * Putting the word "Controller" at the end of each controller name is optional.
     *
     * $controller->getUrl('post',array('post_id'=>5)); //outputs "/blog/5"
     * $controller->getUrl(array('BlogController','post'),array('post_id'=>5)); //outputs "/blog/5"
     * $controller->getUrl(array('Blog','post'),array('post_id'=>5)); //outputs "/blog/5"
     * @param mixed $class_and_method String method_name if reffering to a method in the Controller class that is currently responding, or array('ControllerName,'methodName') if referring to a method in another Controller.
     * @param mixed $arguments Optional arguments to resolve the url.
     * @param bool $include_base_url
     * @return mixed String url or boolean false if the url could not be resolved.
     */
    
public function getUrl($controller_and_method,$arguments false){
        return 
PicoraDispatcher::getUrl($controller_and_method,$arguments);
    }
    
    
/**
     * Alias for Dispatcher::isCurrent();
     * @param mixed $class_and_method String method_name if reffering to a method in the Controller class that is currently responding, or array('ControllerName,'methodName') if referring to a method in another Controller.
     * @param mixed $arguments Optional arguments to resolve the url.
     * @return boolean Wether or not the given class, method and arguments match the current dispatched ones.
     */
    
public function isCurrent($controller_and_method,$arguments false){
        return 
PicoraDispatcher::isCurrent($controller_and_method,$arguments);
    }    
}

/**
 * The Dispatcher class is responsible for mapping urls / routes to Controller methods. Each route that has the same number of directory components as the current requested url is tried, and the first method that returns a response with a non false / non null value will be returned via the Dispatcher::dispatch() method. For example:
 *
 * A route string can be a literal url such as '/pages/about' or contain named variables '/blog/$post_id'. Since these route strings can contain "$", they must always be enclosed by single quotes. In use the variables in the route string are collected in the order they appear and are passed as the arguments to the corresponding controller method.
 * 
 * PicoraDispatcher::addRoute(array(
 *     '/' => array('Page','index'),
 *     '/about/' => array('Page','about'),
 *     '/blog/$post_id' => array('Blog','post'),
 *     '/blog/$post_id/comment/$comment_id/delete' => array('Blog','deleteComment')
 * ));
 *
 * Visiting /about/ would call PageController::about(),
 * visiting /blog/5 would call BlogController::post(5)
 * visiting /blog/5/comment/42/delete would call BlogController::post(5,42)
 *
 * To link to BlogController::deleteComment(5,42) we would call Dispatcher::getUrl(array('Blog','post'),array('post_id'=>5,'comment_id'=>42))
 *
 * The dispatcher is used by calling Dispatcher::addRoute() to setup the route(s), and Dispatcher::dispatch() to handle the current request and get a response.
 */
class PicoraDispatcher {
    
//internally called by Dispatcher::dispatch()
    
static protected function load(){
        @
session_start();        
        if(!isset(
$_SESSION['__flash__']))
            
$_SESSION['__flash__'] = array('values' => array(),'gc' => array());
        
register_shutdown_function(array('PicoraDispatcher','flashGarbageCollection'));
    }
    
    
//called on shutdown to clear out stale flash values
    
static public function flashGarbageCollection(){
        foreach(
$_SESSION["__flash__"]["gc"] as $key => $value)
            --
$_SESSION["__flash__"]["gc"][$key];
        foreach(
$_SESSION["__flash__"]["gc"] as $key => $value){
            if(
$value 0){
                unset(
$_SESSION["__flash__"]["gc"][$key]);
                unset(
$_SESSION["__flash__"]["values"][$key]);
            }
        }
    }
    
    static protected 
$routes = array();
    static protected 
$status = array(
        
'request_url' => '',
        
'current_route' => '',
        
'current_arguments' => array(),
        
'current_controller' => '',
        
'current_method' => '',
        
'current_parameters' => array(),
        
'flash_values' => array(),
        
'dispatcher_dir' => '',
        
'base_url' => ''
    
);
    static protected 
$error_handler = array('PicoraDispatcher','error');
    
    
/**
     * Putting the word "Controller" at the end of each controller name is optional.
     * Dispatcher::addRoute('/index',array('PageController','index'));
     * Dispatcher::addRoute(array(
     *     '/blog/'=>array('Blog','index'),
     *     '/blog/:post_id'=>array('Blog','post')
     * ));
     * @param mixed $route String route or array of route => controller and method pairs.
     * @param mixed $controller_and_method array('MyController','myMethod').
     */
    
static public function addRoute($route,$controller_and_method false){
        if(!
$controller_and_method && is_array($route))
            foreach(
$route as $key => $value)
                
self::addRoute($key,$value);
        else{
            if(
substr($controller_and_method[0],-10) != 'Controller')
                
$controller_and_method[0] .= 'Controller';
            
self::$routes[$route] = $controller_and_method;
        }
    }

    
/**
     * @param callback $callback The callback function that will be called (with the requested url as the only parameter) if no Controller responds to the requested URL.
     */
    
static public function setErrorHandler($callback){
        
self::$error_handler $callback;
    }
    
    
/**
     * Dispatcher::getRouteByClassAndMethod('BlogController','post'); //outputs: "/blog/:post_id"
     * Dispatcher::getRouteByClassAndMethod(array('BlogController','post')); //outputs: "/blog/:post_id"
     * @param mixed $class_name String class name or array(class_name,method_name).
     * @param mixed $method_name String method name.
     * @return mixed Returns the route string that matches the class name and method name
     */
    
static protected function getRouteByClassAndMethod($class_name,$method_name false){
        if(!
$method_name){
            
$method_name $class_name[1];
            
$class_name $class_name[0];
        }
        foreach(
self::$routes as $route => $class_and_method)
            if(
$class_name == $class_and_method[0] && $method_name == $class_and_method[1])
                return 
$route;
        return 
false;
    }
    
    
/**
     * Putting the word "Controller" at the end of each controller name is optional.
     *
     * Dispatcher::getUrl('post',array('post_id'=>5)); //outputs "/blog/5"
     * Dispatcher::getUrl(array('BlogController','post'),array('post_id'=>5)); //outputs "/blog/5"
     * Dispatcher::getUrl(array('Blog','post'),array('post_id'=>5)); //outputs "/blog/5"
     * @param mixed $class_and_method String method_name if reffering to a method in the Controller class that is currently responding, or array('ControllerName,'methodName') if referring to a method in another Controller.
     * @param mixed $arguments Optional arguments to resolve the url.
     * @param bool $include_base_url
     * @return mixed String url or boolean false if the url could not be resolved.
     */
    
static public function getUrl($class_and_method,$arguments false,$include_base_url true){
        if(
is_string($class_and_method))
            
$class_and_method = array(self::$status['current_controller'].'Controller',$class_and_method);
        if(
substr($class_and_method[0],-10) != 'Controller')
            
$class_and_method[0] .= 'Controller';
        
$route_string self::getRouteByClassAndMethod($class_and_method[0],$class_and_method[1]);
        
preg_match_all('/(?<!\\\\)(\$([^\/0-9][\w\_\-]*))/e',$route_string,$matches);
        foreach(
$matches[2] as $match){
            if(
$match == 'id' && isset($variables['id']) && $arguments['id'] === false)
                
$route_string str_replace('$id','new',$route_string);
            elseif(isset(
$arguments[$match]) && !is_null($arguments[$match]))
                
$route_string str_replace('$'.$match,$arguments[$match],$route_string);
            elseif(
is_object($arguments) && method_exists($variables,'get'.str_replace(' ','',ucwords(str_replace('_',' ',$match)))))
                
$route_string str_replace('$'.$match,$arguments->{'get'.str_replace(' ','',ucwords(str_replace('_',' ',$match)))}(),$route_string);
        }
        return (
$route_string && !preg_match('/\$[^\/]/',$route_string))
            ? (
$include_base_url substr(self::$status['base_url'],0,-1) : '').$route_string
            
false
        
;
    }
    
    
/**
     * @param mixed $class_and_method String method_name if reffering to a method in the Controller class that is currently responding, or array('ControllerName,'methodName') if referring to a method in another Controller.
     * @param mixed $arguments Optional arguments to resolve the url.
     * @return boolean Wether or not the given class, method and arguments match the current dispatched ones.
     */
    
static public function isCurrent($class_and_method,$arguments false){
        return (
self::getUrl($class_and_method,$arguments,false) == self::$status['request_url']);
    }
    
    
//used to call a controller and keep track of what class, method and route we are calling
    
static protected function tryRoute($route,$class_and_method,$arguments = array()){
        
self::$status['current_route'] = $route;
        
self::$status['current_controller'] = substr($class_and_method[0],0,-10);
        
self::$status['current_method'] = $class_and_method[1];
        
self::$status['current_arguments'] = $arguments;
        return 
PicoraController::call($class_and_method,self::$status['current_parameters'],$arguments);
    }

    
/**
     * @param string $dispatcher_dir The directory that the application is running in.
     * @param string $base_url The base url that the application is running at.
     * @param string $requested_url The url that is being requested relative to the base url.
     * @return string Returns the response from a Controller that responded to the requested url
     */
    
static public function dispatch($dispatcher_dir,$base_url,$requested_url,$try_with_trailing_slash true){
        
self::load();
        
self::$status['current_parameters'] = array_merge($_POST,$_GET);
        unset(
self::$status['current_parameters']['__route__']);
        
self::$status['dispatcher_dir'] = $dispatcher_dir.'/';
        
self::$status['base_url'] = $base_url;
        
self::$status['request_url'] = $requested_url;
        
self::$status['flash_values'] =& $_SESSION["__flash__"]["values"];
        foreach(
self::$routes as $route => $class_and_method){
            if(
$requested_url == $route && ($response self::tryRoute($route,$class_and_method)))
                return 
$response;
            if(
preg_replace('{([^/]+)}','*',$route) == preg_replace('{([^/]+)}','*',$requested_url)){
                
preg_match_all('{([^/]+)?}',$route,$route_components);
                
preg_match_all('{([^/]+)?}',$requested_url,$requested_url_components);
                
$arguments = array();
                foreach(
$requested_url_components[0] as $key => $requested_url_component){
                    if(
$requested_url_component == '')
                        continue;
                    elseif(
strpos($route_components[0][$key],'$') !== false)
                        
$arguments[] = $requested_url_component;
                    elseif(
$route_components[0][$key] != $requested_url_component)
                        continue(
2);
                }
                if(
$response self::tryRoute($route,$class_and_method,$arguments))
                    return 
$response;
            }
        }
        if(
$try_with_trailing_slash && strlen($requested_url) > && substr($requested_url,0,-1) != '/' && self::dispatch($dispatcher_dir,$base_url,$requested_url.'/',false)){
            
header('HTTP/1.1 301 Moved Permanently');
            
header('Location: http'.($_SERVER['SERVER_PORT'] == 443 's' '').'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].'/');
            exit;
        }
        
header('HTTP/1.1 404 Not Found');
        return 
call_user_func_array(self::$error_handler,array($requested_url));
    }
    
    
/**
     * Key name can be any of the following:
     * - 'dispatcher_dir' string Path to the directory where the file that handled the request is located. For example: "/Library/WebServer/Documents/my_app/"
     * - 'base_url' string The base URL that the application is located at. For example: "http://localhost/my_app/"
     * - 'request_url' string The URL that was requested relative to the base URL. For example: "/blog/5"
     * - 'current_route' string The route string that matched the requested url. For example: "/blog/:post_id"
     * - 'current_controller' string The name of the Controller that responded to the requested URL. For example: "Blog". same as current_class sans the word "Controller"
     * - 'current_method' string The name of the method that responded to the requested URL. For example: "post"
     * - 'current_parameters' array Merged POST and GET arrays.
     * - 'current_arguments' mixed Array arguments passed to the called method, or bool false.
     * @param mixed $key Boolean false or string key name.
     * @return mixed If $key is specified, the value of the key will be returned, else array all key => value pairs.
     */    
    
static public function getStatus($key false){
        return (!
$key)
            ? 
self::$status
            
: (isset(self::$status[$key]) ? self::$status[$key] : false)
        ;
    }
    
    
//default error callback
    
static protected function error($route_string){
        return 
'Dispatch Error';
    }    
}

/**
 * The template object takes a valid path to a template file as the only argument in the constructor. You can then assign properties to the template, which become available as local variables in the template file. You can then call display() to get the output of the template, or just call print on the template directly thanks to PHP 5's __toString magic method.
 * 
 * $v = new PicoraView('views/my_template.php');
 * $v->title = 'My Title';
 * $v->body = 'My body';
 * print $v;
 * //or
 * print $v->display();
 * 
 * or the same thing in one command
 * 
 * print new PicoraView('views/my_template.php',array(
 *     'title' => 'My Title',
 *     'body' => 'My body'
 * ));
 * 
 * my_template.php might look like this: 
 * 
 * <html>
 *     <head>
 *         <title><?php print $title;?></title>
 *     </head>
 *     <body>
 *         <h1><?php print $title;?></h1>
 *         <p><?php print $body;?></p>
 *     </body>
 * </html>
 * 
 * Using view helpers:
 * 
 * $v->addHelperMethod('tag',array('MyHelperClass','myTagGenerator'));
 * 
 * In your template you can now call: 
 *
 * <?php print $this->tag('a',array('href'=>'http://mysite.com/'),'My Link Text');?>
 *
 * Sometimes it is useful to set content to be available outside of the current template.
 * 
 * <?php $this->beginContentFor('head');?>
 *     This content will be available to all subsequent PicoraView objects in the variable $head.
 * <?php $this->endContentFor('head');?>
 *
 * This is useful in implementing layouts, so that templates may add to different sections. Your layout View must be rendered after the view declaring these sections is rendered for the variables to be available. The variables will be overwritten by any manually defined variables.
 *
 * Callbacks:
 *
 * If you extend PicoraView, you can define a beforeDisplay() and afterDisplay(&$contents) method, that will be called before and after display() is called. You can use beforeDisplay() to set or unset variables, or helpers in every view instance that you wish, and use afterDisplay() to modify the $contents that will be returned from display. Note that $contents is passed in as reference. Neither function needs a return value;
 *
 * Rendering a string instead of a file:
 *
 * Just pass in any PHP code (not beginning and ending with PHP tags), in place of a filename. 
 *
 * $v = new View(false,'
 *     <h1><?php print $title;?></h1>
 * ',array('title'=>'My Title'));
 *
 * The only requirement is that this string MUST contain at least one newline character.
 */
class PicoraView {
    protected 
$__is_string__ false;
    protected 
$__file__ false;
    protected 
$__methods__ = array();
    protected 
$__controller__ false;
    static protected 
$__content_blocks__ = array();
    
    
/**
     * @param Controller $c Controller object that is rendering the view.
     * @param file_name $file Path to template file.
     * @param mixed $params Optional array of key value pairs.
     * @return View object
     */
    
final public function __construct($controller false,$file false,$params false){
        
$this->__is_file__ = (strpos($file,chr(10)) !== false);
        
$this->__controller__ $controller;
        
$this->__file__ $file;
        if(
$params)
            foreach(
$params as $key => $value)
                
$this->{$key} = $value;
    }
    
    
/**
     * Renders the current template, returning the result as a string.
     * @return string
     */
    
public function display(){
        
$this->beforeDisplay();
        
//extract and unserialize all flash values and extract content blocks
        
if(isset($_SESSION['__flash__']))
            foreach(
$_SESSION['__flash__']['values'] as $__key__ => $__value__)
                $
$__key__ unserialize($__value__);
        foreach(
self::$__content_blocks__ as $__key__ => $__value__)
            $
$__key__ =& $__value__;
        unset(
$__key__,$__value__);
        
        
//bring public properties and $controller into scope
        
extract(get_object_vars($this),EXTR_REFS);
        
$controller $this->__controller__;
        
        
//include file and return output
        
ob_start();
            if(
$this->__is_string__)
                eval(
' ?>'.$this->__file__.'<?php ');
            else
                include(
$this->__file__);
        
$output ob_get_clean();
        
$this->afterDisplay($output);
        return 
$output;
    }
    
    final public function 
__toString(){
        return 
$this->display();
    }
    
    
/**
     * @param mixed String method name, or array of method name => callback pairs
     * @param callback Callback function
     */
    
final public function addHelperMethod($method,$callback false){
        if(
is_array($method))
            foreach(
$method as $_method => $_callback)
                
$this->addMethod($_method,$_callback);
        else
            
$this->__methods__[$method] = $callback;
    }
    
    final public function 
__call($method,$args){
        if(isset(
$this->__methods__[$method]))
            return 
call_user_func_array($this->__methods__[$method],$args);
        else
            throw new 
Exception('Method '.get_class($this).'::'.$method.' not found.');
    }
    
    
/**
     * @param string Section name.
     */
    
final public function beginContentFor($section_name){
        
ob_start();
    }
    
    
/**
     * @param string Section name.
     */
    
final public function endContentFor($section_name){
        if(isset(
self::$__content_blocks__[$section_name]))
            
self::$__content_blocks__[$section_name] .= ob_get_clean();
        else
            
self::$__content_blocks__[$section_name] = ob_get_clean();
    }
    
    
/**
     * This is a callback function that will be called each time display() is called, before the output has been rendered to a string. You can use this callback to pre set or unset variables you want available to your PicoraView, or any other logic you wish.
     * @return void
     */
    
protected function beforeDisplay(){}
    
    
/**
     * This is a callback function that will be called each time a display() is called, after the output has been rendered to a string. By default it does nothing. You can use this callback to modify the output.
     * @param string &$contents The string that was rendered by the PicoraView object.
     * @return void $contents is passed in as a reference, so you should modify it directly instead of returning a value.
     */
    
protected function afterDisplay(&$contents){}
}

?>
 back to the menu  download  try out this script 
(note: some scripts won't run from this location)