<?php
/**
* Krai action base class
*
* This file holds the class which is the base of any module action.
*
* @package Krai
* @subpackage Module
* @author Greg McWhirter <hide@address.com>
* @copyright Copyright (c) 2008, Greg McWhirter
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Action base class
*
* This class is the basis for every module action in an application. It provides
* filtering functionality, as well as redirection, rendering, and error handling.
*
* @package Krai
* @subpackage Module
*/
abstract class Krai_Module_Action extends Krai
{
/**
* Holds a reference to the parent module
*
* This variable holds a reference to the parent module of the action. This
* allows actions to be tied to a heirarchy of module classes instead of just one
* from which it inherits. It also allows for data separation between the module
* and the action.
*
* @var Krai_Module
*/
protected $_parent;
/**
* The request method for the current action
*
* This variable holds the request method which generated the call to the current
* action. It should be one of "GET" or "POST", as supplied by {@link Krai_Module::DoAction()}.
*
* @var string
*/
protected $_RequestMethod;
/**
* Whether or not any rendering has been done yet.
*
* This variable is a flag for whether or not rendering has been started by any
* action. This allows for errors to be thrown when attempting to render a
* layout more than once.
*
* @var boolean
*/
private static $_RENDER_STARTED = false;
/**
* Constructor
*
* This function initializes the action parameters. It does not control execution
* other than that. Execution is managed within {@link Krai_Module::DoAction()}.
*
* @param Krai_Module $_parent The parent module of the action
* @param string $_rm The request method
* @throws Krai_Module_Exception
*/
public function __construct(Krai_Module &$_parent, $_rm)
{
$this->_parent = $_parent;
if (!in_array($_rm, array("GET","POST")))
{
throw new Krai_Module_Exception("Request method ".$_rm." was not understood.", Krai_Module_Exception::UnknownRequestMethod);
}
$this->_RequestMethod = strtoupper($_rm);
}
/**
* Validate some data
*
* This is the second function of an action called, after the
* {@link Krai_Module_Action::BeforeFilters()} in a standard application flow.
* The intention is to validate input within this method and save it to instance
* variables to be used later in the {@link Krai_Module_Action::Process()} and
* {@link Krai_Module_Action::Display()} methods. If an error is found, a
* {@link Krai_Module_Exception} should be thrown.
*
* @throws Krai_Module_Exception
*/
public function Validate()
{ }
/**
* Process data
*
* This is the third function of an action called, after {@link Krai_Module_Action::BeforeFilters()}
* and {@link Krai_Module_Action::Validate()} in a standard application flow.
* The intention is to process validated input within this method and record
* any messages or errors to be used later in the {@link Krai_Module_Action::Display()}
* method. If an error is found, a {@link Krai_Module_Exception} should be thrown.
*
* @throws Krai_Module_Exception
*/
public function Process()
{ }
/**
* Display something
*
* This is the fourth function of an action called, after {@link Krai_Module_Action::BeforeFilters()},
* {@link Krai_Module_Action::Validate()}, and {@link Krai_Module_Action::Process()}
* in a standard application flow. The intention is to implement display logic
* to handle the actions of the Validation and Processing.
*
*/
public function Display()
{ }
/**
* Handle an error being thrown
*
* This function handles the {@link Krai_Module_Exception}s thrown by the
* {@link Krai_Module_Action::Validate()} and {@link Krai_Module_Action::Process()}
* methods. By default it is implemented to call {@link Krai::Error()} with
* the $_ErrorMsg parameter.
*
* @param integer $_ErrorCode
* @param string $_ErrorMsg
*/
public function HandleError($_ErrorCode, $_ErrorMsg)
{
self::Error($_ErrorMsg);
}
/**
* Render a file with or without a template
*
* This function is responsible for rendering a view file, optionally with a
* template. It should only be called if neither it nor {@link Krai_Module_Action::RenderText()}
* was previously called in the application flow. Before rendering the file,
* it passes off to {@link Krai_Module_Action::BeforeRender()}. It throws a
* Krai_Module_Exception_Adone, so it generally will not return to the action
* application flow.
*
* The filename passed with be prefixed with {@link Krai::$MODULES} and verified
* to be within the {@link Krai::$APPDIR} directory. Additionally, the name of
* the layout file (whether passed or default) will be prefixed with {@link Krai::$LAYOUTS}
* and also verified to live within the application root {@link Krai::$APPDIR}.
*
* @param string $_file The name of the file to be rendered
* @param boolean $_templated The name of the layout to use or true to use the
* default layout or false to use no layout
* @throws Krai_Module_Exception_Adone
* @throws Krai_Module_Exception
*/
protected function Render($_file, $_templated = true)
{
if(!self::$_RENDER_STARTED)
{
self::$_RENDER_STARTED = true;
$this->BeforeRender();
}
$_file = realpath(Krai::$MODULES."/".$_file);
if(strstr($_file, realpath(Krai::$APPDIR)) != 0)
{
throw new Krai_Module_Exception("Invalid path to view file. File must be within application root.", Krai_Module_Exception::FilePathError);
}
if ($_templated)
{
$mconf = Krai::GetConfig("CONFIG_MODULE");
$layout = (is_string($_templated)) ? $_templated : $mconf["DEFAULT_LAYOUT"];
if($layout)
{
$layout = realpath(Krai::$LAYOUTS."/".$layout);
if(strstr($layout, realpath(Krai::$APPDIR)) != 0)
{
throw new Krai_Module_Exception("Invalid path to layout. Layout file must be within application root.", Krai_Module_Exception::FilePathError);
}
include $layout."/header.phtml";
include $_file;
include $layout."/footer.phtml";
}
else
{
include $_file;
}
}
else
{
include $_file;
}
$this->AfterRender();
throw new Krai_Module_Exception_Adone(Krai_Module_Exception_Adone::Rendered);
}
/**
* Renders a partial file
*
* This function provides the ability to render a "partial" within another render.
* It accepts some data and assigns that to a local variable based on the name
* of the partial file that will be available within the scope of the partial
* file. If an array of data is passed, it will include the partial file once
* for each element of the array.
*
* The filename passed with be prefixed with {@link Krai::$MODULES} and verified
* to be within the {@link Krai::$APPDIR} directory.
*
* @param string $_file The name of the partial file
* @param mixed $_collection The data for the partial file
* @param array $_locals Some other local variables that will be available to
* the partial file in the form of varname => value pairs
*
*/
protected function RenderPartial($_file, $_collection = array(), array $_locals = array())
{
if(!self::$_RENDER_STARTED)
{
self::$_RENDER_STARTED = true;
$this->BeforeRender();
}
$_file = realpath(Krai::$MODULES."/".$_file);
if(strstr($_file, realpath(Krai::$APPDIR)) != 0)
{
throw new Krai_Module_Exception("Invalid path to view file. File must be within application root.", Krai_Module_Exception::FilePathError);
}
$_varname = preg_replace("#([^a-zA-Z0-9_])|(\.phtml)#","",array_pop(explode("/",$_file)));
if(is_array($_collection))
{
foreach($_collection as ${$_varname})
{
foreach($_locals as $_k => $_v)
{
${preg_replace("#^_*#","",$_k)} = $_v;
}
include $_file;
}
}
else
{
${$_varname} = $_collection;
include $_file;
}
}
/**
* Renders some text with or without a template
*
* This function is responsible for rendering some text (e.g. JSON), optionally
* with a template. It should only be called if neither it nor {@link Krai_Module_Action::Render()}
* was previously called in the application flow. Before rendering the file,
* it passes off to {@link Krai_Module_Action::BeforeRender()}. It throws a
* Krai_Module_Exception_Adone, so it generally will not return to the action
* application flow.
*
* The name of the layout file (whether passed or default) will be prefixed with
* {@link Krai::$LAYOUTS} and verified to live within the application root {@link Krai::$APPDIR}.
*
* @param string $_text The text to be rendered
* @param boolean $_templated The name of the layout or true to use the default
* layout or false to use no layout
* @throws Krai_Module_Exception_Adone
* @throws Krai_Module_Exception
*/
protected function RenderText($_text, $_templated = true)
{
if(!self::$_RENDER_STARTED)
{
self::$_RENDER_STARTED = true;
$this->BeforeRender();
}
if ($_templated)
{
$mconf = Krai::GetConfig("CONFIG_MODULE");
$layout = (is_string($_templated)) ? $_templated : $mconf["DEFAULT_LAYOUT"];
if($layout)
{
$layout = realpath(Krai::$LAYOUTS."/".$layout);
if(!strstr($layout, realpath(Krai::$APPDIR)) != 0)
{
throw new Krai_Module_Exception("Invalid path to layout. Layout file must be within application root.", Krai_Module_Exception::FilePathError);
}
include $layout."/header.phtml";
echo $_text;
include $layout."/footer.phtml";
}
else
{
echo $_text;
}
}
else
{
echo $_text;
}
$this->AfterRender();
throw new Krai_Module_Exception_Adone(Krai_Module_Exception_Adone::Rendered);
}
/**
* Filters to apply before the validate call
*
* This function provides the ability for things to happen before the call to
* {@link Krai_Module_Action::Validate()}. It should always call
* <code>
* parent::BeforeFilters();
* </code>
* in order to allow stacking.
*
*/
public function BeforeFilters()
{ }
/**
* Filters to apply before any rendering takes place. If no render call is made,
* this is not executed.
*
* This function provides the ability for things to happen before a rendering
* takes place via either {@link Krai_Module_Action::Render()} or {@link Krai_Module_Action::RenderText()}
* It should always call
* <code>
* parent::BeforeRender();
* </code>
* in order to allow stacking.
*
*/
public function BeforeRender()
{ }
/**
* Filters to apply after any rendering takes place. If no render call is made,
* this is not executed.
*
* This function provides the ability for things to happen after a rendering
* takes place via either {@link Krai_Module_Action::Render()} or {@link Krai_Module_Action::RenderText()}
* It should always call
* <code>
* parent::AfterRender();
* </code>
* in order to allow stacking.
*
*/
public function AfterRender()
{ }
/**
* Filters to apply after the Display call
*
* This function provides the ability for things to happen after the call to
* {@link Krai_Module_Action::Display()}. It should always call
* <code>
* parent::AfterFilters();
* </code>
* in order to allow stacking.
*
*/
public function AfterFilters()
{ }
/**
* Execute a redirect
*
* This function executes a redirect by means of the {@link Krai_Module::RedirectTo()}
* function. See that function for details.
*
* @param string $module The name of the module or the url
* @param string $action The name of the action or null to use a url
* @param array $params The array of parameters
* @see Krai_Module::RedirectTo()
*/
protected function RedirectTo($module, $action = null, array $params = array())
{
$this->_parent->RedirectTo($module, $action, $params);
}
/**
* Execute a redirect behind the scenes
*
* This function executes a redirect by means of the {@link Krai_Module::RedirectSilent()}
* function. See that function for details.
*
* @param string $module The name of the module
* @param string $action The name of the action
* @param array $params The array of parameters
* @see Krai_Module::RedirectSilent()
*/
protected function RedirectSilent($_module, $_action, array $_params = array())
{
$this->_parent->RedirectSilent($_module, $_action, $_params);
}
}