Can_Do.php

<?php

namespace Phad;

trait Can_Do {

    public $force_allow_access = false;

    /**
     * Check an access string. 
     * @alias can_access just to make it easier to simply check a string
     * 
     * @param string a string like 'role:admin;call:whatever;'
     * @param $ItemInfo optional array of ItemInfo from a compiled phad item
     *
     * @return true/false
     *
     * @throws exception if a function is not defined.
     */
    public function can(string $function, $ItemInfo=null){
        return $this->can_access(['access'=>$function], $ItemInfo);
    }


    /**
     * @param $node array of the node's attributes & it's tagName
     */
    public function can_read_node(array $node){
        if (!isset($node['access'])){return true;}

       
        return $this->can_access($node);
    }

    /**
     * Checks if the data node can be accessed. 
     *
     * @param $node the data node (as an array)
     * @param $Info item info object
     * @return true/false 
     */
    public function can_read_data(array $node, \Phad\ItemInfo $Info): bool {
        if ($Info->mode==\Phad\Blocks::FORM_SUBMIT&&$node['type']!='default')return false;
        if (!isset($node['access'])){
            return true;
        }

        return $this->can_access($node);
    }

    /**
     * Check an access string
     * 
     * @param array $node a nodeinfo array containing a key 'access' with a string like 'role:admin;call:whatever;'
     * @param $ItemInfo optional array of ItemInfo from a compiled phad item
     *
     * @return true/false
     *
     * @throws exception if a function is not defined.
     */
    public function can_access(array $node, $ItemInfo=null){
        $access_string = $node['access'];
        $functions = $this->parse_functions($access_string);
        if ($this->force_allow_access === true)return true;

        foreach ($functions as $fn=>/*string*/$arg){
            // var_dump("Function:".$fn);
            if ($fn=='role'){
                if (!$this->access->user_has_role($arg))return false;
            } else if ($fn=='request_method'){
                if (strtolower($_SERVER['REQUEST_METHOD'])!=strtolower($arg))return false;
            } else if ($fn=='call'){
                //@todo append to `$Info->errors[]` ??
                if($this->access->call(\Phad\Caller::CAN_ACCESS, $arg, $ItemInfo, $node)!==true)return false;
            } else if ($fn=='std'){
                if ($arg=='false')return false;
                else if ($arg=='true')return true;
                else {
                    throw new \Exception(sprintf(\Phad\Errors::STD_ACCESS_FUNCTION_NOT_EXIST, $fn, $arg, $access_string));
                }
            } else if ($fn=="fn"){
                if (function_exists($arg)){
                    $ret = $arg();
                    if (!is_bool($ret)){
                        throw new \Exception("PHP function '$arg' MUST return strict boolean `true` or `false` when used as an access control");
                    }
                    return $ret;
                } else {
                    throw new \Exception("PHP function '$arg' does not exist, so '$fn:$arg' cannot be processed");
                }
            } else {
                throw new \Exception("'$fn:$arg' cannot be processed because access control prefix '$fn' is not supported."); 
            }
        }

        return true;
    }


    public function can_delete($Info){
        $candelete = $Info->candelete;
        //@todo test the different branches for can_delete() & delete()
        if (!isset($Info->args['id'])){
            $Info->submit_errors[] = ['msg'=>"Cannot delete '".$Info->name."' without an id. No id was submitted."];
            return false;
        } else if ($candelete===true)return true;
        else if ($candelete===false){
            $Info->submit_errors[] = ['msg'=>"Deletion of '".$Info->name."' is disabled."];
            return false;
        }

        $functions = $this->parse_functions($Info->candelete);
        foreach ($functions as $fn=> /* string */ $args){
            if ($fn=='role'){
                if (!$this->user_has_role($args)){
                    $Info->submit_errors[] = ['msg'=>"Deletion of '".$Info->name."' not allowed unless you have role(s): '$args'"];
                    return false;
                }
            }
            else if ($fn=='call'){
                $count = count($Info->submit_errors);
                $did_pass = $this->access->call(\Phad\Caller::CAN_DELETE, $args, $Info);
                if ($did_pass!==true){
                    if (count($Info->submit_errors)==$count){
                        $item_name = $Info->name;
                        $Info->submit_errors[] = ['msg'=>"Deleting '$item_name' rejected by '$fn:$args'"];
                    }
                    return false;
                }
            }

        }
    
        return true;
    }



    public function can_submit(\Phad\ItemInfo $Info, $ItemRow){
        //@todo test the different branches for can_delete() & delete()
        if (isset($Info->cancel_submit)&&$Info->cancel_submit === true){
            // var_dump($Info->cansubmit);
            return false;
        }

        $functions = $this->parse_functions($Info->cansubmit);
        foreach ($functions as $fn=> /* string */ $args){
            if ($fn=='role'){
                if (!$this->user_has_role($args)){
                    $Info->submit_errors[] = ['msg'=>"Deletion of '".$Info->name."' not allowed unless you have role(s): '$args'"];
                    return false;
                }
            }
            else if ($fn=='call'){
                $count = count($Info->submit_errors);
                $did_pass = $this->access->call(\Phad\Caller::CAN_SUBMIT, $args, $Info, $ItemRow);
                if ($did_pass!==true){
                    if (count($Info->submit_errors)==$count){
                        $item_name = $Info->name;
                        $Info->submit_errors[] = ['msg'=>"Deleting '$item_name' rejected by '$fn:$args'"];
                    }
                    return false;
                }
            }

        }
    
        return true;
    }
}