» /rd/lib.framework/Action/Action.php

<?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;
        }
        
gourl("/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::docNVL($classget_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)
            return 
$this->_reflection_call($method);

        
// 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);
    }

}