View.php

<?php

namespace Lia\Addon;


/**
 * @todo enable fallback-namespacing when default namespace (null) is not set. So you can add view `lia:theme`, then load view `theme` and get `lia:theme`
 * @warning conflictMode has a weird interaction with null-namespace views. If you add `ns:viewname`, then try to add `viewname` a conflict will now be generated because `ns:viewname` sets null-namespace `viewname`.
 */
class View extends \Lia\Addon {

    public string $fqn = 'lia:server.view';
    public string $name = 'view';


    /**
     * Views to be loaded.
     * @structure `['namespace'=>['view/name'=>$view_callable]]`
     */
    public $views = [null=>[]];

    /** a lazy way to map viewname => package */
    public array $packages = [];

    /**
     * Args to pass to every view
     *
     */
    public array $globalArgs = [];


    public function init_lia(){
        $lia = $this->lia;
        $lia->methods['view'] = [$this, 'view'];
        $lia->methods['addView'] = [$this, 'addView'];
        $lia->methods['addViewCallable'] = [$this, 'addViewCallable'];
    }

    /** @deprecated this is just here to prevent BC breaks*/
    public function getHeadHtml(){
        return $this->lia->getHeadHtml();
    }

    public function addDir($dir, $package){
        $lia = $this->lia;
        $files = \Lia\Utility\Files::all($dir,$dir, '.php');
        foreach ($files as $f){
            //remove leading `/` and trailing `.php` in a pretty simple, dumb way
            $viewName = substr($f,1,-4);
            $this->packages[$viewName] = $package;
            $viewName = $package->name.':'.$viewName;
            $this->addView($viewName,$dir);
        }
    }

    public function addView($view_name, $dir){
        $parts = explode(':',$view_name);
        $name = array_pop($parts);
        $namespace = array_shift($parts);

        $view = [
            'type'=>'main',
            'dir'=>$dir,
            'name'=>$name,
        ];

        $this->views[$namespace][$name] = $view;
        $this->views[null][$name] = $view;
        // $this->views[null][$name] = $this->views[null][$name] ?? $view;
    }

    public function addViewFile($view_name, $file){
        $parts = explode(':',$view_name);
        $name = array_pop($parts);
        $namespace = array_shift($parts);

        $view = [
            'type'=>'file',
            'file'=>$file
        ];

        $this->views[$namespace][$name] = $view;
        $this->views[null][$name] = $view;
        // $this->views[null][$name] = $this->views[null][$name] ?? $view;
    }

    public function addViewCallable($view_name, $callable){
        $parts = explode(':',$view_name);
        $name = array_pop($parts);
        $namespace = array_shift($parts);

        $view = [
            'type'=>'callable',
            'callable'=>$callable,
            'name'=>$name
        ];

        $this->views[$namespace][$name] = $view;
        $this->views[null][$name] = $view;
        // $this->views[null][$name] = $this->views[null][$name] ?? $view;
    }

    /**
     * @todo let the view name be nested in dirs
     */
    public function view($name, $args=[]){
        $args['lia'] = $args['lia'] ?? $this->lia;
        foreach ($this->globalArgs as $key=>$value){
            if (!isset($args[$key]))$args[$key] = $value;
        }
        $parts = explode(':',$name);
        $name = array_pop($parts);
        $namespace = array_shift($parts);

        // if (!isset($this->views[$namespace][$name])){
            // throw new \Exception("View '$namespace:$name' does not exist.");
        // }
        if (isset($this->packages[$name]))$args['package'] = $this->packages[$name];
        if (!isset($this->views[$namespace][$name])){
            $ns_name = $namespace ?? 'null';
            throw new \Lia\Exception(\Lia\Exception::VIEW_NOT_FOUND, $ns_name.':'.$name);
        }
        $view = $this->views[$namespace][$name];
        $method = 'show_'.$view['type'];

        $content = $this->$method($view, $args);


        if ($this->lia!=null&&$this->lia->has_method('call_hook')){
            $rets = $this->lia->call_hook(\Lia\Hooks::VIEW_LOADED, $namespace, $name, $view, $content);
            $c = count($rets);
            if ($c==1){
                $content = $rets[0];
            } else if ($c > 1){
                throw new \Lia\Exception(\Lia\Exception::ONLY_ONE_VIEW_LOADED_HOOK_ALLOWED);
            }
        }

        return $content;
    }

    public function show_file($view, $args){
        ob_start();
        extract($args);
        require($view['file']);
        $content = ob_get_clean();
        return $content;
    }

    public function show_callable($view, $args){
        return $view['callable']($view['name'], $args);
    }

    public function show_main($view, $args){
        $path = $view['dir'].'/'.$view['name'];

        if (file_exists($path.'.js'))$this->lia->addResourceFile($path.'.js');
        if (file_exists($path.'.css'))$this->lia->addResourceFile($path.'.css');

        $this->addFiles($path);

        ob_start();
        extract($args);
        require($path.'.php');
        $content = ob_get_clean();

        return $content;
    }

    /**
     * add resource files in the given directory
     * @param $dir the directory to scan 
     * @note(jan 17, 2022) only scans cur_level ... does not descend
     */
    public function addFiles($dir){
        foreach (is_dir($dir)?scandir($dir):[] as $file){
            if (substr($file,-4)=='.css'||substr($file,-3)=='.js'){
                $this->lia->addResourceFile($dir.'/'.$file);
            }
        }
    }

    /**
     * Rename the original_namespace to the new_namespace. The old namespace is removed.
     * Only affects views already added. 
     * @throw \Lia\Exception if original_namespace does not exist, or if new_namespace already exists.
     */
    public function change_namespace(string $original_namespace, string $new_namespace, bool $allow_overrides = false){

        //echo "\n\nCHANGE NS\n\n";
        //print_r(array_keys($this->views));

        if (!isset($this->views[$original_namespace])){
            throw new \Lia\Exception(\Lia\Exception::CANNOT_COPY_NONEXISTENT_VIEW_NS, $original_namespace, $new_namespace);
        } else if (isset($this->views[$new_namespace])){
            foreach ($this->views[$original_namespace] as $k=>$v){
                if (!$allow_overrides&&isset($this->views[$new_namespace][$k])){
                    throw new \Lia\Exception(\Lia\Exception::CANNOT_OVERWRITE_EXISTING_VIEW, $original_namespace.':'.$k, $new_namespace);
                }
                $this->views[$new_namespace][$k] = $v;
            }
        }
        $this->views[$new_namespace] = $this->views[$original_namespace];
        unset($this->views[$original_namespace]);

    }
}