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