hnhdigital-os/laravel-html-builder

View on GitHub
src/Tag.php

Summary

Maintainability
D
2 days
Test Coverage
<?php

namespace HnhDigital\LaravelHtmlBuilder;

use HnhDigital\LaravelHtmlGenerator\Html;
use Illuminate\Support\Arr;

/**
 * @method static p(...$attributes)
 * @method static div(...$attributes)
 * @method static span(...$attributes)
 * @method static label(...$attributes)
 * @method static table(...$attributes)
 * @method static thead(...$attributes)
 * @method static tbody(...$attributes)
 * @method static th(...$attributes)
 * @method static td(...$attributes)
 * @method static tr(...$attributes)
 *
 * @mixin Tag
 */
class Tag
{
    private static $tag_registry = [];
    private static $special_tags = [
        'img', 'br', 'hr', 'input', 'area', 'link', 'meta', 'param',
    ];

    protected $tag = 'tag';
    protected $text = '';
    protected $attributes = [];
    protected $parent_node;
    protected $child_nodes = [];
    protected $allowed_tags = [];
    protected $allowed_text = false;
    protected $use_whitespace = true;

    /**
     * Create a new tag object.
     *
     * @param  string  $tag
     * @param  array  $attributes
     * @param  string  $text
     * @return self
     */
    public static function create($tag, $attributes = [], $text = '')
    {
        return (new self())->add($tag, $attributes, $text);
    }

    /**
     * Add a child node to this tag.
     *
     * @param  string  $tag
     * @param  array  $arguments
     * @return self
     */
    public function add($tag, ...$arguments)
    {
        $tag = strtolower($tag);
        $text = '';
        $attributes = [];

        for ($i = 0; $i <= 1; $i++) {
            if (Arr::has($arguments, $i)) {
                if (is_array(Arr::get($arguments, $i))) {
                    $attributes = Arr::get($arguments, $i);
                } elseif (is_string(Arr::get($arguments, $i)) || is_object(Arr::get($arguments, $i))) {
                    $text = Arr::get($arguments, $i);
                }
            }
        }

        if ($this->tag === 'tag'
            || empty($this->allowed_tags)
            || in_array($tag, $this->allowed_tags)
            || in_array($tag, self::$special_tags)) {
            // Tag belongs to the special tags list.
            if (in_array($tag, self::$special_tags)) {
                $tag_object = Html::createElement($tag);
                $tag_object->text($text)->addAttributes($attributes);
                self::$tag_registry[] = &$tag_object;
                $this->child_nodes[] = &$tag_object;

                return $tag_object;
            }

            // Create the class name for this tag
            $class_name = 'HnhDigital\\LaravelHtmlBuilder\\Tag\\'.ucfirst($tag);

            // If the tag exists, otherwise we throw an exception.
            if (class_exists($class_name)) {
                $tag_object = (new $class_name())
                    ->setAttributes($attributes)
                    ->setText($text);
                self::$tag_registry[] = &$tag_object;
                $this->child_nodes[] = &$tag_object;
                $tag_object->setParent($tag_object);

                return $tag_object;
            }

            throw new \Exception($tag.' does not exist.');
        }

        throw new \Exception($this->tag.' does permit '.$tag);
    }

    /**
     * Get the tag parent node.
     *
     * @return self
     */
    public function getParent()
    {
        return $this->parent_node;
    }

    /**
     * Set the parent of this tag.
     *
     * @param  LaravelHtmlBuilder\Tag  $tag_object
     * @return self
     */
    public function setParent(&$tag_object)
    {
        $this->parent_node = &$tag_object;

        return $this;
    }

    /**
     * Set the tag name.
     *
     * @return self
     */
    public function setTag($tag)
    {
        if (in_array($tag, $this->getParent()->allowed_tags)) {
            throw new \Exception(
                $tag.' can not be changed as it is not allowed under '.$this->getParent()->getTag().'.'
            );
        }

        $this->tag = $tag;

        return $this;
    }

    /**
     * Get the tag name.
     *
     * @return string
     */
    public function getTag()
    {
        return $this->tag;
    }

    /**
     * Set the text for this tag.
     *
     * @param  string  $value
     * @return self
     */
    public function setText($value)
    {
        $this->text = $value;

        // Auto-convert into html.
        if (is_object($value) && stripos(get_class($value), 'HtmlGenerator\\Html')) {
            $this->text = (string) $value;
        }

        return $this;
    }

    /**
     * Get the text value.
     *
     * @return string
     */
    public function getText()
    {
        return $this->text;
    }

