Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Html/Tag.php
<?php

namespace XF\Html;

use function
in_array;

/**
 * Represents an individual tag within an HTML tree.
 */
class Tag
{
   
/**
     * Name of the tag (lower case).
     *
     * @var string
     */
   
protected $_tagName = '';

   
/**
     * Key-value pairs of attributes for the tag
     *
     * @var array
     */
   
protected $_attributes = [];

   
/**
     * Parent tag object.
     *
     * @var Tag|null Null for root tag
     */
   
protected $_parent = null;

   
/**
     * List of child tags and text.
     *
     * @var array Values are Tag or Text elements
     */
   
protected $_children = [];

   
/**
     * List of tags that are considered to be block tags.
     *
     * @var array
     */
   
protected $_blockTags = [
       
'address', 'article', 'aside', 'blockquote', 'dd', 'details',
       
'dialog', 'div', 'dl', 'dt', 'fieldset', 'figcaption',
       
'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
       
'header', 'hgroup', 'hr', 'li', 'main', 'nav', 'ol', 'p', 'pre',
       
'section', 'table', 'tbody', 'tfoot', 'thead', 'tr', 'ul'
       
// note that "" is not here
   
];

    protected
$_voidTags = [
       
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'wbr'
   
];

    protected
$_replacedTags = [
       
'audio', 'video'
   
];

   
/**
     * Constructor.
     *
     * @param string $tagName
     * @param array $attributes
     * @param Tag $parent
     */
   
public function __construct($tagName, array $attributes = [], Tag $parent = null)
    {
       
$this->_tagName = strtolower($tagName);
       
$this->_attributes = $attributes;
       
$this->_parent = $parent;
    }

   
/**
     * Appends text to the tag. If the last child is text, it will be added
     * to that child; otherwise, a new child will be created.
     *
     * @param string $text
     */
   
public function appendText($text)
    {
        if (
$this->isVoid())
        {
            throw new \
LogicException('Void tag ' . htmlspecialchars($this->_tagName) . ' cannot have children');
        }

        if (
$this->_children)
        {
           
$keys = array_keys($this->_children);
           
$lastKey = end($keys);
            if (
$this->_children[$lastKey] instanceof Text)
            {
               
$this->_children[$lastKey]->addText($text);
                return;
            }
        }

       
$this->_children[] = new Text($text, $this);
    }

   
/**
     * Adds a new child tag.
     *
     * @param string $tagName
     * @param array $attributes
     *
     * @return Tag New child tag
     */
   
public function addChildTag($tagName, array $attributes = [])
    {
        if (
$this->isVoid())
        {
            throw new \
LogicException('Void tag ' . htmlspecialchars($this->_tagName) . ' cannot have children');
        }

        if ((
$tagName == 'li' || $tagName == 'p') && $tagName == $this->_tagName)
        {
           
// can't child to this tag, should be a sibling
           
$parent = $this->parent();
            if (
$parent)
            {
                return
$parent->addChildTag($tagName, $attributes);
            }
        }

       
$child = new Tag($tagName, $attributes, $this);
       
$this->_children[] = $child;

        return
$child;
    }

   
/**
     * Closes the given tag. This generally does not require modifying the tag tree,
     * unless invalid nesting occurred.
     *
     * @param string $tagName
     *
     * @return Tag The new "parent" tag that should be used by the parser
     */
   
public function closeTag($tagName)
    {
       
$tagName = strtolower($tagName);
        if (
$tagName == $this->_tagName || $this->isVoid())
        {
            return
$this->_parent;
        }
        else
        {
           
$stack = [];
            for (
$tag = $this; $tag && $tag->tagName() != $tagName; $tag = $tag->parent())
            {
               
$stack[] = $tag;
            }

            if (
$tag)
            {
               
$newParent = $tag->closeTag($tagName);
                while (
$createTag = array_pop($stack))
                {
                   
$newParent = $newParent->addChildTag($createTag->tagName(), $createTag->attributes());
                }

                return
$newParent;
            }
            else
            {
               
// tag not found, ignore it
               
return $this->_parent;
            }
        }
    }

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

   
/**
     * Gets the attributes.
     *
     * @return array
     */
   
public function attributes()
    {
        return
$this->_attributes;
    }

   
/**
     * Gets the named attribute.
     *
     * @param string $attribute
     *
     * @return mixed|false
     */
   
public function attribute($attribute)
    {
        return (
$this->_attributes[$attribute] ?? false);
    }

    public function
hasClass($findClass)
    {
       
$class = $this->attribute('class');
        if (
$class)
        {
            return
strpos(" $class ", " $findClass ") !== false;
        }
        else
        {
            return
false;
        }
    }

   
/**
     * Gets the parent tag.
     *
     * @return Tag|null
     */
   
public function parent()
    {
        return
$this->_parent;
    }

   
/**
     * Sets the parent tag. This does not check for circular references!
     *
     * @param Tag $parent
     */
   
public function setParent(Tag $parent)
    {
       
$this->_parent = $parent;
    }

   
/**
     * Gets the child tags and text.
     *
     * @return array
     */
   
public function children()
    {
        return
$this->_children;
    }

   
/**
     * Copies this tag. Does not copy any children tags or this tag's parent. The
     * parent will need to be set manually later.
     *
     * @return Tag
     */
   
public function copy()
    {
        return new
Tag($this->_tagName, $this->_attributes);
    }

   
/**
     * Determines if the tag has renderable content within.
     *
     * @return boolean
     */
   
public function isEmpty()
    {
        switch (
$this->_tagName)
        {
            case
'img':
            case
'br':
                return
false;
        }

        foreach (
$this->children() AS $child)
        {
            if (
$child instanceof Tag)
            {
                if (!
$child->isEmpty())
                {
                    return
false;
                }
            }
            else if (
$child instanceof Text)
            {
                if (
trim($child->text()) !== '')
                {
                    return
false;
                }
            }
        }

        return
true;
    }

   
/**
     * Determines if this tag is a block-level tag.
     *
     * @return boolean
     */
   
public function isBlock()
    {
        return
in_array($this->_tagName, $this->_blockTags);
    }

   
/**
     * Determines if this tag is a void tag. Void tags can't have children.
     *
     * @return boolean
     */
   
public function isVoid()
    {
        return
in_array($this->_tagName, $this->_voidTags);
    }

   
/**
     * Determines if this tag is valid even if it's empty. Automatically includes all void tags.
     * Should only be used for tags that render content without children (such as a video tag).
     *
     * @return bool
     */
   
public function isAllowedEmpty()
    {
        return
$this->isVoid() || $this->isReplaced();
    }

   
/**
     * Determines if the tag's output is normally replaced (when rendered by the browser). These
     * tags can optionally have child tags/output that aren't normally displayed unless there's
     * a reason. Normally you wouldn't want to count line breaks within these tags, for example.
     *
     * @return bool
     */
   
public function isReplaced()
    {
        return
in_array($this->_tagName, $this->_replacedTags);
    }
}