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];
        $view = $this->views[$namespace][$name];
        $method = 'show_'.$view['type'];
        return $this->$method($view, $args);
    }

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