* * @license http://opensource.org/licenses/bsd-license.php BSD * * @version $Id: Page.php 1880 2006-09-30 19:45:18Z pmjones $ * */ /** * Load Solar_Uri_Action for dispatch comparisons. */ Solar::loadClass('Solar_Uri_Action'); /** * * Abstract page controller class. * * Expects a directory structure similar to the following ... * * Vendor/ # your vendor namespace * App/ # subdirectory for page controllers * Helper/ # shared helper classes * ... * Layout/ # shared layout files * ... * Locale/ # shared locale files * ... * View/ # shared view scripts * ... * Example.php # an example page controller app * Example/ * Helper/ # helper classes specific to the page * ... * Layout/ # layout files to override shared layouts * ... * Locale/ # locale files * en_US.php * pt_BR.php * View/ # view scripts * _item.php # partial template * list.php # full template * edit.php * * Note that models are not included in the application itself; this is * for class-name deconfliction reasons. Your models should be stored * elsewhere in the Solar hierarchy, e.g. Vendor_Model_Name. * * When you call [[fetch()]], these intercept methods * are run in the following order ... * * * [[_load()]] to load class properties from the * fetch() URI specification * * * [[_preRun()]] before the first action * * * [[_preAction()]] before each action (including * _forward()-ed actions) * * * ... The action method itself runs here ... * * * [[_postAction()]] after each action * * * [[_postRun()]] after the last action, and before rendering * * * [[_render()]] to render the view and layout; * this in its turn calls [[_getView()]] for * the view object, and [[_setViewLayout()]] to * reset the view object to use layout templates. * * @category Solar * * @package Solar_Controller * */ abstract class Solar_Controller_Page extends Solar_Base { /** * * The default application action. * * @var string * */ protected $_action_default = null; /** * * The action being requested of (performed by) the application. * * @var string * */ protected $_action = null; /** * * Session data, including read-once flashes. * * @var Solar_Session * */ protected $_session; /** * * Application request parameters collected from the URI pathinfo. * * @var array * */ protected $_info = array(); /** * * The name of the layout to be rendered. * * @var string * */ protected $_layout = null; /** * * The name of the variable where page content is placed in the layout. * * Default is 'layout_content'. * * @var string * */ protected $_layout_var = 'layout_content'; /** * * The short-name of this application. * * @var string * */ protected $_name = null; /** * * Application request parameters collected from the URI query string. * * @var string * */ protected $_query = array(); /** * * Name of the form element with a 'submit' value. * * @var string * * @see Solar_Controller_Page::_submit() * */ protected $_submit_key = 'submit'; /** * * The name of the view to be rendered. * * @var string * */ protected $_view = null; /** * * Request environment details: get, post, etc. * * @var Solar_Request * */ protected $_request; // these helper classes will be added in the middle of the stack, between the // solar-view-helper final fallbacks and the vendor+app specific helpers. protected $_helper_class = array(); /** * * Constructor. * * @param array $config User-provided configuration values. * */ public function __construct($config = null) { $class = get_class($this); // create the request object $this->_request = Solar::factory('Solar_Request'); // auto-set the name; e.g. Solar_App_Something => 'something' if (empty($this->_name)) { $pos = strrpos($class, '_'); $this->_name = substr($class, $pos + 1); $this->_name[0] = strtolower($this->_name[0]); } // create the flash object $this->_session = Solar::factory( 'Solar_Session', array('class' => $class) ); // now do the parent construction parent::__construct($config); // extended setup $this->_setup(); } /** * * Try to force users to define what their view variables are. * * @param string $key The property name. * * @param mixed $val The property value. * * @return void * */ public function __set($key, $val) { throw $this->_exception( 'ERR_PROPERTY_NOT_DEFINED', array('property' => "\$$key") ); } /** * * Try to force users to define what their view variables are. * * @param string $key The property name. * * @return void * */ public function __get($key) { throw $this->_exception( 'ERR_PROPERTY_NOT_DEFINED', array('property' => "\$$key") ); } /** * * Executes the requested action and returns its output with layout. * * @param string $spec The action specification string, e.g., * "tags/php+framework" or "user/pmjones/php+framework?page=3" * * @return string The results of the action + view + layout. * */ public function fetch($spec = null) { // load action, info, and query properties $this->_load($spec); // prerun hook $this->_preRun(); // action chain, with pre- and post-action hooks var_dump($this->_action);exit; $this->_forward($this->_action, $this->_info); // postrun hook $this->_postRun(); // render the view and layout, with pre- and post-render hooks return $this->_render(); } /** * * Executes the requested action and displays its output. * * @param string $spec The action specification string, e.g., * "tags/php+framework" or "user/pmjones/php+framework?page=3" * * @return void * */ public function display($spec = null) { echo $this->fetch($spec); } /** * * Renders the view with layout with pre- and post-rendering. * * @return string The results of the view and layout scripts. * */ protected function _render() { // get a view object and assign variables $view = $this->_getView(); $this->_preRender($view); $view->assign($this); try { $output = $view->fetch($this->_view . '.php'); } catch (Solar_View_Exception_TemplateNotFound $e) { $view->errors[] = $this->locale('ERR_TEMPLATE_NOT_FOUND'); $view->errors[] = implode(PATH_SEPARATOR, $e->getInfo('path')); $view->errors[] = $e->getInfo('name'); $output = $view->fetch('error.php'); } // are we using a layout? if (! $this->_layout) { // no layout, just use the post-render filter on the view. return $this->_postRender($output); } else { // using a layout. reset the view to use Layout templates. $this->_setViewLayout($view); // assign the output $view->assign($this->_layout_var, $output); // use the post-render filter on the layout return $this->_postRender($view->fetch($this->_layout . '.php')); } } /** * * Creates and returns a new Solar_View object for a view. * * Automatically sets up a template-path stack for you, searching * for view files in this order ... * * 1. Vendor/App/Example/View/ * * 2. Vendor/App/View * * Automatically sets up a helper-class stack for you, searching * for helper classes in this order ... * * 1. Vendor_App_Example_Helper_ * * 2. Vendor_App_Helper_ * * 3. Vendor_View_Helper_ * * 4. Solar_View_Helper_ (this is part of Solar_View to begin with) * * @return Solar_View * */ protected function _getView() { $view = Solar::factory('Solar_View'); // get the current class $class = get_class($this); // get the parent-level class $pos = strrpos($class, '_'); $parent = substr($class, 0, $pos); // who's the vendor? $pos = strpos($class, '_'); $vendor = substr($class, 0, $pos); // add template paths to the view object. // the order of searching will be: // Vendor/App/Example/View, Vendor/App/View, Solar/App/View $template = array(); $template[] = str_replace('_', DIRECTORY_SEPARATOR, "{$class}_View"); $template[] = str_replace('_', DIRECTORY_SEPARATOR, "{$parent}_View"); if ($vendor != 'Solar') { // non-Solar vendor, add Solar views as final fallback $template[] = str_replace('_', DIRECTORY_SEPARATOR, 'Solar_App_View'); } $view->addTemplatePath($template); // add helper classes to the view object. // the order of searching will be: // Vendor_App_Example_Helper_*, Vendor_App_Helper_*, // Vendor_View_Helper_*, Solar_View_Helper_* $helper = array(); $helper[] = $class . '_Helper'; $helper[] = $parent . '_Helper'; $helper[] = $vendor . '_View_Helper'; // are there additional helper classes we need to add? if (! empty($this->_helper_class)) { $helper = array_merge($helper, (array) $this->_helper_class); } /** * @todo: do we really need this? The View class already has the * Solar_View_Helper class in the stack. */ // if ($vendor != 'Solar') { // // non-Solar vendor, add Solar helpers as final fallback // $helper[] = 'Solar_View_Helper'; // } $view->addHelperClass($helper); // set the locale class for the getText helper $view->getTextRaw("$class::"); // done! return $view; } /** * * Points an existing Solar_View object to the Layout templates. * * This effectively re-uses the Solar_View object from the page * (with its helper objects and data) to build the layout. This * helps to transfer JavaScript and other layout data back up to * the layout with zero effort. * * Automatically sets up a template-path stack for you, searching * for layout files in this order ... * * 1. Vendor/App/Example/Layout/ * * 2. Vendor/App/Layout/ * * 3. Solar/App/Layout/ * * @param Solar_View $view The Solar_View object to modify. * * @return Solar_View * */ protected function _setViewLayout($view) { // get the current class $class = get_class($this); // get the parent-level class $pos = strrpos($class, '_'); $parent = substr($class, 0, $pos); // who's the vendor? $pos = strpos($class, '_'); $vendor = substr($class, 0, $pos); // reset template paths in the view object. // the order of searching will be: // Vendor/App/Example/Layout, Vendor/App/Layout, Solar/App/Layout $template = array(); $template[] = str_replace('_', DIRECTORY_SEPARATOR, "{$class}_Layout"); $template[] = str_replace('_', DIRECTORY_SEPARATOR, "{$parent}_Layout"); if ($vendor != 'Solar') { // non-Solar vendor, add Solar views as final fallback $template[] = str_replace('_', DIRECTORY_SEPARATOR, 'Solar_App_Layout'); } $view->setTemplatePath($template); } /** * * Loads properties from an action specification. * * @param string $spec The action specification. * * @return void * */ protected function _load($spec) { // process the page/action/info specification if (! $spec) { // no spec, use the current URI $uri = Solar::factory('Solar_Uri_Action'); $this->_info = $uri->path; $this->_query = $uri->query; } elseif ($spec instanceof Solar_Uri_Action) { // pull from a Solar_Uri_Action object $this->_info = $spec->path; $this->_query = $spec->query; } else { // a string, assumed to be a page/action/info?query spec. $uri = Solar::factory('Solar_Uri_Action'); $uri->set($spec); $this->_info = $uri->path; $this->_query = $uri->query; } echo "
" ; var_Dump($this->_info);
// remove the page name from the info
if (! empty($this->_info[0]) && $this->_info[0] == $this->_name) {
array_shift($this->_info);
}
// do we have an initial info element as an action method?
if (! empty($this->_info[0])) {
echo "Checking method naming\n";
$method = $this->_getActionMethod($this->_info[0]);
if ($method) {
// save it and remove from info
$this->_action = array_shift($this->_info);
}
}
// if no action yet, use the default
if (! $this->_action) {
$this->_action = $this->_action_default;
}
}
/**
*
* Retrieves the TAINTED value of a path-info value by position.
*
* Note that this value is direct user input; you should sanitize it
* with Solar_Valid or Solar_Filter (or some other technique) before
* using it.
*
* @param int $key The path-info position number.
*
* @param mixed $val If the key does not exist, use this value
* as a default in its place.
*
* @return mixed The value of that query key.
*
*/
protected function _info($key, $val = null)
{
if (array_key_exists($key, $this->_info) && $this->_info[$key] !== null) {
return $this->_info[$key];
} else {
return $val;
}
}
/**
*
* Retrieves the TAINTED value of a query request key by name.
*
* Note that this value is direct user input; you should sanitize it
* with Solar_Valid or Solar_Filter (or some other technique) before
* using it.
*
* @param string $key The query key.
*
* @param mixed $val If the key does not exist, use this value
* as a default in its place.
*
* @return mixed The value of that query key.
*
*/
protected function _query($key, $val = null)
{
if (array_key_exists($key, $this->_query) && $this->_query[$key] !== null) {
return $this->_query[$key];
} else {
return $val;
}
}
/**
*
* Redirects to another page and action.
*
* @param Solar_Uri_Action|string $spec The URI to redirect to.
*
* @return void
*
*/
protected function _redirect($spec)
{
if ($spec instanceof Solar_Uri_Action) {
$href = $spec->fetch();
} elseif (strpos($spec, '://') !== false) {
// external link, protect against header injections
$href = str_replace(array("\r", "\n"), '', $spec);
} else {
$uri = Solar::factory('Solar_Uri_Action');
$href = $uri->quick($spec);
}
// make sure there's actually an href
$href = trim($href);
if (! $href || trim($spec) == '') {
throw $this->_exception('ERR_REDIRECT_FAILED', array(
'spec' => $spec,
'href' => $href,
));
}
// kill off all output buffers and redirect
while(@ob_end_clean());
header("Location: $href");
exit;
}
/**
*
* Forwards internally to another action, using pre- and post-
* action hooks, and resets $this->_view to the requested action.
*
* You should generally use "return $this->_forward(...)" instead
* of just $this->_forward; otherwise, script execution will come
* back to where you called the forwarding.
*
* @param string $action The action name.
*
* @param array $params Parameters to pass to the action method.
*
* @return void
*
*/
protected function _forward($action, $params = null)
{
// set the current action on entry
$this->_action = $action;
// run this before every action, may change the
// requested action.
$this->_preAction();
// does a related action-method exist?
$method = $this->_getActionMethod($this->_action);
if (! $method) {
throw $this->_exception(
'ERR_ACTION_NOT_FOUND',
array(
'action' => $this->_action,
)
);
}
// set the view to the requested action
$this->_view = $this->_getActionView($this->_action);
// run the action script, which may itself _forward() to
// other actions. pass all pathinfo parameters in order.
if (empty($params)) {
// speed boost
$this->$method();
} else {
// somewhat slower
call_user_func_array(
array($this, $method),
(array) $params
);
}
// run this after every action
$this->_postAction();
// set the current action on exit so that $this->_action is
// always the **first** action requested when we finally exit.
$this->_action = $action;
}
/**
*
* Reports whether or not user requested a specific submit type.
*
* By default, looks for $submit_key in [[Solar_Request::post()]] to get the
* value of the submit request.
*
* Checks against "SUBMIT_$type" locale string for matching. E.g.,
* $this->_isSubmit('save') checks Solar_Request::post('submit')
* against $this->locale('SUBMIT_SAVE').
*
* @param string $type The submit type; e.g., 'save', 'delete',
* 'preview', etc. If empty, returns true if *any* submission type
* was posted.
*
* @param string $submit_key If not empty, check against this
* [[Solar_Request::post()]] key instead $this->_submit_key. Default
* null.
*
* @return bool
*
*/
protected function _isSubmit($type = null, $submit_key = null)
{
// make sure we know what post-var to look in
if (empty($submit_key)) {
$submit_key = $this->_submit_key;
}
// didn't ask for a submission type; answer if *any* submission
// was attempted.
if (empty($type)) {
$any = $this->_request->post($submit_key);
return ! empty($any);
}
// asked for a submission type, find the locale string for it.
$locale_key = 'SUBMIT_' . strtoupper($type);
$locale = $this->locale($locale_key);
// $submit must be non-empty, and must match locale string.
// not enough just to match the locale string, as it might
// be empty.
$submit = $this->_request->post($submit_key, false);
return $submit && $submit == $locale;
}
/**
*
* Returns the method name for an action.
*
* @param string $action The action name.
*
* @return string The method name, or boolean false if the action
* method does not exist.
*
*/
protected function _getActionMethod($action)
{
// convert example-name and example_name to "actionExampleName"
$word = str_replace(array('_', '-'), ' ', $action);
$word = ucwords(trim($word));
$word = 'action' . str_replace(' ', '', $word);
// does it exist?
if (method_exists($this, $word)) {
return $word;
} else {
return false;
}
}
/**
*
* Returns the view name for an action.
*
* @param string $action The action name.
*
* @return string The related view name.
*
*/
protected function _getActionView($action)
{
// convert example-name and example_name to exampleName
$word = str_replace(array('_', '-'), ' ', $action);
$word = ucwords(trim($word));
$word = str_replace(' ', '', $word);
$word[0] = strtolower($word[0]);
return $word;
}
// -----------------------------------------------------------------
//
// Behavior hooks.
//
// -----------------------------------------------------------------
/**
*
* Executes after construction.
*
* @return void
*
*/
protected function _setup()
{
}
/**
*
* Executes before the first action.
*
* @return void
*
*/
protected function _preRun()
{
}
/**
*
* Executes before each action.
*
* @return void
*
*/
protected function _preAction()
{
}
/**
*
* Executes after each action.
*
* @return void
*
*/
protected function _postAction()
{
}
/**
*
* Executes after the last action.
*
* @return void
*
*/
protected function _postRun()
{
}
/**
*
* Executes before rendering the page view and layout.
*
* Use this to pre-process the Solar_View object, or to manipulate
* controller properties with view helpers.
*
* @param Solar_View $view The Solar_View object for rendering the
* page view script.
*
* @return void
*
*/
protected function _preRender($view)
{
}
/**
*
* Executes after rendering the page view and layout.
*
* Use this to do a final filter or maniuplation of the output text
* from the view and layout scripts. By default, it leaves the
* rendered output alone and returns it as-is.
*
* @param string $output The output from the rendered view and layout.
*
* @return string The filtered output.
*
*/
protected function _postRender($output)
{
return $output;
}
}
?>