['custom-var-table', 'name-value-table'] ]; /** * Create a new CustomVarTable * * @param iterable $data * @param ?Model $object */ public function __construct($data, Model $object = null) { $this->data = $data; $this->object = $object; $this->body = new HtmlElement('tbody'); } /** * Set the header to show * * @param string $title * * @return $this */ protected function setHeader(string $title): self { $this->headerTitle = $title; return $this; } /** * Add a new row to the body * * @param mixed $name * @param mixed $value * * @return void */ protected function addRow($name, $value) { $this->body->addHtml(new HtmlElement( 'tr', Attributes::create(['class' => "level-{$this->level}"]), new HtmlElement('th', null, Html::wantHtml($name)), new HtmlElement('td', null, Html::wantHtml($value)) )); } /** * Render a variable * * @param mixed $name * @param mixed $value * * @return void */ protected function renderVar($name, $value) { if ($this->object !== null && $this->level === 0) { list($name, $value, $group) = call_user_func($this->hookApplier, $name, $value); if ($group !== null) { $this->groups[$group][] = [$name, $value]; return; } } $isArray = is_array($value); switch (true) { case $isArray && is_int(key($value)): $this->renderArray($name, $value); break; case $isArray: $this->renderObject($name, $value); break; default: $this->renderScalar($name, $value); } } /** * Render an array * * @param string $name * @param array $array * * @return void */ protected function renderArray($name, array $array) { $numItems = count($array); $name = (new HtmlDocument())->addHtml( Html::wantHtml($name), Text::create(' (Array)') ); $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems)); ++$this->level; ksort($array); foreach ($array as $key => $value) { $this->renderVar("[$key]", $value); } --$this->level; } /** * Render an object (associative array) * * @param mixed $name * @param array $object * * @return void */ protected function renderObject($name, array $object) { $numItems = count($object); $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems)); ++$this->level; ksort($object); foreach ($object as $key => $value) { $this->renderVar($key, $value); } --$this->level; } /** * Render a scalar * * @param mixed $name * @param mixed $value * * @return void */ protected function renderScalar($name, $value) { if ($value === '') { $value = new EmptyState(t('empty string')); } $this->addRow($name, $value); } /** * Render a group * * @param string $name * @param iterable $entries * * @return void */ protected function renderGroup(string $name, $entries) { $table = new self($entries); $wrapper = $this->getWrapper(); if ($wrapper === null) { $wrapper = new HtmlDocument(); $wrapper->addHtml($this); $this->prependWrapper($wrapper); } $wrapper->addHtml($table->setHeader($name)); } protected function assemble() { if ($this->object !== null) { $this->hookApplier = CustomVarRendererHook::prepareForObject($this->object); } if ($this->headerTitle !== null) { $this->getAttributes() ->add('class', 'collapsible') ->add('data-visible-height', 100) ->add('data-toggle-element', 'thead') ->add( 'id', preg_replace('/\s+/', '-', strtolower($this->headerTitle)) . '-customvars' ); $this->addHtml(new HtmlElement('thead', null, new HtmlElement( 'tr', null, new HtmlElement( 'th', Attributes::create(['colspan' => 2]), new HtmlElement( 'span', null, new Icon('angle-right'), new Icon('angle-down') ), Text::create($this->headerTitle) ) ))); } if (is_array($this->data)) { ksort($this->data); } foreach ($this->data as $name => $value) { $this->renderVar($name, $value); } $this->addHtml($this->body); // Hooks can return objects as replacement for keys, hence a generator is needed for group entries $genGenerator = function ($entries) { foreach ($entries as list($key, $value)) { yield $key => $value; } }; foreach ($this->groups as $group => $entries) { $this->renderGroup($group, $genGenerator($entries)); } } }