    /**
     * Set one or many attributes.
     *
     * @param  array  ...$attributes
     * @return self
     */
    public function setAttributes()
    {
        $attributes = func_get_args();
        if (count($attributes)) {
            if (count($attributes) == 1 && is_array($attributes[0])) {
                $attributes = $attributes[0];
            }
            $this->attributes = $attributes;
        }

        return $this;
    }

    /**
     * Get the attributes for this tag.
     *
     * @return array
     */
    public function getAttributes()
    {
        return $this->attributes;
    }

    /**
     * Set a single attribute for this tag.
     *
     * @param  string  $name
     * @param  string  $value
     * @return self
     */
    public function setAttribute($name, $value)
    {
        $this->attributes[$name] = $value;

        return $this;
    }

    /**
     * Get a single attribute value from this tag.
     *
     * @param  string  $name
     * @return string
     */
    public function getAttribute($name)
    {
        return (isset($this->attributes[$name])) ? $this->attributes[$name] : null;
    }

    /**
     * Checks to see if this tag has child nodes.
     *
     * @return bool
     */
    public function hasChildNodes()
    {
        return count($this->child_nodes) > 0;
    }

    /**
     * Returns list of child nodes.
     *
     * @return array
     */
    public function getChildNodes()
    {
        return $this->child_nodes;
    }

    /**
     * Checks and defaults the options request for a build request.
     *
     * @param  array  &$options
     * @return void
     */
    private static function checkBuildOptions(&$options)
    {
        if (isset($options['ignore_tags']) && ! is_array($options['ignore_tags'])) {
            $options['ignore_tags'] = [$options['ignore_tags']];
        }
    }

    /**
     * Build html from this object.
     *
     * @param  string  &$html
     * @param  LaravelHtmlBuilder\Tag  $tag_object
     * @param  array  $options
     * @param  int  $tab
     * @return void
     */
    private static function buildHtml(&$html, $tag_object, $options, $tab = 0)
    {
        self::checkBuildOptions($options);

        $tag_is_special = in_array($tag_object->getTag(), self::$special_tags);
        $tag_is_ignored = in_array($tag_object->getTag(), Arr::get($options, 'ignore_tags', []));

        $ignore_tag = true;
        if (! isset($options['ignore_tags']) || ! $tag_is_ignored) {
            $ignore_tag = false;
        } elseif (isset($options['ignore_tags']) && $tag_is_ignored) {
            $tab--;
        }

        $pad = '';
        if ($tab > 0) {
            $pad = str_pad($pad, $tab * 2, ' ', STR_PAD_LEFT);
        }

        if (! $ignore_tag) {
            if ($tag_is_special) {
                $html .= $pad.trim((string) $tag_object)."\n";
            } elseif (! $tag_is_special) {
                $html .= $pad;
                $html .= '<'.$tag_object->tag.''.self::buildHtmlAttribute($tag_object->getAttributes()).'>';
                $html .= ($tag_object->use_whitespace) ? "\n" : '';
                if (strlen($tag_object->getText())) {
                    $html .= ($tag_object->use_whitespace) ? $pad.'  ' : '';
                    $html .= $tag_object->getText();
                }
                $html .= ($tag_object->use_whitespace && strlen($tag_object->getText())) ? "\n" : '';
            }
        }

        if (! $tag_is_special && $tag_object->hasChildNodes()) {
            foreach ($tag_object->getChildNodes() as $child_tag_object) {
                self::buildHtml($html, $child_tag_object, $options, $tab + 1);
            }
        }

        $pad = '';
        if ($tab > 0) {
            $pad = str_pad($pad, $tab * 2, ' ', STR_PAD_LEFT);
        }

        if (! $ignore_tag && ! $tag_is_special) {
            $html .= ($tag_object->use_whitespace) ? $pad : '';
            $html .= '</'.$tag_object->tag.'>'."\n";
        }
    }

    /**
     * Build html attributes from array.
     *
     * @param  array  $attributes
     * @return string
     */
    private static function buildHtmlAttribute($attributes)
    {
        $html = '';
        foreach ($attributes as $name => $value) {
            if (is_array($value)) {
                $value = implode(' ', $value);
            }
            $html .= ' '.$name.'="'.$value.'"';
            $class_list = explode(' ', $value);
            foreach ($class_list as $class_name) {
                switch ($class_name) {
                    case 'class':
                        if (function_exists('hookAddClassHtmlTag')) {
                            hookAddClassHtmlTag($class_name);
                        }
                }
            }
        }

        return $html;
    }

