<?php
/** $Id: Action.php 24677 2010-08-05 21:05:52Z parf $
*
* SYNOPSIS: Action
*
* Web Action Base Class
*
* DESCRIPTION:
*
* Base class for Web Actions
*
* Access/Set all parameters using $this->_["NAME"] or $this->NAME
* Access named parameters using function parameters
*
* METHODS NAMING
Methods starting with "_" are private - they can't be called as actions
Methods starting with "a_" url:"a." (URLs starting with a-,a_,a.) are treated as NON-CACHEABLE ajax
Methods starting with "c_" url:"c." (URLs starting with a-,a_,a.) are treated as CACHEABLE ajax
IF action method returns 0 - template is NOT used
* if Action is not found - system returns template with the same name as action
*
See Controller for Action return codes
URL to ACTION MAPPING
/ -> Action_Root->index
/Name -> Action_Root->Name
/Name/ -> Action_Name->index
/Name/Name2 -> Action_Name->Name2
/Name/Name2/Name3 -> Action_Name_Name2->Name3
/Name/Name.ext -> Action_Name->Name_ext (underscore)
/Name/first-second -> Action_Name->first__second (2 underscores)
In case if a system can't find a method with this name, it will return template NAME
**/
class Action
{
var $_; // ref to Parameter list
var $method; // current method
var $url; // current url
function __construct (&$_) {
$this->_=& $_;
}
static function _i($method="_custom") { # Action
$p=$_GET+$_POST;
$a=new Action($p);
if ($method) $a->method=$method;
return $a;
}
// only registered user can pass
/*
If not registered and ajax:
=> 403 error
If POST request:
=> Human readable message about logging in
Default:
=> Redirect to Login/Reg, then return back to original page
*/
function _require_reg($msg="", $mess="") { # return null || redirect to login screen || error
if (id()) return;
if (CD("AJAX")) $this->_forbidden($msg);
if ($_POST) $this->_auth_required($msg);
if (($this->url[0] ?? null) === '/') {
$this->url=substr($this->url,1);
}
//$backurl=url("/".$this->url, $_GET);
$backurl=url(NVL(@$_SERVER["HTTP_X_URI"], @$_SERVER["REQUEST_URI"], "/".$this->url), $_GET);
$p = ["backurl" => $backurl];
if ($mess) {
$p["mess"] = $mess;
}
go( url("/login/login", $p ), 302 );
}
// called FIRST !! before any method
// VOID = Continue with processing
// other value - NEW method name (e.g. _404, _access_denied, _forbidden, your_method)
// new method will be called with $original_method_name as parameter
function _access() { # void || method_name
}
// called before method
// VOID = Continue with processing
// other value - NEW template name
function _common() { # null|string || template
}
// called when there are no corresponding method
// VOID = Continue with processing - use template with the same name
// other value - route to other method / or template
function _missing_method() { # void || new_method || template
}
// error page along with http code
// message - shown to user
function _error($code, $text, $msg="") { # return error code and die
Profiler::disable();
header("HTTP/1.1 $code $text");
echo "<h1>$text</h1>".$msg;
$mn=get_class($this)."->".$this->method;
\Log::notice("$text $mn","_$code");
if ($code==401) {
echo "<p><a href=/>Login or Register</a> to access this page</p>";
}
if (Debug::is_admin() ) {
ob_flush(); // avoid header code override in is_admin
Debug::pre("Method: $mn\nError#$code\n");
}
die;
}
// Page not found handler
function _not_found($msg="") { # 404 error & die
header("Status: 404 Not Found");
self::_error(404, "Not Found", $msg);
}
// Page gone handler
function _gone($msg="") { # 404 error & die
header("Status: 410 Gone");
self::_error(410, "Gone", $msg);
}
function _auth_required($msg="") { # 401 error & die
self::_error(401, "Authentication Required", $msg);
}
function _forbidden($msg="") { # 403 error & die
self::_error(403, "Access Denied", $msg);
}
// Action Documentation Access
function doc($class="", $cfn="") {
if (! Debug::is_admin() ) \Log::alert("Access Denied");
Action_Helper::doc( NVL($class, get_class($this)), $cfn );
return 0;
}
function _call($method, $url) {
$this->method=$method;
$this->url=$url;
if ($method[0]=='_') {
if (PHP_SAPI != 'cli')
$this->_forbidden("Can't call internal method");
}
// seems like a bug
if ($a=$this->_access($method)) return $this->$a($method); // common access handler
if ($c=$this->_common($method)) return $c;
$mex=1; // method exists
if (! method_exists($this, $method) ) {
$mex=0;
// missing method processing
Profiler::in("Action::missing method");
$m=$this->_missing_method();
Profiler::out($m);
if ($m) {
$method=$m;
if (method_exists($this, $method) )
$mex=1;
}
}
if ($mex)
// use template name (same as url)
return $url;
}
/* internal */
// serve method (normal or gzipped)
function _serve($m = '') {
if ($this->gz)
return "_gz";
return $this->method;
}
/* RETURN GZIPPED METHOD_ONLY!! OUTPUT */
function _gz($method="") {
if (! $method) $method=$this->method;
ob_start();
$this->$method();
$r=ob_get_clean();
Controller::gz_serve($r);
return 0;
}
// ------ INTERNAL --------------------------------------------------
// GET request FIELD
public function __get($field) { if (isset($this->_[$field])) return $this->_[$field]; }
// SET request FIELD
public function __set($field, $value) { return $this->_[$field]=$value; }
// call function (php reflection api case)
// pass parameters according to it's php definition
private function _reflection_call($method) { #
try {
$rm=new ReflectionMethod($this, $method);
$params=$rm->getParameters();
$r=[];
foreach ($params as $p) {
$k=$p->name;
if (array_key_exists($k, $this->_) )
$r[$k]=$this->_[$k];
else {
if ($p->isDefaultValueAvailable() )
$r[$k]=$p->getDefaultValue();
else {
$msg = "Parameter '$k' is missing and is not optional";
\Log::notice($msg,"",0);
self::_error(400, "Incorrect request", $msg); // http://stackoverflow.com/questions/6123425/rest-response-code-for-invalid-data
}
}
}
} catch(Exception $ex) {
\Log::alert( $ex->getMessage() );
}
try {
return call_user_func_array( [$this, $method], $r);
} catch(RedirectException $ex) {
return $ex->getMessage();
}
}
function page404() {
$this->_not_found();
}
} // end class
class RedirectException extends Exception {
static function page($message) {
throw new RedirectException($message);
}
}