373 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			373 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace nur\v\base;
 | 
						|
 | 
						|
use nur\A;
 | 
						|
use nur\b\coll\BaseArray;
 | 
						|
use nur\b\coll\GenericArray;
 | 
						|
use nur\b\ExitError;
 | 
						|
use nur\co;
 | 
						|
use nur\config;
 | 
						|
use nur\func;
 | 
						|
use nur\json;
 | 
						|
use nur\v\html5\Html5BasicErrorPage;
 | 
						|
use nur\v\model\IChildComponent;
 | 
						|
use nur\v\model\IComponent;
 | 
						|
use nur\v\model\IErrorPage;
 | 
						|
use nur\v\model\IPage;
 | 
						|
use nur\v\model\IPageContainer;
 | 
						|
use nur\v\model\IPlugin;
 | 
						|
use nur\v\page;
 | 
						|
use nur\v\prefix;
 | 
						|
use nur\v\vo;
 | 
						|
use Throwable;
 | 
						|
use Traversable;
 | 
						|
 | 
						|
abstract class AbstractPageContainer implements IPageContainer {
 | 
						|
  protected static function ensure_preparec(IComponent $c, bool $afterPrepare=false): bool {
 | 
						|
    if (!$c->didPrepare()) {
 | 
						|
      $c->beforePrepare();
 | 
						|
      $c->prepare();
 | 
						|
      if ($afterPrepare) $c->afterPrepare();
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  
 | 
						|
  protected static function ensure_configc(IComponent $c, array &$config, bool $afterConfig=false): bool {
 | 
						|
    if (!$c->didConfig()) {
 | 
						|
      $c->beforeConfig($config);
 | 
						|
      $c->config($config);
 | 
						|
      if ($afterConfig) $c->afterConfig();
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  
 | 
						|
  protected static function ensure_setupc(IComponent $c, bool $afterSetup=false, &$output=null): bool {
 | 
						|
    if (!$c->didSetup()) {
 | 
						|
      $c->beforeSetup();
 | 
						|
      $output = $c->setup();
 | 
						|
      if ($afterSetup) $c->afterSetup();
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  
 | 
						|
  protected static function ensure_teardownc(IComponent $c, bool $afterTeardown=false): bool {
 | 
						|
    if (!$c->didTeardown()) {
 | 
						|
      $c->beforeTeardown();
 | 
						|
      $c->teardown();
 | 
						|
      if ($afterTeardown) $c->afterTeardown();
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  
 | 
						|
  protected static function printc(IComponent $c, ?array &$config): void {
 | 
						|
    self::ensure_preparec($c, true);
 | 
						|
    self::ensure_configc($c, $config, true);
 | 
						|
    self::ensure_setupc($c, true);
 | 
						|
    try {
 | 
						|
      if ($c->haveContent()) vo::write($c);
 | 
						|
    } finally {
 | 
						|
      self::ensure_teardownc($c, true);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  #############################################################################
 | 
						|
 | 
						|
  /** configurer les classes utilisées dans le cadre de ce container */
 | 
						|
  protected abstract function initToolkit(): void;
 | 
						|
 | 
						|
  function __construct(?array $config=null) {
 | 
						|
    $this->initToolkit();
 | 
						|
    $this->config = [
 | 
						|
      "configure_options" => null,
 | 
						|
      "error_page_class" => null,
 | 
						|
      "self" => null,
 | 
						|
      "prefix" => prefix::compute(null),
 | 
						|
      "title" => null,
 | 
						|
      "css" => [],
 | 
						|
      "js" => [],
 | 
						|
      "plugins" => [],
 | 
						|
    ];
 | 
						|
    $css = A::getdel($config, "css");
 | 
						|
    $js = A::getdel($config, "js");
 | 
						|
    $plugins = A::getdel($config, "plugins");
 | 
						|
    A::merge_nz($this->config, $config);
 | 
						|
    A::merge($this->config["css"], $css);
 | 
						|
    A::merge($this->config["js"], $js);
 | 
						|
    A::merge($this->config["plugins"], $plugins);
 | 
						|
    $this->params = new GenericArray();
 | 
						|
    $this->components = [];
 | 
						|
  }
 | 
						|
 | 
						|
  /** @return array les options par défaut pour {@link config::configure()} */
 | 
						|
  protected function CONFIGURE_OPTIONS(): ?array {
 | 
						|
    return static::CONFIGURE_OPTIONS;
 | 
						|
  } const CONFIGURE_OPTIONS = null;
 | 
						|
 | 
						|
  /** @return string la page utilisée pour afficher les erreurs */
 | 
						|
  protected function ERROR_PAGE_CLASS(): string {
 | 
						|
    return static::ERROR_PAGE_CLASS;
 | 
						|
  } const ERROR_PAGE_CLASS = Html5BasicErrorPage::class;
 | 
						|
 | 
						|
  /**
 | 
						|
   * @return string chemin du script correspondant à cette page, depuis la racine
 | 
						|
   * de l'application e.g. index.php, ou null s'il faut utiliser la valeur par
 | 
						|
   * défaut
 | 
						|
   */
 | 
						|
  protected function SELF(): ?string {
 | 
						|
    return static::SELF;
 | 
						|
  } const SELF = null;
 | 
						|
 | 
						|
  /** @return string titre de la page */
 | 
						|
  protected function TITLE(): ?string {
 | 
						|
    return static::TITLE;
 | 
						|
  } const TITLE = null;
 | 
						|
 | 
						|
  /** @return ?string|array liste de feuilles CSS à charger dans la page */
 | 
						|
  protected function CSS() {
 | 
						|
    return static::CSS;
 | 
						|
  } const CSS = null;
 | 
						|
 | 
						|
  /** @return ?string|array liste de scripts à charger dans la page */
 | 
						|
  protected function JS() {
 | 
						|
    return static::JS;
 | 
						|
  } const JS = null;
 | 
						|
 | 
						|
  /** @return ?string|array liste de plugins à ajouter à cette page */
 | 
						|
  protected function PLUGINS() {
 | 
						|
    return static::PLUGINS;
 | 
						|
  } const PLUGINS = null;
 | 
						|
 | 
						|
  /** @var array */
 | 
						|
  protected $config;
 | 
						|
 | 
						|
  protected function initConfig(): void {
 | 
						|
    A::replace_z($this->config, "configure_options", $this->CONFIGURE_OPTIONS());
 | 
						|
    A::replace_z($this->config, "error_page_class", $this->ERROR_PAGE_CLASS());
 | 
						|
    $self = $this->SELF();
 | 
						|
    if ($self !== null) {
 | 
						|
      A::merge($this->config, [
 | 
						|
        "self" => $self,
 | 
						|
        "prefix" => prefix::compute($self),
 | 
						|
      ]);
 | 
						|
    }
 | 
						|
    A::replace_z($this->config, "title", $this->TITLE());
 | 
						|
    A::merge($this->config["css"], $this->CSS());
 | 
						|
    A::merge($this->config["js"], $this->JS());
 | 
						|
    A::merge($this->config["plugins"], $this->PLUGINS());
 | 
						|
  }
 | 
						|
 | 
						|
  function getConfig(): array { return $this->config; }
 | 
						|
  function getErrorPage(): IErrorPage {
 | 
						|
    # NB: cette fonction peut être appelée AVANT initConfig()
 | 
						|
    $error_page_class = $this->config["error_page_class"];
 | 
						|
    if ($error_page_class === null) $error_page_class = $this->ERROR_PAGE_CLASS();
 | 
						|
    if ($error_page_class === null) $error_page_class = Html5BasicErrorPage::class;
 | 
						|
    $error_page = new $error_page_class();
 | 
						|
    $error_page->initContainer($this);
 | 
						|
    return $error_page;
 | 
						|
  }
 | 
						|
  function getSelf(): ?string { return $this->config["self"]; }
 | 
						|
  function getSelfRelativePrefix(): string { return $this->config["prefix"]; }
 | 
						|
  function getTitle(): string { return $this->config["title"]; }
 | 
						|
  function getCssUrls(): array { return $this->config["css"]; }
 | 
						|
  function getJsUrls(): array { return $this->config["js"]; }
 | 
						|
  function getPlugins(): array { return $this->config["plugins"]; }
 | 
						|
 | 
						|
  /** @var BaseArray */
 | 
						|
  protected $params;
 | 
						|
 | 
						|
  function getParams(): BaseArray {
 | 
						|
    return $this->params;
 | 
						|
  }
 | 
						|
 | 
						|
  function haveError(): bool {
 | 
						|
    return boolval($this->params["page_error"]);
 | 
						|
  }
 | 
						|
 | 
						|
  function setError(?string $message, ?Throwable $exception=null): void {
 | 
						|
    $this->params["page_error"] = [
 | 
						|
      "message" => $message,
 | 
						|
      "exception" => $exception,
 | 
						|
    ];
 | 
						|
  }
 | 
						|
 | 
						|
  function getError(): ?array {
 | 
						|
    return $this->params["page_error"];
 | 
						|
  }
 | 
						|
 | 
						|
  # null = init, 0 = prepare, 1 = config, 2 = setup, 3 = print, 4 = teardown
 | 
						|
  const INIT_PHASE = null;
 | 
						|
  const PREPARE_PHASE = 0;
 | 
						|
  const CONFIG_PHASE = 1;
 | 
						|
  const SETUP_PHASE = 2;
 | 
						|
  const PRINT_PHASE = 3;
 | 
						|
  const TEARDOWN_PHASE = 4;
 | 
						|
 | 
						|
  protected $phase;
 | 
						|
 | 
						|
  protected function ensure_phasec(IComponent $c) {
 | 
						|
    $phase = $this->phase;
 | 
						|
    if ($phase !== self::INIT_PHASE) {
 | 
						|
      if ($phase >= self::PREPARE_PHASE) self::ensure_preparec($c);
 | 
						|
      if ($phase >= self::CONFIG_PHASE) self::ensure_configc($c, $this->config);
 | 
						|
      if ($phase >= self::SETUP_PHASE) self::ensure_setupc($c);
 | 
						|
      if ($phase >= self::TEARDOWN_PHASE) self::ensure_teardownc($c);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /** @var array composants enregistrés dans ce container, indexés par nom */
 | 
						|
  protected $components;
 | 
						|
 | 
						|
  function addPlugin($plugin, ?string $name=null): IPlugin {
 | 
						|
    if (is_string($plugin)) $plugin = new $plugin();
 | 
						|
    elseif (is_array($plugin)) $plugin = func::cons(...$plugin);
 | 
						|
    if ($plugin instanceof IChildComponent) $plugin->initContainer($this);
 | 
						|
    if ($plugin instanceof IComponent) $this->ensure_phasec($plugin);
 | 
						|
    A::set($this->components, $name, $plugin);
 | 
						|
    return $plugin;
 | 
						|
  }
 | 
						|
 | 
						|
  /** @var IPage composant de page à afficher dans ce container */
 | 
						|
  protected $page;
 | 
						|
 | 
						|
  function setPage(IPage $page): void {
 | 
						|
    if ($page instanceof IChildComponent) $page->initContainer($this);
 | 
						|
    $this->page = $page;
 | 
						|
    $this->haveOutput = false;
 | 
						|
  }
 | 
						|
 | 
						|
  /** @var bool ce container a-t-il déjà commencé à afficher du contenu? */
 | 
						|
  protected $haveOutput;
 | 
						|
 | 
						|
  function haveOutput(): bool {
 | 
						|
    return $this->haveOutput;
 | 
						|
  }
 | 
						|
 | 
						|
  function print(): void {
 | 
						|
    $page = $this->page;
 | 
						|
    page::set_current_page($page);
 | 
						|
    $output = null;
 | 
						|
 | 
						|
    try {
 | 
						|
      $this->phase = self::PREPARE_PHASE;
 | 
						|
      if (self::ensure_preparec($page)) {
 | 
						|
        $this->overridePrepare($page);
 | 
						|
        $page->afterPrepare();
 | 
						|
      }
 | 
						|
 | 
						|
      $this->phase = self::CONFIG_PHASE;
 | 
						|
      $this->initConfig();
 | 
						|
      if (self::ensure_configc($page, $this->config)) {
 | 
						|
        $this->overrideConfig($page);
 | 
						|
        $page->afterConfig();
 | 
						|
      }
 | 
						|
      config::configure($this->config["configure_options"]);
 | 
						|
 | 
						|
      $this->phase = self::SETUP_PHASE;
 | 
						|
      if (self::ensure_setupc($page, false, $output)) {
 | 
						|
        if ($output === false)  {
 | 
						|
          # désactiver la gestion des actions si le retour est false (retour en
 | 
						|
          # l'état)
 | 
						|
          $page->dispatchAction(false);
 | 
						|
        }
 | 
						|
        $this->overrideSetup($page);
 | 
						|
        $page->afterSetup();
 | 
						|
      }
 | 
						|
 | 
						|
      $this->phase = self::PRINT_PHASE;
 | 
						|
      if ($output instanceof IComponent) {
 | 
						|
        # composant autonome
 | 
						|
        self::ensure_phasec($output);
 | 
						|
        if ($this->beforePrint($output)) {
 | 
						|
          $this->haveOutput = true;
 | 
						|
          co::_print([$output]);
 | 
						|
        }
 | 
						|
      } elseif (is_callable($output)) {
 | 
						|
        # générateur de contenu
 | 
						|
        if ($this->beforePrint(null)) {
 | 
						|
          $this->haveOutput = true;
 | 
						|
          $output();
 | 
						|
        }
 | 
						|
      } elseif ($output === false) {
 | 
						|
        # retour en l'état (contenu déjà géré dans setup())
 | 
						|
        $this->haveOutput = false;
 | 
						|
      } elseif ($output !== null) {
 | 
						|
        # contenu json ou texte
 | 
						|
        if ($this->beforePrint(null)) {
 | 
						|
          $this->haveOutput = true;
 | 
						|
          if (is_iterable($output)) {
 | 
						|
            header("Content-Type: application/json");
 | 
						|
            echo "[";
 | 
						|
            $sep = "";
 | 
						|
            foreach ($output as $data) {
 | 
						|
              $line = json::encode($data);
 | 
						|
              echo "$sep$line\n";
 | 
						|
              $sep = ",";
 | 
						|
            }
 | 
						|
            echo "]";
 | 
						|
          } else {
 | 
						|
            co::_write([strval($output)]);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        # afficher la page normalement
 | 
						|
        $this->overridePrint($page);
 | 
						|
      }
 | 
						|
    } catch (Throwable $e) {
 | 
						|
      if ($e instanceof ExitError && !$e->isError()) {
 | 
						|
        # NOP
 | 
						|
      } else {
 | 
						|
        $this->setError(null, $e);
 | 
						|
      }
 | 
						|
 | 
						|
    } finally {
 | 
						|
      if ($this->haveError()) {
 | 
						|
        $errorPage = $this->getErrorPage();
 | 
						|
        if ($this->haveOutput()) {
 | 
						|
          # n'afficher que l'erreur si on a déjà du contenu
 | 
						|
          $errorPage->printError();
 | 
						|
        } else {
 | 
						|
          # sinon afficher la page entière
 | 
						|
          $errorPage->print();
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if ($page->didSetup()) {
 | 
						|
        $this->phase = self::TEARDOWN_PHASE;
 | 
						|
        if ($output instanceof IComponent) {
 | 
						|
          self::ensure_teardownc($output);
 | 
						|
        }
 | 
						|
        if (self::ensure_teardownc($page)) {
 | 
						|
          $this->overrideTeardown($page);
 | 
						|
          $page->afterTeardown();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  protected function overridePrepare(IPage $page): void {
 | 
						|
  }
 | 
						|
 | 
						|
  protected function overrideConfig(IPage $page): void {
 | 
						|
  }
 | 
						|
 | 
						|
  protected function overrideSetup(IPage $page): void {
 | 
						|
  }
 | 
						|
 | 
						|
  protected function beforePrint(?IComponent $component): bool {
 | 
						|
    return $component === null || $component->haveContent();
 | 
						|
  }
 | 
						|
 | 
						|
  protected function overridePrint(IPage $page): void {
 | 
						|
    if (!$this->beforePrint($page)) return;
 | 
						|
    $this->haveOutput = true;
 | 
						|
    co::_print([$page]);
 | 
						|
  }
 | 
						|
 | 
						|
  protected function overrideTeardown(IPage $page): void {
 | 
						|
  }
 | 
						|
}
 |