    /**
     * Build an array from this object.
     *
     * @param  array  &$array
     * @param  LaravelHtmlBuilder\Tag  $tag_object
     * @param  array  $options
     * @return void
     */
    private static function buildArray(&$array, $tag_object, $options = [])
    {
        self::checkBuildOptions($options);

        if (in_array($tag_object->getTag(), self::$special_tags)) {
            $array[] = [trim((string) $tag_object)];
        } elseif (! isset($options['ignore_tags']) || ! in_array($tag_object->getTag(), $options['ignore_tags'])) {
            $array[] = [
                $tag_object->getTag(),
                self::buildHtmlAttribute($tag_object->getAttributes()),
                $tag_object->getText(),
                [],
            ];
        }

        if ($tag_object->hasChildNodes()) {
            if (isset($options['ignore_tags']) && in_array($tag_object->getTag(), $options['ignore_tags'])) {
                foreach ($tag_object->getChildNodes() as $child_tag_object) {
                    self::buildArray($array, $child_tag_object);
                }

                return;
            }

            foreach ($tag_object->getChildNodes() as $child_tag_object) {
                $current_position = count($array) - 1;
                if (isset($array[$current_position][3]) && ! is_null($array[$current_position][3])) {
                    self::buildArray($array[count($array) - 1][3], $child_tag_object);
                }
            }
        }
    }

    /**
     * Build html from an array.
     *
     * @param  string  &$html
     * @param  array  $array
     * @param  int  $tab
     * @return void
     */
    public static function buildFromArray(&$html, $array, $tab = 0)
    {
        $pad = '';
        if ($tab > 0) {
            $pad = str_pad($pad, $tab * 2, ' ', STR_PAD_LEFT);
        }

        if (! isset($array[1])) {
            $html .= $array[0]."\n";

            return;
        }

        if (! empty($array[0])) {
            $html .= $pad;
            $html .= '<'.$array[0].$array[1].'>';
            $html .= "\n";
        }

        if (! empty($array[2])) {
            $html .= $pad.'  ';
            $html .= $array[2];
            $html .= "\n";
        }

        if (isset($array[3]) && is_array($array[3])) {
            foreach ($array[3] as $child_array) {
                self::buildFromArray($html, $child_array, $tab + 1);
            }
        }

        if (! empty($array[0])) {
            $html .= $pad;
            $html .= '</'.$array[0].'>';
            $html .= "\n";
        }
    }

    /**
     * Automatically returns the object based on the http request.
     *
     * @return html|array
     */
    public function prepare($options = [])
    {
        if (request()->ajax()) {
            return $this->getArray($options);
        }

        return $this->getHtml($options);
    }

    /**
     * Get html from this object.
     *
     * @param  array  $options
     * @return string
     */
    public function getHtml($options = [])
    {
        $html = '';
        self::buildHtml($html, $this, $options, 0);

        return $html;
    }

    /**
     * Get array from this object.
     *
     * @param  array  $options
     * @return string
     */
    public function getArray($options = [])
    {
        $array = [];
        self::buildArray($array, $this, $options);

        return $array;
    }

    /**
     * Get json from this object (via $this->getArray).
     *
     * @param  array  $options
     * @return string
     */
    public function getJson($options = [])
    {
        return json_encode($this->getArray($options));
    }

    /**
     * Get Html from an array (that was originally generated from $this->getArray).
     *
     * @param  array  $array
     * @return html
     */
    public static function getHtmlFromArray($array)
    {
        $html = '';
        foreach ($array as $child_array) {
            self::buildFromArray($html, $child_array);
        }

        return $html;
    }

    /**
     * This object will turn into html.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->getHtml();
    }

    /**
     * Create a html tab object.
     *
     * @param  string  $tag
     * @param  array  $arguments
     * @return mixed
     */
    public function __call($tag, $arguments)
    {
        if (empty($this->allowed_tags) || in_array($tag, $this->allowed_tags) || in_array($tag, self::$special_tags)) {
            array_unshift($arguments, $tag);

            return $this->add(...$arguments);
        }

        throw new \Exception($this->tag.' does permit '.$tag);
    }

    /**
     * Create a html tab object.
     *
     * @param  string  $tag
     * @param  array  $arguments
     * @return self $tag_object
     */
    public static function __callStatic($tag, $arguments)
    {
        array_unshift($arguments, $tag);

        return call_user_func_array([Tag::class, 'create'], $arguments);
    }
}