137748cd8SNickeau<?php 237748cd8SNickeau/** 337748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 437748cd8SNickeau * 537748cd8SNickeau * This source code is licensed under the GPL license found in the 637748cd8SNickeau * COPYING file in the root directory of this source tree. 737748cd8SNickeau * 837748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 937748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1037748cd8SNickeau * 1137748cd8SNickeau */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace ComboStrap; 1437748cd8SNickeau 1504fd306cSNickeauuse ComboStrap\Tag\BoxTag; 1604fd306cSNickeauuse ComboStrap\TagAttribute\Align; 1704fd306cSNickeauuse ComboStrap\TagAttribute\Animation; 1804fd306cSNickeauuse ComboStrap\TagAttribute\BackgroundAttribute; 1904fd306cSNickeauuse ComboStrap\TagAttribute\Boldness; 2004fd306cSNickeauuse ComboStrap\TagAttribute\Hero; 2104fd306cSNickeauuse ComboStrap\TagAttribute\Shadow; 2204fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 2304fd306cSNickeauuse ComboStrap\TagAttribute\TextAlign; 2404fd306cSNickeauuse ComboStrap\TagAttribute\Toggle; 2504fd306cSNickeauuse ComboStrap\TagAttribute\Underline; 2604fd306cSNickeauuse ComboStrap\TagAttribute\Vertical; 2704fd306cSNickeauuse ComboStrap\Web\Url; 2804fd306cSNickeauuse ComboStrap\Xml\XmlDocument; 2904fd306cSNickeauuse ComboStrap\Xml\XmlElement; 3037748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin; 3137748cd8SNickeau 3237748cd8SNickeau/** 3304fd306cSNickeau * An utility: 3404fd306cSNickeau * * to parse the jsx/component markup match 3504fd306cSNickeau * * to enforce user security (ie `style` is not allowed) 3604fd306cSNickeau * * to get from component attributes to html attributes 3704fd306cSNickeau * 3804fd306cSNickeau * 3904fd306cSNickeau * This is the equivalent of an {@link XmlElement} 4004fd306cSNickeau * but does not need any {@link XmlDocument} to be created 4137748cd8SNickeau * 4237748cd8SNickeau * You can: 4337748cd8SNickeau * * declare component attribute after parsing 4437748cd8SNickeau * * declare Html attribute during parsing 4537748cd8SNickeau * * output the final HTML attributes at the end of the process with the function {@link TagAttributes::toHTMLAttributeString()} 4637748cd8SNickeau * 4737748cd8SNickeau * Component attributes have precedence on HTML attributes. 4837748cd8SNickeau * 4937748cd8SNickeau * @package ComboStrap 5037748cd8SNickeau */ 5137748cd8SNickeauclass TagAttributes 5237748cd8SNickeau{ 5337748cd8SNickeau /** 5437748cd8SNickeau * @var string the alt attribute value (known as the title for dokuwiki) 5537748cd8SNickeau */ 5637748cd8SNickeau const TITLE_KEY = 'title'; 5737748cd8SNickeau 5837748cd8SNickeau 5937748cd8SNickeau const TYPE_KEY = "type"; 6037748cd8SNickeau const ID_KEY = "id"; 6137748cd8SNickeau 6237748cd8SNickeau /** 634cadd4f8SNickeau * If not strict, no error is reported 644cadd4f8SNickeau */ 654cadd4f8SNickeau const STRICT = "strict"; 664cadd4f8SNickeau 674cadd4f8SNickeau /** 6804fd306cSNickeau * The logical attributes that are not becoming HTML attributes 6937748cd8SNickeau * (ie internal reserved words) 704cadd4f8SNickeau * 7104fd306cSNickeau * TODO: They should be advertised by the syntax component 7237748cd8SNickeau */ 7337748cd8SNickeau const RESERVED_ATTRIBUTES = [ 7437748cd8SNickeau self::SCRIPT_KEY, // no script attribute for security reason 7537748cd8SNickeau TagAttributes::TYPE_KEY, // type is the component class 7604fd306cSNickeau MediaMarkup::LINKING_KEY, // internal to image 7704fd306cSNickeau IFetcherAbs::CACHE_KEY, // internal also 7804fd306cSNickeau Tag\WebCodeTag::RENDERING_MODE_ATTRIBUTE, 7904fd306cSNickeau Vertical::VERTICAL_ATTRIBUTE, 8037748cd8SNickeau self::OPEN_TAG, 8137748cd8SNickeau self::HTML_BEFORE, 8282a60d03SNickeau self::HTML_AFTER, 834cadd4f8SNickeau Dimension::RATIO_ATTRIBUTE, 844cadd4f8SNickeau self::STRICT, 8504fd306cSNickeau FetcherSvg::REQUESTED_PRESERVE_ATTRIBUTE, 864cadd4f8SNickeau \syntax_plugin_combo_link::CLICKABLE_ATTRIBUTE, 8704fd306cSNickeau LinkMarkup::PREVIEW_ATTRIBUTE, 884cadd4f8SNickeau Skin::SKIN_ATTRIBUTE, 894cadd4f8SNickeau ColorRgb::PRIMARY_VALUE, 904cadd4f8SNickeau ColorRgb::SECONDARY_VALUE, 9104fd306cSNickeau Dimension::ZOOM_ATTRIBUTE, 9204fd306cSNickeau Tag\FollowTag::HANDLE_ATTRIBUTE, 9304fd306cSNickeau \syntax_plugin_combo_menubar::BREAKPOINT_ATTRIBUTE, 9404fd306cSNickeau ContainerTag::CONTAINER_ATTRIBUTE, 9504fd306cSNickeau HeadingTag::HEADING_TEXT_ATTRIBUTE, 9604fd306cSNickeau self::GENERATED_ID_KEY 9737748cd8SNickeau ]; 9837748cd8SNickeau 9937748cd8SNickeau /** 10037748cd8SNickeau * The inline element 10137748cd8SNickeau * We could pass the plugin object into tag attribute in place of the logical tag 10237748cd8SNickeau * and check if the {@link SyntaxPlugin::getPType()} is normal 10337748cd8SNickeau */ 10437748cd8SNickeau const INLINE_LOGICAL_ELEMENTS = [ 10504fd306cSNickeau FetcherSvg::CANONICAL, 10604fd306cSNickeau FetcherRaster::CANONICAL, 10704fd306cSNickeau \syntax_plugin_combo_media::TAG, 10837748cd8SNickeau \syntax_plugin_combo_link::TAG, // link button for instance 10904fd306cSNickeau ButtonTag::MARKUP_LONG 11037748cd8SNickeau ]; 11104fd306cSNickeau 11204fd306cSNickeau /** 11304fd306cSNickeau * Container 11404fd306cSNickeau * Heading is a block but not a container 11504fd306cSNickeau */ 11604fd306cSNickeau const CONTAINER_LOGICAL_ELEMENTS = [ 11704fd306cSNickeau BoxTag::TAG, 11804fd306cSNickeau CardTag::CARD_TAG, 11904fd306cSNickeau BlockquoteTag::TAG, 12004fd306cSNickeau ]; 12104fd306cSNickeau 12237748cd8SNickeau const SCRIPT_KEY = "script"; 12337748cd8SNickeau const TRANSFORM = "transform"; 12437748cd8SNickeau 12537748cd8SNickeau const CANONICAL = "tag"; 1264cadd4f8SNickeau 12737748cd8SNickeau const CLASS_KEY = "class"; 12837748cd8SNickeau const WIKI_ID = "wiki-id"; 12937748cd8SNickeau 13037748cd8SNickeau /** 13137748cd8SNickeau * The open tag attributes 13237748cd8SNickeau * permit to not close the tag in {@link TagAttributes::toHtmlEnterTag()} 13337748cd8SNickeau * 13437748cd8SNickeau * It's used for instance by the {@link \syntax_plugin_combo_tooltip} 13537748cd8SNickeau * to advertise that it will add attribute and close it 13637748cd8SNickeau */ 13737748cd8SNickeau const OPEN_TAG = "open-tag"; 13837748cd8SNickeau 13937748cd8SNickeau /** 14037748cd8SNickeau * If an attribute has this value, 14137748cd8SNickeau * it will not be added to the output (ie {@link TagAttributes::toHtmlEnterTag()}) 14237748cd8SNickeau * Child element can unset attribute this way 14337748cd8SNickeau * in order to write their own 14437748cd8SNickeau * 14537748cd8SNickeau * This is used by the {@link \syntax_plugin_combo_tooltip} 14637748cd8SNickeau * to advertise that the title attribute should not be set 14737748cd8SNickeau */ 14837748cd8SNickeau const UN_SET = "unset"; 14937748cd8SNickeau 15037748cd8SNickeau /** 15137748cd8SNickeau * When wrapping an element 15237748cd8SNickeau * A tag may get HTML before and after 15337748cd8SNickeau * Uses for instance to wrap a svg in span 15437748cd8SNickeau * when adding a {@link \syntax_plugin_combo_tooltip} 15537748cd8SNickeau */ 15637748cd8SNickeau const HTML_BEFORE = "htmlBefore"; 15737748cd8SNickeau const HTML_AFTER = "htmlAfter"; 15837748cd8SNickeau 15937748cd8SNickeau /** 16082a60d03SNickeau * Attribute with multiple values 16182a60d03SNickeau */ 16204fd306cSNickeau const MULTIPLE_VALUES_ATTRIBUTES = [self::CLASS_KEY, self::REL, Align::ALIGN_ATTRIBUTE]; 1634cadd4f8SNickeau 1644cadd4f8SNickeau /** 1654cadd4f8SNickeau * Link relation attributes 1664cadd4f8SNickeau * https://html.spec.whatwg.org/multipage/links.html#linkTypes 1674cadd4f8SNickeau */ 1684cadd4f8SNickeau const REL = "rel"; 1694cadd4f8SNickeau 17004fd306cSNickeau /** 17104fd306cSNickeau * The default id if no one is specified 17204fd306cSNickeau */ 17304fd306cSNickeau const GENERATED_ID_KEY = "generated_id"; 17404fd306cSNickeau 17504fd306cSNickeau /** 17604fd306cSNickeau * The attributes that may flow into an HTML output 17704fd306cSNickeau * TODO: href comes from {@link \syntax_plugin_combo_brand}, it should be corrected to use {@link LinkMarkup} 17804fd306cSNickeau */ 17904fd306cSNickeau const HTML_ATTRIBUTES = [ 18004fd306cSNickeau TagAttributes::CLASS_KEY, 18104fd306cSNickeau StyleAttribute::STYLE_ATTRIBUTE, 18204fd306cSNickeau TagAttributes::ID_KEY, 18304fd306cSNickeau TagAttributes::TITLE_KEY, 18404fd306cSNickeau "href", 18504fd306cSNickeau "rel", // anchor 18604fd306cSNickeau "name", // iframe 18704fd306cSNickeau "frameborder", // iframe 18804fd306cSNickeau "target" // a 18904fd306cSNickeau ]; 19004fd306cSNickeau 19104fd306cSNickeau /** 19204fd306cSNickeau * Attribute that cannot be deleted 19304fd306cSNickeau * TODO: This is because the request object and the response object are the same. We should add the request attribute in the {@link \TagAttributes} 19404fd306cSNickeau */ 19504fd306cSNickeau const PROTECTED_ATTRIBUTES = [ 19604fd306cSNickeau TagAttributes::TYPE_KEY 19704fd306cSNickeau ]; 19804fd306cSNickeau const NAME_ATTRIBUTE = "name"; 19904fd306cSNickeau 20004fd306cSNickeau /** 20104fd306cSNickeau * The dokuwiki name attribute to store 20204fd306cSNickeau * text node data 20304fd306cSNickeau */ 20404fd306cSNickeau public const DOKUWIKI_TEXT_NODE_ATTRIBUTE = "_data"; 20582a60d03SNickeau 20682a60d03SNickeau /** 20737748cd8SNickeau * A global static counter 20837748cd8SNickeau * to {@link TagAttributes::generateAndSetId()} 20937748cd8SNickeau */ 21037748cd8SNickeau private static $counter = 0; 21137748cd8SNickeau 21237748cd8SNickeau 21337748cd8SNickeau /** 21404fd306cSNickeau * @var ArrayCaseInsensitive attribute that were set on a component 21537748cd8SNickeau */ 21604fd306cSNickeau private ArrayCaseInsensitive $componentAttributesCaseInsensitive; 21737748cd8SNickeau 21837748cd8SNickeau /** 21937748cd8SNickeau * @var array the style declaration array 22037748cd8SNickeau */ 22104fd306cSNickeau private array $styleDeclaration = array(); 22237748cd8SNickeau 22337748cd8SNickeau /** 22437748cd8SNickeau * @var bool - set when the transformation from component attribute to html attribute 22537748cd8SNickeau * was done to avoid circular problem 22637748cd8SNickeau */ 22737748cd8SNickeau private $componentToHtmlAttributeProcessingWasDone = false; 22837748cd8SNickeau 22937748cd8SNickeau /** 2304cadd4f8SNickeau * @var array - output attribute are not the parsed attributes known as componentAttribute) 2314cadd4f8SNickeau * They are created by the {@link TagAttributes::toHtmlArray()} processing mainly 23237748cd8SNickeau */ 2334cadd4f8SNickeau private $outputAttributes = array(); 23437748cd8SNickeau 23537748cd8SNickeau /** 23637748cd8SNickeau * @var array - the final html array 23737748cd8SNickeau */ 23837748cd8SNickeau private $finalHtmlArray = array(); 23937748cd8SNickeau 24037748cd8SNickeau /** 24137748cd8SNickeau * @var string the functional tag to which the attributes applies 24237748cd8SNickeau * It's not an HTML tag (a div can have a flex display or a block and they don't carry this information) 24337748cd8SNickeau * The tag gives also context for the attributes (ie an div has no natural width while an img has) 24437748cd8SNickeau */ 24537748cd8SNickeau private $logicalTag; 24637748cd8SNickeau 24737748cd8SNickeau /** 24837748cd8SNickeau * An html that should be added after the enter tag 24937748cd8SNickeau * (used for instance to add metadata such as backgrounds, illustration image for cards ,... 25037748cd8SNickeau * @var string 25137748cd8SNickeau */ 25237748cd8SNickeau private $htmlAfterEnterTag; 25337748cd8SNickeau 25437748cd8SNickeau /** 25537748cd8SNickeau * Use to make the difference between 25637748cd8SNickeau * an HTTP call for a media (ie SVG) vs an HTTP call for a page (HTML) 25737748cd8SNickeau */ 25837748cd8SNickeau const TEXT_HTML_MIME = "text/html"; 25937748cd8SNickeau private $mime = TagAttributes::TEXT_HTML_MIME; 26037748cd8SNickeau 2614cadd4f8SNickeau /** 2624cadd4f8SNickeau * @var bool - adding the default class for the logical tag 2634cadd4f8SNickeau */ 2644cadd4f8SNickeau private $defaultStyleClassShouldBeAdded = true; 26504fd306cSNickeau private $knownTypes; 26604fd306cSNickeau 26704fd306cSNickeau /** 26804fd306cSNickeau * @var string - the inner Text (used for script or style tag mostly) 26904fd306cSNickeau */ 27004fd306cSNickeau private string $innerText; 2714cadd4f8SNickeau 27237748cd8SNickeau 27337748cd8SNickeau /** 27437748cd8SNickeau * ComponentAttributes constructor. 27537748cd8SNickeau * Use the static create function to instantiate this object 27637748cd8SNickeau * @param $tag - tag (the tag gives context for the attributes 27737748cd8SNickeau * * an div has no natural width while an img has 27837748cd8SNickeau * * this is not always the component name / syntax name (for instance the {@link \syntax_plugin_combo_codemarkdown} is another syntax 27937748cd8SNickeau * for a {@link \syntax_plugin_combo_code} and have therefore the same logical name) 28037748cd8SNickeau * @param array $componentAttributes 28137748cd8SNickeau */ 2824cadd4f8SNickeau private function __construct(array $componentAttributes = array(), $tag = null) 28337748cd8SNickeau { 28437748cd8SNickeau $this->logicalTag = $tag; 28537748cd8SNickeau $this->componentAttributesCaseInsensitive = new ArrayCaseInsensitive($componentAttributes); 28637748cd8SNickeau 28737748cd8SNickeau /** 28837748cd8SNickeau * Delete null values 28937748cd8SNickeau * Empty string, 0 may exist 29037748cd8SNickeau */ 29137748cd8SNickeau foreach ($componentAttributes as $key => $value) { 29237748cd8SNickeau if (is_null($value)) { 29337748cd8SNickeau unset($this->componentAttributesCaseInsensitive[$key]); 2944cadd4f8SNickeau continue; 2954cadd4f8SNickeau } 29604fd306cSNickeau if ($key === StyleAttribute::STYLE_ATTRIBUTE) { 2974cadd4f8SNickeau unset($this->componentAttributesCaseInsensitive[$key]); 29804fd306cSNickeau LogUtility::warning("The style attribute cannot be set or used due to security. Uses the combostrap style attribute or set a class attibute instead."); 29937748cd8SNickeau } 30037748cd8SNickeau } 30137748cd8SNickeau 30237748cd8SNickeau } 30337748cd8SNickeau 30437748cd8SNickeau /** 30537748cd8SNickeau * @param $match - the {@link SyntaxPlugin::handle()} match 30604fd306cSNickeau * @param array $defaultAttributes - the default attributes values 30704fd306cSNickeau * @param array $knownTypes - the known types 30804fd306cSNickeau * @param bool $allowFirstBooleanAttributesAsType - if the first attribute is a boolean, make it a type 30937748cd8SNickeau * @return TagAttributes 31037748cd8SNickeau */ 31104fd306cSNickeau public static function createFromTagMatch($match, array $defaultAttributes = [], array $knownTypes = [], bool $allowFirstBooleanAttributesAsType = false): TagAttributes 31237748cd8SNickeau { 31304fd306cSNickeau $inlineHtmlAttributes = PluginUtility::getTagAttributes($match, $knownTypes, $allowFirstBooleanAttributesAsType); 31404fd306cSNickeau $tag = PluginUtility::getMarkupTag($match); 31537748cd8SNickeau $mergedAttributes = PluginUtility::mergeAttributes($inlineHtmlAttributes, $defaultAttributes); 31604fd306cSNickeau return (new TagAttributes($mergedAttributes, $tag)) 31704fd306cSNickeau ->setKnownTypes($knownTypes); 31837748cd8SNickeau } 31937748cd8SNickeau 32037748cd8SNickeau 32104fd306cSNickeau public static function createEmpty($logicalTag = ""): TagAttributes 32237748cd8SNickeau { 32337748cd8SNickeau if ($logicalTag !== "") { 32437748cd8SNickeau return new TagAttributes([], $logicalTag); 32537748cd8SNickeau } else { 32637748cd8SNickeau return new TagAttributes(); 32737748cd8SNickeau } 32837748cd8SNickeau } 32937748cd8SNickeau 33037748cd8SNickeau /** 3314cadd4f8SNickeau * @param array|null $callStackArray - an array of key value pair 3324cadd4f8SNickeau * @param string|null $logicalTag - the logical tag for which this attribute will apply 33337748cd8SNickeau * @return TagAttributes 33437748cd8SNickeau */ 3354cadd4f8SNickeau public static function createFromCallStackArray(?array $callStackArray, string $logicalTag = null): TagAttributes 33637748cd8SNickeau { 3374cadd4f8SNickeau if ($callStackArray === null) { 3384cadd4f8SNickeau $callStackArray = []; 33937748cd8SNickeau } 3404cadd4f8SNickeau if (!is_array($callStackArray)) { 3414cadd4f8SNickeau LogUtility::msg("The renderArray variable passed is not an array ($callStackArray)"); 3424cadd4f8SNickeau $callStackArray = []; 3434cadd4f8SNickeau } 34404fd306cSNickeau /** 34504fd306cSNickeau * Style is not allowed in a TagAttributes 34604fd306cSNickeau * 34704fd306cSNickeau * Because callstack is safe, 34804fd306cSNickeau * style have been added by plugin 34904fd306cSNickeau * For instance, the card had a `max-width style of 100%` to the image 35004fd306cSNickeau * 35104fd306cSNickeau * We capture it and add them afterwards 35204fd306cSNickeau */ 35304fd306cSNickeau if (isset($callStackArray[StyleAttribute::STYLE_ATTRIBUTE])) { 35404fd306cSNickeau $style = $callStackArray[StyleAttribute::STYLE_ATTRIBUTE]; 35504fd306cSNickeau unset($callStackArray[StyleAttribute::STYLE_ATTRIBUTE]); 35604fd306cSNickeau } 35704fd306cSNickeau 35804fd306cSNickeau $tagAttributes = new TagAttributes($callStackArray, $logicalTag); 35904fd306cSNickeau 36004fd306cSNickeau /** 36104fd306cSNickeau * Add the styles 36204fd306cSNickeau */ 36304fd306cSNickeau if (isset($style)) { 36404fd306cSNickeau $stylingProperties = StyleAttribute::HtmlStyleValueToArray($style); 36504fd306cSNickeau foreach ($stylingProperties as $styleKey => $styleValue) { 36604fd306cSNickeau $tagAttributes->addStyleDeclarationIfNotSet($styleKey, $styleValue); 36704fd306cSNickeau } 36804fd306cSNickeau } 36904fd306cSNickeau 37004fd306cSNickeau return $tagAttributes; 37137748cd8SNickeau } 37237748cd8SNickeau 37337748cd8SNickeau 37437748cd8SNickeau /** 37537748cd8SNickeau * For CSS a unit is mandatory (not for HTML or SVG attributes) 37637748cd8SNickeau * @param $value 37737748cd8SNickeau * @return string return a CSS property with pixel as unit if the unit is not specified 37804fd306cSNickeau * @throws ExceptionBadArgument 37937748cd8SNickeau */ 3804cadd4f8SNickeau public static function toQualifiedCssValue($value): string 38137748cd8SNickeau { 38204fd306cSNickeau return ConditionalLength::createFromString($value)->toCssLength(); 38337748cd8SNickeau 38437748cd8SNickeau } 38537748cd8SNickeau 38637748cd8SNickeau /** 38737748cd8SNickeau * Function used to normalize the attribute name to the combostrap attribute name 38837748cd8SNickeau * @param $name 38937748cd8SNickeau * @return mixed|string 39037748cd8SNickeau */ 39137748cd8SNickeau public static function AttributeNameFromDokuwikiToCombo($name) 39237748cd8SNickeau { 39337748cd8SNickeau switch ($name) { 39437748cd8SNickeau case "w": 39537748cd8SNickeau return Dimension::WIDTH_KEY; 39637748cd8SNickeau case "h": 39737748cd8SNickeau return Dimension::HEIGHT_KEY; 39837748cd8SNickeau default: 39937748cd8SNickeau return $name; 40037748cd8SNickeau } 40137748cd8SNickeau } 40237748cd8SNickeau 403c3437056SNickeau /** 404c3437056SNickeau * Clone a tag attributes 405c3437056SNickeau * Tag Attributes are used for request and for response 406c3437056SNickeau * To avoid conflict, a function should clone it before 407c3437056SNickeau * calling the final method {@link TagAttributes::toHtmlArray()} 408c3437056SNickeau * or {@link TagAttributes::toHtmlEnterTag()} 409c3437056SNickeau * @param TagAttributes $tagAttributes 410c3437056SNickeau * @return TagAttributes 411c3437056SNickeau */ 41204fd306cSNickeau public static function createFromTagAttributeString(TagAttributes $tagAttributes): TagAttributes 413c3437056SNickeau { 41482a60d03SNickeau $newTagAttributes = new TagAttributes($tagAttributes->getComponentAttributes(), $tagAttributes->getLogicalTag()); 41582a60d03SNickeau foreach ($tagAttributes->getStyleDeclarations() as $property => $value) { 41682a60d03SNickeau $newTagAttributes->addStyleDeclarationIfNotSet($property, $value); 41782a60d03SNickeau } 41882a60d03SNickeau return $newTagAttributes; 419c3437056SNickeau } 420c3437056SNickeau 42104fd306cSNickeau public static function isEmptyValue($attributeValue): bool 42282a60d03SNickeau { 42304fd306cSNickeau return empty($attributeValue) && !is_bool($attributeValue); 42482a60d03SNickeau } 42582a60d03SNickeau 42682a60d03SNickeau public function addClassName($className): TagAttributes 42737748cd8SNickeau { 42837748cd8SNickeau 42937748cd8SNickeau $this->addComponentAttributeValue(self::CLASS_KEY, $className); 43037748cd8SNickeau return $this; 43137748cd8SNickeau 43237748cd8SNickeau } 43337748cd8SNickeau 43404fd306cSNickeau /** 43504fd306cSNickeau * @throws ExceptionNull 43604fd306cSNickeau */ 43704fd306cSNickeau public function getClass($default = null) 43837748cd8SNickeau { 43904fd306cSNickeau $value = $this->getValue(self::CLASS_KEY, $default); 44004fd306cSNickeau if ($value !== null) { 44104fd306cSNickeau return $value; 44204fd306cSNickeau } 44304fd306cSNickeau throw new ExceptionNull("No class was found"); 44437748cd8SNickeau } 44537748cd8SNickeau 44637748cd8SNickeau /** 44704fd306cSNickeau * @return string 44804fd306cSNickeau * @throws ExceptionNotFound 44937748cd8SNickeau */ 45004fd306cSNickeau public function getStyle(): string 45104fd306cSNickeau { 45204fd306cSNickeau 45304fd306cSNickeau if (sizeof($this->styleDeclaration) === 0) { 45404fd306cSNickeau throw new ExceptionNotFound("No style"); 45537748cd8SNickeau } 45604fd306cSNickeau return Html::array2InlineStyle($this->styleDeclaration); 45737748cd8SNickeau 45837748cd8SNickeau } 45937748cd8SNickeau 46082a60d03SNickeau public function getStyleDeclarations(): array 46182a60d03SNickeau { 46282a60d03SNickeau return $this->styleDeclaration; 46382a60d03SNickeau 46482a60d03SNickeau } 46582a60d03SNickeau 46637748cd8SNickeau /** 46737748cd8SNickeau * Add an attribute with its value if the value is not empty 46837748cd8SNickeau * @param $attributeName 46937748cd8SNickeau * @param $attributeValue 4704cadd4f8SNickeau * @return TagAttributes 47137748cd8SNickeau */ 4724cadd4f8SNickeau public function addComponentAttributeValue($attributeName, $attributeValue): TagAttributes 47337748cd8SNickeau { 47437748cd8SNickeau 47504fd306cSNickeau if (TagAttributes::isEmptyValue($attributeValue)) { 4764cadd4f8SNickeau LogUtility::msg("The value of the attribute ($attributeName) is empty. Use the nonEmpty function instead if it's the wanted behavior", LogUtility::LVL_MSG_WARNING, "support"); 47737748cd8SNickeau } 47837748cd8SNickeau 47937748cd8SNickeau $attLower = strtolower($attributeName); 48082a60d03SNickeau $actual = null; 48137748cd8SNickeau if ($this->hasComponentAttribute($attLower)) { 48237748cd8SNickeau $actual = $this->componentAttributesCaseInsensitive[$attLower]; 48337748cd8SNickeau } 48437748cd8SNickeau 48537748cd8SNickeau /** 48637748cd8SNickeau * Type of data: list (class) or atomic (id) 48737748cd8SNickeau */ 48882a60d03SNickeau if (in_array($attributeName, self::MULTIPLE_VALUES_ATTRIBUTES)) { 48904fd306cSNickeau $this->componentAttributesCaseInsensitive[$attLower] = Html::mergeClassNames($attributeValue, $actual); 49037748cd8SNickeau } else { 49137748cd8SNickeau if (!empty($actual)) { 49237748cd8SNickeau LogUtility::msg("The attribute ($attLower) stores an unique value and has already a value ($actual). to set another value ($attributeValue), use the `set` operation instead", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 49337748cd8SNickeau } 49437748cd8SNickeau $this->componentAttributesCaseInsensitive[$attLower] = $attributeValue; 49537748cd8SNickeau } 49637748cd8SNickeau 49782a60d03SNickeau return $this; 49837748cd8SNickeau 49937748cd8SNickeau } 50037748cd8SNickeau 50104fd306cSNickeau public function setComponentAttributeValue($attributeName, $attributeValue): TagAttributes 50237748cd8SNickeau { 50337748cd8SNickeau $attLower = strtolower($attributeName); 50437748cd8SNickeau $actualValue = $this->getValue($attributeName); 50537748cd8SNickeau if ($actualValue === null || $actualValue !== TagAttributes::UN_SET) { 50637748cd8SNickeau $this->componentAttributesCaseInsensitive[$attLower] = $attributeValue; 50737748cd8SNickeau } 50804fd306cSNickeau return $this; 50937748cd8SNickeau } 51037748cd8SNickeau 51137748cd8SNickeau public function addComponentAttributeValueIfNotEmpty($attributeName, $attributeValue) 51237748cd8SNickeau { 51337748cd8SNickeau if (!empty($attributeValue)) { 51437748cd8SNickeau $this->addComponentAttributeValue($attributeName, $attributeValue); 51537748cd8SNickeau } 51637748cd8SNickeau } 51737748cd8SNickeau 51804fd306cSNickeau public function hasComponentAttribute($attributeName): bool 51937748cd8SNickeau { 52037748cd8SNickeau $isset = isset($this->componentAttributesCaseInsensitive[$attributeName]); 52104fd306cSNickeau if ($isset === false && $this->knownTypes === null) { 52237748cd8SNickeau /** 52337748cd8SNickeau * Edge effect 52437748cd8SNickeau * if this is a boolean value and the first value, it may be stored in the type 52537748cd8SNickeau */ 52637748cd8SNickeau if (isset($this->componentAttributesCaseInsensitive[TagAttributes::TYPE_KEY])) { 52737748cd8SNickeau if ($attributeName == $this->componentAttributesCaseInsensitive[TagAttributes::TYPE_KEY]) { 52804fd306cSNickeau LogUtility::warning("Internal Error: The tag ({$this->getLogicalTag()}) has a boolean attribute ($attributeName) defined as a type. The possible types should be defined for this tag as it's deprecated."); 52937748cd8SNickeau return true; 53037748cd8SNickeau } 53137748cd8SNickeau } 53237748cd8SNickeau } 53337748cd8SNickeau return $isset; 53437748cd8SNickeau } 53537748cd8SNickeau 53637748cd8SNickeau /** 53737748cd8SNickeau * To an HTML array in the form 53837748cd8SNickeau * class => 'value1 value2', 53937748cd8SNickeau * att => 'value1 value 2' 54037748cd8SNickeau * For historic reason, data passed between the handle and the render 54137748cd8SNickeau * can still be in this format 54237748cd8SNickeau */ 543c3437056SNickeau public function toHtmlArray(): array 54437748cd8SNickeau { 545c3437056SNickeau if ($this->componentToHtmlAttributeProcessingWasDone) { 546c3437056SNickeau LogUtility::msg("This tag attribute ($this) was already finalized. You cannot finalized it twice", LogUtility::LVL_MSG_ERROR); 547c3437056SNickeau return $this->finalHtmlArray; 548c3437056SNickeau } 54937748cd8SNickeau 55037748cd8SNickeau $this->componentToHtmlAttributeProcessingWasDone = true; 55137748cd8SNickeau 55237748cd8SNickeau /** 55337748cd8SNickeau * Width and height 55437748cd8SNickeau */ 55537748cd8SNickeau Dimension::processWidthAndHeight($this); 55637748cd8SNickeau 55737748cd8SNickeau /** 55837748cd8SNickeau * Process animation (onHover, onView) 55937748cd8SNickeau */ 56037748cd8SNickeau Hover::processOnHover($this); 56137748cd8SNickeau Animation::processOnView($this); 56237748cd8SNickeau 56337748cd8SNickeau 56437748cd8SNickeau /** 56537748cd8SNickeau * Position and Stickiness 56637748cd8SNickeau */ 56737748cd8SNickeau Position::processStickiness($this); 56837748cd8SNickeau Position::processPosition($this); 5694cadd4f8SNickeau Display::processDisplay($this); 57004fd306cSNickeau Vertical::processVertical($this); 57104fd306cSNickeau Horizontal::processHorizontal($this); 57237748cd8SNickeau 57337748cd8SNickeau /** 57437748cd8SNickeau * Block processing 57537748cd8SNickeau * 57637748cd8SNickeau * Float, align, spacing 57737748cd8SNickeau */ 57837748cd8SNickeau FloatAttribute::processFloat($this); 57937748cd8SNickeau Align::processAlignAttributes($this); 58037748cd8SNickeau Spacing::processSpacingAttributes($this); 58104fd306cSNickeau Hero::processHero($this); 58237748cd8SNickeau Opacity::processOpacityAttribute($this); 58304fd306cSNickeau BackgroundAttribute::processBackgroundAttributes($this); 58437748cd8SNickeau Shadow::process($this); 58537748cd8SNickeau 58637748cd8SNickeau /** 58737748cd8SNickeau * Process text attributes 58837748cd8SNickeau */ 58937748cd8SNickeau LineSpacing::processLineSpacingAttributes($this); 59037748cd8SNickeau TextAlign::processTextAlign($this); 59137748cd8SNickeau Boldness::processBoldnessAttribute($this); 59237748cd8SNickeau FontSize::processFontSizeAttribute($this); 59337748cd8SNickeau TextColor::processTextColorAttribute($this); 59437748cd8SNickeau Underline::processUnderlineAttribute($this); 59537748cd8SNickeau 59637748cd8SNickeau /** 59737748cd8SNickeau * Process the style attributes if any 59837748cd8SNickeau */ 59937748cd8SNickeau PluginUtility::processStyle($this); 60037748cd8SNickeau Toggle::processToggle($this); 60137748cd8SNickeau 60237748cd8SNickeau 60337748cd8SNickeau /** 60437748cd8SNickeau * Skin Attribute 60537748cd8SNickeau */ 60637748cd8SNickeau Skin::processSkinAttribute($this); 60737748cd8SNickeau 60837748cd8SNickeau /** 60937748cd8SNickeau * Lang 61037748cd8SNickeau */ 61137748cd8SNickeau Lang::processLangAttribute($this); 61237748cd8SNickeau 61337748cd8SNickeau /** 61437748cd8SNickeau * Transform 61537748cd8SNickeau */ 61637748cd8SNickeau if ($this->hasComponentAttribute(self::TRANSFORM)) { 61737748cd8SNickeau $transformValue = $this->getValueAndRemove(self::TRANSFORM); 61882a60d03SNickeau $this->addStyleDeclarationIfNotSet("transform", $transformValue); 61937748cd8SNickeau } 62037748cd8SNickeau 62104fd306cSNickeau 62237748cd8SNickeau /** 6234cadd4f8SNickeau * Tooltip 6244cadd4f8SNickeau */ 6254cadd4f8SNickeau Tooltip::processTooltip($this); 6264cadd4f8SNickeau 6274cadd4f8SNickeau /** 62837748cd8SNickeau * Add the type class used for CSS styling 62937748cd8SNickeau */ 63004fd306cSNickeau StyleAttribute::addStylingClass($this); 63137748cd8SNickeau 63237748cd8SNickeau /** 63337748cd8SNickeau * Add the style has html attribute 63437748cd8SNickeau * before processing 63537748cd8SNickeau */ 63604fd306cSNickeau try { 6374cadd4f8SNickeau $this->addOutputAttributeValueIfNotEmpty("style", $this->getStyle()); 63804fd306cSNickeau } catch (ExceptionNotFound $e) { 63904fd306cSNickeau // no style 64004fd306cSNickeau } 64137748cd8SNickeau 64237748cd8SNickeau /** 64337748cd8SNickeau * Create a non-sorted temporary html attributes array 64437748cd8SNickeau */ 6454cadd4f8SNickeau $tempHtmlArray = $this->outputAttributes; 64637748cd8SNickeau 64737748cd8SNickeau /** 64837748cd8SNickeau * copy the unknown component attributes 64937748cd8SNickeau */ 65037748cd8SNickeau $originalArray = $this->componentAttributesCaseInsensitive->getOriginalArray(); 65137748cd8SNickeau foreach ($originalArray as $key => $value) { 65237748cd8SNickeau 65337748cd8SNickeau // Null Value, not needed 65437748cd8SNickeau if (is_null($value)) { 65537748cd8SNickeau continue; 65637748cd8SNickeau } 65737748cd8SNickeau 65837748cd8SNickeau // No overwrite 65937748cd8SNickeau if (isset($tempHtmlArray[$key])) { 66037748cd8SNickeau continue; 66137748cd8SNickeau } 66237748cd8SNickeau 66304fd306cSNickeau // We only add the common HTML attribute 66404fd306cSNickeau if (in_array($key, self::HTML_ATTRIBUTES) || strpos($key, 'data-') === 0) { 66537748cd8SNickeau $tempHtmlArray[$key] = $value; 66604fd306cSNickeau } else { 66704fd306cSNickeau 66804fd306cSNickeau if (!in_array($key, [ 66904fd306cSNickeau TagAttributes::TYPE_KEY, 67004fd306cSNickeau TagAttributes::GENERATED_ID_KEY, 67104fd306cSNickeau TagAttributes::OPEN_TAG 67204fd306cSNickeau ])) { 67304fd306cSNickeau 67404fd306cSNickeau /** 67504fd306cSNickeau * Note for developers: 67604fd306cSNickeau * * If it must be in the HTML output, you should add it via the output attribute methods during processing. 67704fd306cSNickeau * * Otherwise you need for now to get and delete it 67804fd306cSNickeau */ 67904fd306cSNickeau $message = "The component attribute ($key) is unknown or does not apply "; 68004fd306cSNickeau if (isset($this->logicalTag)) { 68104fd306cSNickeau $message = "$message for the component ({$this->logicalTag})."; 68204fd306cSNickeau } 68304fd306cSNickeau LogUtility::warning($message); 68404fd306cSNickeau 68504fd306cSNickeau } 68637748cd8SNickeau } 68737748cd8SNickeau 68837748cd8SNickeau } 68937748cd8SNickeau 69037748cd8SNickeau 69137748cd8SNickeau /** 69237748cd8SNickeau * Sort by attribute 69337748cd8SNickeau * https://datacadamia.com/web/html/attribute#order 69404fd306cSNickeau * https://codeguide.co/#html-attribute-order 69537748cd8SNickeau */ 69637748cd8SNickeau $sortedArray = array(); 69737748cd8SNickeau $once = "once"; 69837748cd8SNickeau $multiple = "multiple"; 69937748cd8SNickeau $orderPatterns = [ 70037748cd8SNickeau "class" => $once, 70137748cd8SNickeau "id" => $once, 70237748cd8SNickeau "name" => $once, 70337748cd8SNickeau "data-.*" => $multiple, 70437748cd8SNickeau "src.*" => $multiple, 70537748cd8SNickeau "for" => $once, 70637748cd8SNickeau "type" => $once, 70737748cd8SNickeau "href" => $once, 70837748cd8SNickeau "value" => $once, 70937748cd8SNickeau "title" => $once, 71037748cd8SNickeau "alt" => $once, 71137748cd8SNickeau "role" => $once, 71237748cd8SNickeau "aria-*" => $multiple]; 71337748cd8SNickeau foreach ($orderPatterns as $pattern => $type) { 71437748cd8SNickeau foreach ($tempHtmlArray as $name => $value) { 71537748cd8SNickeau $searchPattern = "^$pattern$"; 71637748cd8SNickeau if (preg_match("/$searchPattern/", $name)) { 71737748cd8SNickeau unset($tempHtmlArray[$name]); 7184cadd4f8SNickeau if ($type === $once) { 7194cadd4f8SNickeau $sortedArray[$name] = $value; 7204cadd4f8SNickeau continue 2; 7214cadd4f8SNickeau } else { 7224cadd4f8SNickeau $multipleValues[$name] = $value; 72337748cd8SNickeau } 72437748cd8SNickeau } 72537748cd8SNickeau } 7264cadd4f8SNickeau if (!empty($multipleValues)) { 7274cadd4f8SNickeau ksort($multipleValues); 7284cadd4f8SNickeau $sortedArray = array_merge($sortedArray, $multipleValues); 7294cadd4f8SNickeau $multipleValues = []; 7304cadd4f8SNickeau } 73137748cd8SNickeau } 73237748cd8SNickeau foreach ($tempHtmlArray as $name => $value) { 73337748cd8SNickeau 73437748cd8SNickeau if (!is_null($value)) { 73537748cd8SNickeau /** 73637748cd8SNickeau * 73737748cd8SNickeau * Don't add a filter on the empty values 73837748cd8SNickeau * 73937748cd8SNickeau * The value of an HTML attribute may be empty 74037748cd8SNickeau * Example the wiki id of the root namespace 74137748cd8SNickeau * 7424cadd4f8SNickeau * By default, {@link TagAttributes::addOutputAttributeValue()} 74337748cd8SNickeau * will not accept any value, it must be implicitly said with the 7444cadd4f8SNickeau * {@link TagAttributes::addOutputAttributeValue()} 74537748cd8SNickeau * 74637748cd8SNickeau */ 74737748cd8SNickeau $sortedArray[$name] = $value; 74837748cd8SNickeau } 74937748cd8SNickeau 75037748cd8SNickeau } 75137748cd8SNickeau $this->finalHtmlArray = $sortedArray; 75237748cd8SNickeau 7534cadd4f8SNickeau /** 7544cadd4f8SNickeau * To Html attribute encoding 7554cadd4f8SNickeau */ 7564cadd4f8SNickeau $this->finalHtmlArray = $this->encodeToHtmlValue($this->finalHtmlArray); 7574cadd4f8SNickeau 75837748cd8SNickeau return $this->finalHtmlArray; 75937748cd8SNickeau 76037748cd8SNickeau } 76137748cd8SNickeau 76237748cd8SNickeau /** 7634cadd4f8SNickeau * 7644cadd4f8SNickeau * 76537748cd8SNickeau * @param $key 76637748cd8SNickeau * @param $value 76737748cd8SNickeau * @return TagAttributes 76837748cd8SNickeau */ 7694cadd4f8SNickeau public function addOutputAttributeValue($key, $value): TagAttributes 77037748cd8SNickeau { 77104fd306cSNickeau 77237748cd8SNickeau if (blank($value)) { 77304fd306cSNickeau LogUtility::error("The value of the output attribute is blank for the key ($key) - Tag ($this->logicalTag). Use the empty / boolean function if the value can be empty"); 77437748cd8SNickeau } 77504fd306cSNickeau 776*70bbd7f1Sgerardnico $actualValue = $this->outputAttributes[$key] ?? null; 77704fd306cSNickeau if ($actualValue === null) { 7784cadd4f8SNickeau $this->outputAttributes[$key] = $value; 77937748cd8SNickeau return $this; 78037748cd8SNickeau } 78137748cd8SNickeau 78204fd306cSNickeau if (!in_array($key, self::MULTIPLE_VALUES_ATTRIBUTES)) { 78304fd306cSNickeau LogUtility::internalError("The output attribute ($key) was already set with the value ($actualValue), we have added the value ($value)"); 78404fd306cSNickeau } 78504fd306cSNickeau 78604fd306cSNickeau $this->outputAttributes[$key] = "$value $actualValue"; 78704fd306cSNickeau return $this; 78804fd306cSNickeau 78904fd306cSNickeau } 79004fd306cSNickeau 79137748cd8SNickeau 7924cadd4f8SNickeau public function addOutputAttributeValueIfNotEmpty($key, $value) 79337748cd8SNickeau { 79437748cd8SNickeau if (!empty($value)) { 7954cadd4f8SNickeau $this->addOutputAttributeValue($key, $value); 79637748cd8SNickeau } 79737748cd8SNickeau } 79837748cd8SNickeau 79937748cd8SNickeau /** 80037748cd8SNickeau * @param $attributeName 80137748cd8SNickeau * @param null $default 80237748cd8SNickeau * @return string|array|null a HTML value in the form 'value1 value2...' 80337748cd8SNickeau */ 80437748cd8SNickeau public function getValue($attributeName, $default = null) 80537748cd8SNickeau { 80637748cd8SNickeau $attributeName = strtolower($attributeName); 80737748cd8SNickeau if ($this->hasComponentAttribute($attributeName)) { 80837748cd8SNickeau return $this->componentAttributesCaseInsensitive[$attributeName]; 80937748cd8SNickeau } else { 81037748cd8SNickeau return $default; 81137748cd8SNickeau } 81237748cd8SNickeau } 81337748cd8SNickeau 81437748cd8SNickeau 81537748cd8SNickeau /** 81637748cd8SNickeau * Get the value and remove it from the attributes 81737748cd8SNickeau * @param $attributeName 81837748cd8SNickeau * @param $default 81937748cd8SNickeau * @return string|array|null 82004fd306cSNickeau * 82104fd306cSNickeau * TODO: we should create a new response object and not deleting data from the request 82237748cd8SNickeau */ 82337748cd8SNickeau public function getValueAndRemove($attributeName, $default = null) 82437748cd8SNickeau { 82537748cd8SNickeau $attributeName = strtolower($attributeName); 82637748cd8SNickeau $value = $default; 82737748cd8SNickeau if ($this->hasComponentAttribute($attributeName)) { 82837748cd8SNickeau $value = $this->getValue($attributeName); 82937748cd8SNickeau 83004fd306cSNickeau if (!in_array($attributeName, self::PROTECTED_ATTRIBUTES)) { 83137748cd8SNickeau /** 83237748cd8SNickeau * Don't remove for instance the `type` 83337748cd8SNickeau * because it may be used elsewhere 83437748cd8SNickeau */ 83537748cd8SNickeau unset($this->componentAttributesCaseInsensitive[$attributeName]); 83637748cd8SNickeau } 83737748cd8SNickeau 83837748cd8SNickeau } 83937748cd8SNickeau return $value; 84037748cd8SNickeau } 84137748cd8SNickeau 84237748cd8SNickeau 84337748cd8SNickeau /** 84437748cd8SNickeau * @return array - an array of key string and value of the component attributes 84537748cd8SNickeau * This array is saved on the disk 84637748cd8SNickeau */ 8474cadd4f8SNickeau public function toCallStackArray(): array 84837748cd8SNickeau { 84904fd306cSNickeau 85004fd306cSNickeau $generatedId = $this->getValue(TagAttributes::GENERATED_ID_KEY); 85104fd306cSNickeau if ($generatedId === null) { 85204fd306cSNickeau 85304fd306cSNickeau $componentName = $this->logicalTag; 85404fd306cSNickeau if ($componentName === null) { 85504fd306cSNickeau $componentName = "unknown-component"; 85604fd306cSNickeau } 85704fd306cSNickeau $id = ExecutionContext::getActualOrCreateFromEnv() 85804fd306cSNickeau ->getIdManager() 85904fd306cSNickeau ->generateNewHtmlIdForComponent($componentName); 86004fd306cSNickeau $this->addComponentAttributeValue(TagAttributes::GENERATED_ID_KEY, $id); 86104fd306cSNickeau 86204fd306cSNickeau } 86304fd306cSNickeau 86437748cd8SNickeau $array = array(); 86537748cd8SNickeau $originalArray = $this->componentAttributesCaseInsensitive->getOriginalArray(); 86637748cd8SNickeau foreach ($originalArray as $key => $value) { 86737748cd8SNickeau /** 86837748cd8SNickeau * Only null value are not passed 86937748cd8SNickeau * width can be zero, wiki-id can be the empty string (ie root namespace) 87004fd306cSNickeau * 87104fd306cSNickeau * Value can be array, number, string 87237748cd8SNickeau */ 87337748cd8SNickeau if (!is_null($value)) { 87404fd306cSNickeau $array[$key] = $value; 87537748cd8SNickeau } 87637748cd8SNickeau } 8774cadd4f8SNickeau /** 8784cadd4f8SNickeau * html attribute may also be in the callstack 8794cadd4f8SNickeau */ 8804cadd4f8SNickeau foreach ($this->outputAttributes as $key => $value) { 88104fd306cSNickeau $array[$key] = $value; 8824cadd4f8SNickeau } 88304fd306cSNickeau try { 88404fd306cSNickeau $array["style"] = $this->getStyle(); 88504fd306cSNickeau } catch (ExceptionNotFound $e) { 88604fd306cSNickeau // no style 88704fd306cSNickeau } 88804fd306cSNickeau 88904fd306cSNickeau if (isset($this->innerText)) { 89004fd306cSNickeau $array[self::DOKUWIKI_TEXT_NODE_ATTRIBUTE] = $this->innerText; 89137748cd8SNickeau } 89237748cd8SNickeau return $array; 89337748cd8SNickeau } 89437748cd8SNickeau 89537748cd8SNickeau public 89637748cd8SNickeau function getComponentAttributeValue($attributeName, $default = null) 89737748cd8SNickeau { 89837748cd8SNickeau $lowerAttribute = strtolower($attributeName); 89937748cd8SNickeau $value = $default; 90037748cd8SNickeau if ($this->hasComponentAttribute($lowerAttribute)) { 90137748cd8SNickeau $value = $this->getValue($lowerAttribute); 90237748cd8SNickeau } 90337748cd8SNickeau return $value; 90437748cd8SNickeau } 90537748cd8SNickeau 90637748cd8SNickeau public 90782a60d03SNickeau function addStyleDeclarationIfNotSet($property, $value) 90837748cd8SNickeau { 90937748cd8SNickeau ArrayUtility::addIfNotSet($this->styleDeclaration, $property, $value); 91037748cd8SNickeau } 91137748cd8SNickeau 91204fd306cSNickeau public 91304fd306cSNickeau function setStyleDeclaration($property, $value): TagAttributes 91404fd306cSNickeau { 91504fd306cSNickeau $this->styleDeclaration[$property] = $value; 91604fd306cSNickeau return $this; 91704fd306cSNickeau } 91804fd306cSNickeau 91937748cd8SNickeau 92037748cd8SNickeau public 9214cadd4f8SNickeau function hasStyleDeclaration($styleDeclaration): bool 92237748cd8SNickeau { 92337748cd8SNickeau return isset($this->styleDeclaration[$styleDeclaration]); 92437748cd8SNickeau } 92537748cd8SNickeau 92604fd306cSNickeau public function getAndRemoveStyleDeclaration($styleDeclaration) 92737748cd8SNickeau { 92837748cd8SNickeau $styleValue = $this->styleDeclaration[$styleDeclaration]; 92937748cd8SNickeau unset($this->styleDeclaration[$styleDeclaration]); 93037748cd8SNickeau return $styleValue; 93137748cd8SNickeau } 93237748cd8SNickeau 93337748cd8SNickeau 93437748cd8SNickeau public 9354cadd4f8SNickeau function toHTMLAttributeString(): string 93637748cd8SNickeau { 93737748cd8SNickeau 93837748cd8SNickeau $tagAttributeString = ""; 93937748cd8SNickeau 94037748cd8SNickeau $htmlArray = $this->toHtmlArray(); 94137748cd8SNickeau foreach ($htmlArray as $name => $value) { 94237748cd8SNickeau 94337748cd8SNickeau /** 94437748cd8SNickeau * Empty value are authorized 94537748cd8SNickeau * null are just not set 94637748cd8SNickeau */ 94737748cd8SNickeau if (!is_null($value)) { 94837748cd8SNickeau 94937748cd8SNickeau /** 95037748cd8SNickeau * Unset attribute should not be added 95137748cd8SNickeau */ 95237748cd8SNickeau if ($value === TagAttributes::UN_SET) { 95337748cd8SNickeau continue; 95437748cd8SNickeau } 95537748cd8SNickeau 95637748cd8SNickeau /** 95737748cd8SNickeau * The condition is important 95837748cd8SNickeau * because we may pass the javascript character `\n` in a `srcdoc` for javascript 95937748cd8SNickeau * and the {@link StringUtility::toString()} will transform it as `\\n` 96037748cd8SNickeau * making it unusable 96137748cd8SNickeau */ 96237748cd8SNickeau if (!is_string($value)) { 96337748cd8SNickeau $stringValue = StringUtility::toString($value); 96437748cd8SNickeau } else { 96537748cd8SNickeau $stringValue = $value; 96637748cd8SNickeau } 96737748cd8SNickeau 96837748cd8SNickeau 96937748cd8SNickeau $tagAttributeString .= $name . '="' . $stringValue . '" '; 97037748cd8SNickeau } 97137748cd8SNickeau 97237748cd8SNickeau } 97337748cd8SNickeau return trim($tagAttributeString); 97437748cd8SNickeau 97537748cd8SNickeau 97637748cd8SNickeau } 97737748cd8SNickeau 97837748cd8SNickeau public 9794cadd4f8SNickeau function getComponentAttributes(): array 98037748cd8SNickeau { 98137748cd8SNickeau return $this->toCallStackArray(); 98237748cd8SNickeau } 98337748cd8SNickeau 98437748cd8SNickeau public 98537748cd8SNickeau function removeComponentAttributeIfPresent($attributeName) 98637748cd8SNickeau { 98737748cd8SNickeau if ($this->hasComponentAttribute($attributeName)) { 98837748cd8SNickeau unset($this->componentAttributesCaseInsensitive[$attributeName]); 98937748cd8SNickeau } 99037748cd8SNickeau 99137748cd8SNickeau } 99237748cd8SNickeau 99337748cd8SNickeau public 9944cadd4f8SNickeau function toHtmlEnterTag($htmlTag): string 99537748cd8SNickeau { 99637748cd8SNickeau 99704fd306cSNickeau $enterTag = "<" . trim($htmlTag); 99837748cd8SNickeau $attributeString = $this->toHTMLAttributeString(); 99937748cd8SNickeau if (!empty($attributeString)) { 100037748cd8SNickeau $enterTag .= " " . $attributeString; 100137748cd8SNickeau } 100237748cd8SNickeau /** 100337748cd8SNickeau * Is it an open tag ? 100437748cd8SNickeau */ 100537748cd8SNickeau if (!$this->getValue(self::OPEN_TAG, false)) { 100637748cd8SNickeau 100737748cd8SNickeau $enterTag .= ">"; 100837748cd8SNickeau 100937748cd8SNickeau /** 101037748cd8SNickeau * Do we have html after the tag is closed 101137748cd8SNickeau */ 101237748cd8SNickeau if (!empty($this->htmlAfterEnterTag)) { 101337748cd8SNickeau $enterTag .= DOKU_LF . $this->htmlAfterEnterTag; 101437748cd8SNickeau } 101537748cd8SNickeau 101637748cd8SNickeau } 101737748cd8SNickeau 101837748cd8SNickeau 101937748cd8SNickeau return $enterTag; 102037748cd8SNickeau 102137748cd8SNickeau } 102237748cd8SNickeau 102337748cd8SNickeau public 10244cadd4f8SNickeau function toHtmlEmptyTag($htmlTag): string 10254cadd4f8SNickeau { 10264cadd4f8SNickeau 10274cadd4f8SNickeau $enterTag = "<" . $htmlTag; 10284cadd4f8SNickeau $attributeString = $this->toHTMLAttributeString(); 10294cadd4f8SNickeau if (!empty($attributeString)) { 10304cadd4f8SNickeau $enterTag .= " " . $attributeString; 10314cadd4f8SNickeau } 10324cadd4f8SNickeau return $enterTag . "/>"; 10334cadd4f8SNickeau 10344cadd4f8SNickeau } 10354cadd4f8SNickeau 103604fd306cSNickeau public function getLogicalTag() 103737748cd8SNickeau { 103837748cd8SNickeau return $this->logicalTag; 103937748cd8SNickeau } 104037748cd8SNickeau 104137748cd8SNickeau public 10424cadd4f8SNickeau function setLogicalTag($tag): TagAttributes 104337748cd8SNickeau { 104437748cd8SNickeau $this->logicalTag = $tag; 10454cadd4f8SNickeau return $this; 104637748cd8SNickeau } 104737748cd8SNickeau 104804fd306cSNickeau /** 104904fd306cSNickeau * @param $attribute 105004fd306cSNickeau * @return mixed|null - the value deleted / null if it does not exist 105104fd306cSNickeau */ 105204fd306cSNickeau public function removeComponentAttribute($attribute) 105337748cd8SNickeau { 105437748cd8SNickeau $lowerAtt = strtolower($attribute); 105537748cd8SNickeau if (isset($this->componentAttributesCaseInsensitive[$lowerAtt])) { 105637748cd8SNickeau $value = $this->componentAttributesCaseInsensitive[$lowerAtt]; 105737748cd8SNickeau unset($this->componentAttributesCaseInsensitive[$lowerAtt]); 105837748cd8SNickeau return $value; 105937748cd8SNickeau } else { 106037748cd8SNickeau /** 106137748cd8SNickeau * Edge case, this is the first boolean attribute 106237748cd8SNickeau * and may has been categorized as the type 106337748cd8SNickeau */ 106437748cd8SNickeau if (!$this->getType() == $lowerAtt) { 106504fd306cSNickeau LogUtility::msg("Internal Error: The component attribute ($attribute) is not present. Use the ifPresent function, if you don't want this message"); 106637748cd8SNickeau } 106704fd306cSNickeau return null; 106804fd306cSNickeau 106937748cd8SNickeau 107037748cd8SNickeau } 107137748cd8SNickeau 107237748cd8SNickeau } 107337748cd8SNickeau 107437748cd8SNickeau /** 107537748cd8SNickeau * @param $html - an html that should be closed and added after the enter tag 107637748cd8SNickeau */ 107737748cd8SNickeau public 107837748cd8SNickeau function addHtmlAfterEnterTag($html) 107937748cd8SNickeau { 108037748cd8SNickeau $this->htmlAfterEnterTag = $html . $this->htmlAfterEnterTag; 108137748cd8SNickeau } 108237748cd8SNickeau 108337748cd8SNickeau /** 108437748cd8SNickeau * The mime of the HTTP request 108537748cd8SNickeau * This is not the good place but yeah, 108637748cd8SNickeau * this class has become the context class 108737748cd8SNickeau * 108837748cd8SNickeau * Mime make the difference for a svg to know if it's required as external resource (ie SVG) 108937748cd8SNickeau * or as included in HTML page 109037748cd8SNickeau * @param $mime 109137748cd8SNickeau */ 109237748cd8SNickeau public 109337748cd8SNickeau function setMime($mime) 109437748cd8SNickeau { 109537748cd8SNickeau $this->mime = $mime; 109637748cd8SNickeau } 109737748cd8SNickeau 109837748cd8SNickeau /** 109937748cd8SNickeau * @return string - the mime of the request 110037748cd8SNickeau */ 110137748cd8SNickeau public 110237748cd8SNickeau function getMime() 110337748cd8SNickeau { 110437748cd8SNickeau return $this->mime; 110537748cd8SNickeau } 110637748cd8SNickeau 110737748cd8SNickeau public 110837748cd8SNickeau function getType() 110937748cd8SNickeau { 111037748cd8SNickeau return $this->getValue(self::TYPE_KEY); 111137748cd8SNickeau } 111237748cd8SNickeau 111337748cd8SNickeau /** 111437748cd8SNickeau * @param $attributeName 111537748cd8SNickeau * @return ConditionalValue 111637748cd8SNickeau */ 111737748cd8SNickeau public 111837748cd8SNickeau function getConditionalValueAndRemove($attributeName) 111937748cd8SNickeau { 112037748cd8SNickeau $value = $this->getConditionalValueAndRemove($attributeName); 112137748cd8SNickeau return new ConditionalValue($value); 112237748cd8SNickeau 112337748cd8SNickeau } 112437748cd8SNickeau 112537748cd8SNickeau /** 112637748cd8SNickeau * @param $attributeName 112704fd306cSNickeau * @param null $default 112804fd306cSNickeau * @return null|string[] - an array of values 112904fd306cSNickeau * @throws ExceptionBadArgument 113037748cd8SNickeau */ 113137748cd8SNickeau public 113204fd306cSNickeau function getValuesAndRemove($attributeName, $default = null): array 113337748cd8SNickeau { 113437748cd8SNickeau 113504fd306cSNickeau $trim = $this->getValues($attributeName, $default); 113604fd306cSNickeau $this->removeAttributeIfPresent($attributeName); 113704fd306cSNickeau return $trim; 113837748cd8SNickeau 113937748cd8SNickeau 114037748cd8SNickeau } 114137748cd8SNickeau 114237748cd8SNickeau public 11434cadd4f8SNickeau function setType($type): TagAttributes 114437748cd8SNickeau { 114537748cd8SNickeau $this->setComponentAttributeValue(TagAttributes::TYPE_KEY, $type); 11464cadd4f8SNickeau return $this; 114737748cd8SNickeau } 114837748cd8SNickeau 114937748cd8SNickeau /** 115037748cd8SNickeau * Merging will add the values, no replace or overwrite 115137748cd8SNickeau * @param $callStackArray 115237748cd8SNickeau */ 115337748cd8SNickeau public 115437748cd8SNickeau function mergeWithCallStackArray($callStackArray) 115537748cd8SNickeau { 115637748cd8SNickeau foreach ($callStackArray as $key => $value) { 11574cadd4f8SNickeau 115837748cd8SNickeau if ($this->hasComponentAttribute($key)) { 11594cadd4f8SNickeau $isMultipleAttributeValue = in_array($key, self::MULTIPLE_VALUES_ATTRIBUTES); 11604cadd4f8SNickeau if ($isMultipleAttributeValue) { 116137748cd8SNickeau $this->addComponentAttributeValue($key, $value); 11624cadd4f8SNickeau } 116337748cd8SNickeau } else { 116437748cd8SNickeau $this->setComponentAttributeValue($key, $value); 116537748cd8SNickeau } 116637748cd8SNickeau } 116737748cd8SNickeau 116837748cd8SNickeau } 116937748cd8SNickeau 117037748cd8SNickeau /** 117137748cd8SNickeau * @param $string 11724cadd4f8SNickeau * @return TagAttributes 117337748cd8SNickeau */ 117437748cd8SNickeau public 11754cadd4f8SNickeau function removeAttributeIfPresent($string): TagAttributes 117637748cd8SNickeau { 117737748cd8SNickeau $this->removeComponentAttributeIfPresent($string); 117804fd306cSNickeau $this->removeOutputAttributeIfPresent($string); 11794cadd4f8SNickeau return $this; 118037748cd8SNickeau 118137748cd8SNickeau } 118237748cd8SNickeau 118304fd306cSNickeau public function removeOutputAttributeIfPresent($string) 118437748cd8SNickeau { 118537748cd8SNickeau $lowerAtt = strtolower($string); 11864cadd4f8SNickeau if (isset($this->outputAttributes[$lowerAtt])) { 11874cadd4f8SNickeau unset($this->outputAttributes[$lowerAtt]); 118837748cd8SNickeau } 118937748cd8SNickeau } 119037748cd8SNickeau 119137748cd8SNickeau public 119237748cd8SNickeau function getValueAndRemoveIfPresent($attribute, $default = null) 119337748cd8SNickeau { 119437748cd8SNickeau $value = $this->getValue($attribute, $default); 119537748cd8SNickeau $this->removeAttributeIfPresent($attribute); 119637748cd8SNickeau return $value; 119737748cd8SNickeau } 119837748cd8SNickeau 119937748cd8SNickeau public 120037748cd8SNickeau function generateAndSetId() 120137748cd8SNickeau { 120237748cd8SNickeau self::$counter += 1; 120337748cd8SNickeau $id = self::$counter; 120437748cd8SNickeau $logicalTag = $this->getLogicalTag(); 120537748cd8SNickeau if (!empty($logicalTag)) { 120637748cd8SNickeau $id = $this->logicalTag . $id; 120737748cd8SNickeau } 120837748cd8SNickeau $this->setComponentAttributeValue("id", $id); 120937748cd8SNickeau return $id; 121037748cd8SNickeau } 121137748cd8SNickeau 121237748cd8SNickeau /** 121337748cd8SNickeau * 121437748cd8SNickeau * @param $markiTag 121537748cd8SNickeau * @return string - the marki tag made of logical attribute 121637748cd8SNickeau * There is no processing to transform it to an HTML tag 121737748cd8SNickeau */ 121837748cd8SNickeau public 121937748cd8SNickeau function toMarkiEnterTag($markiTag) 122037748cd8SNickeau { 122137748cd8SNickeau $enterTag = "<" . $markiTag; 122237748cd8SNickeau 122337748cd8SNickeau $attributeString = ""; 122437748cd8SNickeau foreach ($this->getComponentAttributes() as $key => $value) { 122537748cd8SNickeau $attributeString .= "$key=\"$value\" "; 122637748cd8SNickeau } 122737748cd8SNickeau $attributeString = trim($attributeString); 122837748cd8SNickeau 122937748cd8SNickeau if (!empty($attributeString)) { 123037748cd8SNickeau $enterTag .= " " . $attributeString; 123137748cd8SNickeau } 123237748cd8SNickeau $enterTag .= ">"; 123337748cd8SNickeau return $enterTag; 123437748cd8SNickeau 123537748cd8SNickeau } 123637748cd8SNickeau 123737748cd8SNickeau /** 123837748cd8SNickeau * @param string $key add an html attribute with the empty string 123937748cd8SNickeau */ 124037748cd8SNickeau public 124104fd306cSNickeau function addBooleanOutputAttributeValue(string $key): TagAttributes 124237748cd8SNickeau { 124337748cd8SNickeau 124404fd306cSNickeau $this->outputAttributes[$key] = null; 124537748cd8SNickeau return $this; 124637748cd8SNickeau 124737748cd8SNickeau } 124837748cd8SNickeau 124937748cd8SNickeau public 125037748cd8SNickeau function addEmptyComponentAttributeValue($attribute) 125137748cd8SNickeau { 125237748cd8SNickeau $this->componentAttributesCaseInsensitive[$attribute] = ""; 125337748cd8SNickeau } 125437748cd8SNickeau 125537748cd8SNickeau /** 125637748cd8SNickeau * @param $attribute 125737748cd8SNickeau * @param null $default 125837748cd8SNickeau * @return mixed 125937748cd8SNickeau */ 126037748cd8SNickeau public 12614cadd4f8SNickeau function getBooleanValueAndRemoveIfPresent($attribute, $default = null) 126237748cd8SNickeau { 126304fd306cSNickeau $value = $this->getBooleanValue($attribute, $default); 126404fd306cSNickeau $this->removeAttributeIfPresent($attribute); 126504fd306cSNickeau return $value; 126637748cd8SNickeau } 126737748cd8SNickeau 126837748cd8SNickeau public 126904fd306cSNickeau function getBooleanValue($attribute, $default = null) 127004fd306cSNickeau { 127104fd306cSNickeau $value = $this->getValue($attribute); 127204fd306cSNickeau if ($value !== null) { 127304fd306cSNickeau return DataType::toBoolean($value); 127404fd306cSNickeau } 127504fd306cSNickeau return $default; 127604fd306cSNickeau } 127704fd306cSNickeau 127804fd306cSNickeau public function hasAttribute($attribute): bool 127937748cd8SNickeau { 128037748cd8SNickeau $hasAttribute = $this->hasComponentAttribute($attribute); 128137748cd8SNickeau if ($hasAttribute === true) { 128237748cd8SNickeau return true; 128337748cd8SNickeau } else { 128437748cd8SNickeau return $this->hasHtmlAttribute($attribute); 128537748cd8SNickeau } 128637748cd8SNickeau } 128737748cd8SNickeau 12884cadd4f8SNickeau function hasHtmlAttribute($attribute): bool 128937748cd8SNickeau { 12904cadd4f8SNickeau return isset($this->outputAttributes[$attribute]); 129137748cd8SNickeau } 129237748cd8SNickeau 129337748cd8SNickeau /** 129404fd306cSNickeau * @throws ExceptionNotFound 129504fd306cSNickeau */ 129604fd306cSNickeau function getOutputAttribute($attribute) 129704fd306cSNickeau { 1298*70bbd7f1Sgerardnico $value = $this->outputAttributes[$attribute] ?? null; 129904fd306cSNickeau if ($value === null) { 130004fd306cSNickeau throw new ExceptionNotFound("No output attribute with the key ($attribute)"); 130104fd306cSNickeau } 130204fd306cSNickeau return $value; 130304fd306cSNickeau } 130404fd306cSNickeau 130504fd306cSNickeau /** 13064cadd4f8SNickeau * Encoding should happen always to the target format output. 13074cadd4f8SNickeau * ie HTML 13084cadd4f8SNickeau * 13094cadd4f8SNickeau * If it's user or not data. 13104cadd4f8SNickeau * 13114cadd4f8SNickeau * Sanitizing is completely useless. We follow the same principal than SQL parameters 13124cadd4f8SNickeau * 13134cadd4f8SNickeau * We follows the rule 2 to encode the unknown value 13144cadd4f8SNickeau * We encode the component attribute to the target output (ie HTML) 13154cadd4f8SNickeau * 131637748cd8SNickeau * @param array $arrayToEscape 131737748cd8SNickeau * @param null $subKey 13184cadd4f8SNickeau * 13194cadd4f8SNickeau * 13204cadd4f8SNickeau * 13214cadd4f8SNickeau * 13224cadd4f8SNickeau * https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-2-attribute-encode-before-inserting-untrusted-data-into-html-common-attributes 13234cadd4f8SNickeau * 13244cadd4f8SNickeau * @return array 132537748cd8SNickeau */ 132637748cd8SNickeau private 13274cadd4f8SNickeau function encodeToHtmlValue(array $arrayToEscape, $subKey = null): array 132837748cd8SNickeau { 132937748cd8SNickeau 13304cadd4f8SNickeau $returnedArray = []; 133137748cd8SNickeau foreach ($arrayToEscape as $name => $value) { 133237748cd8SNickeau 133304fd306cSNickeau $encodedName = Html::encode($name); 133437748cd8SNickeau 133537748cd8SNickeau /** 133637748cd8SNickeau * Boolean does not need to be encoded 133737748cd8SNickeau */ 133837748cd8SNickeau if (is_bool($value)) { 133937748cd8SNickeau if ($subKey == null) { 13404cadd4f8SNickeau $returnedArray[$encodedName] = $value; 134137748cd8SNickeau } else { 13424cadd4f8SNickeau $returnedArray[$subKey][$encodedName] = $value; 134337748cd8SNickeau } 134437748cd8SNickeau continue; 134537748cd8SNickeau } 134637748cd8SNickeau 13474cadd4f8SNickeau /** 13484cadd4f8SNickeau * 13494cadd4f8SNickeau * Browser bug in a srcset 13504cadd4f8SNickeau * 13514cadd4f8SNickeau * In the HTML attribute srcset (not in the img src), if we set, 13524cadd4f8SNickeau * ``` 13534cadd4f8SNickeau * http://nico.lan/_media/docs/metadata/metadata_manager.png?w=355&h=176&tseed=1636624852&tok=af396a 355w 13544cadd4f8SNickeau * ``` 13554cadd4f8SNickeau * the request is encoded ***by the browser**** one more time and the server gets: 13564cadd4f8SNickeau * * `&&h = 176` 13574cadd4f8SNickeau * * php create therefore the property 13584cadd4f8SNickeau * * `&h = 176` 13594cadd4f8SNickeau * * and note `h = 176` 13604cadd4f8SNickeau */ 13614cadd4f8SNickeau $encodeValue = true; 13624cadd4f8SNickeau if ($encodedName === "srcset" && !PluginUtility::isTest()) { 13634cadd4f8SNickeau /** 13644cadd4f8SNickeau * Our test xhtml processor does not support non ampersand encoded character 13654cadd4f8SNickeau */ 13664cadd4f8SNickeau $encodeValue = false; 13674cadd4f8SNickeau } 13684cadd4f8SNickeau if ($encodeValue) { 136904fd306cSNickeau $value = Html::encode($value); 13704cadd4f8SNickeau } 137137748cd8SNickeau if ($subKey == null) { 13724cadd4f8SNickeau $returnedArray[$encodedName] = $value; 137337748cd8SNickeau } else { 13744cadd4f8SNickeau $returnedArray[$subKey][$encodedName] = $value; 137537748cd8SNickeau } 13764cadd4f8SNickeau 137737748cd8SNickeau } 13784cadd4f8SNickeau return $returnedArray; 13794cadd4f8SNickeau 138037748cd8SNickeau } 138137748cd8SNickeau 13821fa8c418SNickeau public function __toString() 13831fa8c418SNickeau { 13841fa8c418SNickeau return "TagAttributes"; 13851fa8c418SNickeau } 13861fa8c418SNickeau 13874cadd4f8SNickeau /** 138804fd306cSNickeau * @throws ExceptionCompile 13894cadd4f8SNickeau */ 13904cadd4f8SNickeau public function getValueAsInteger(string $WIDTH_KEY, ?int $default = null): ?int 13914cadd4f8SNickeau { 13924cadd4f8SNickeau $value = $this->getValue($WIDTH_KEY, $default); 13934cadd4f8SNickeau if ($value === null) { 13944cadd4f8SNickeau return null; 13954cadd4f8SNickeau } 13964cadd4f8SNickeau return DataType::toInteger($value); 13974cadd4f8SNickeau } 13984cadd4f8SNickeau 13994cadd4f8SNickeau public function hasClass(string $string): bool 14004cadd4f8SNickeau { 14014cadd4f8SNickeau return strpos($this->getClass(), $string) !== false; 14024cadd4f8SNickeau } 14034cadd4f8SNickeau 14044cadd4f8SNickeau public function getDefaultStyleClassShouldBeAdded(): bool 14054cadd4f8SNickeau { 14064cadd4f8SNickeau return $this->defaultStyleClassShouldBeAdded; 14074cadd4f8SNickeau } 14084cadd4f8SNickeau 14094cadd4f8SNickeau public function setDefaultStyleClassShouldBeAdded(bool $bool): TagAttributes 14104cadd4f8SNickeau { 14114cadd4f8SNickeau $this->defaultStyleClassShouldBeAdded = $bool; 14124cadd4f8SNickeau return $this; 14134cadd4f8SNickeau } 14144cadd4f8SNickeau 141504fd306cSNickeau public function getDefaultGeneratedId() 141604fd306cSNickeau { 141704fd306cSNickeau return $this->getValue(TagAttributes::GENERATED_ID_KEY); 141804fd306cSNickeau } 141904fd306cSNickeau 142004fd306cSNickeau public function setKnownTypes(?array $knownTypes): TagAttributes 142104fd306cSNickeau { 142204fd306cSNickeau $this->knownTypes = $knownTypes; 142304fd306cSNickeau return $this; 142404fd306cSNickeau } 142504fd306cSNickeau 142604fd306cSNickeau public function removeType(): TagAttributes 142704fd306cSNickeau { 142804fd306cSNickeau $this->removeAttributeIfPresent(self::TYPE_KEY); 142904fd306cSNickeau return $this; 143004fd306cSNickeau } 143104fd306cSNickeau 143204fd306cSNickeau /** 143304fd306cSNickeau * @param $attributeName 143404fd306cSNickeau * @param array|null $default 143504fd306cSNickeau * @return string[] 143604fd306cSNickeau * @throws ExceptionBadArgument 143704fd306cSNickeau */ 143804fd306cSNickeau public function getValues($attributeName, ?array $default = null): ?array 143904fd306cSNickeau { 144004fd306cSNickeau /** 144104fd306cSNickeau * Replace all suite of space that have more than 2 characters 144204fd306cSNickeau */ 144304fd306cSNickeau $value = $this->getValue($attributeName); 144404fd306cSNickeau if ($value === null) { 144504fd306cSNickeau return $default; 144604fd306cSNickeau } 144704fd306cSNickeau if (!is_string($value)) { 144804fd306cSNickeau throw new ExceptionBadArgument("The attribute ($attributeName) does not contain a string, we can't return multiple values"); 144904fd306cSNickeau } 145004fd306cSNickeau $value = preg_replace("/\s{2,}/", " ", trim($value)); 145104fd306cSNickeau return explode(" ", $value); 145204fd306cSNickeau 145304fd306cSNickeau } 145404fd306cSNickeau 145504fd306cSNickeau public function getComponentAttributeValueAndRemoveIfPresent(string $attribute, $default = null) 145604fd306cSNickeau { 145704fd306cSNickeau $value = $this->getComponentAttributeValue($attribute, $default); 145804fd306cSNickeau $this->removeComponentAttributeIfPresent($attribute); 145904fd306cSNickeau return $value; 146004fd306cSNickeau } 146104fd306cSNickeau 146204fd306cSNickeau public function toUrl(): Url 146304fd306cSNickeau { 146404fd306cSNickeau $url = Url::createEmpty(); 146504fd306cSNickeau foreach ($this->componentAttributesCaseInsensitive as $key => $value) { 146604fd306cSNickeau $url->addQueryParameter($key, $value); 146704fd306cSNickeau } 146804fd306cSNickeau return $url; 146904fd306cSNickeau } 147004fd306cSNickeau 147104fd306cSNickeau public function hasComponentAttributeAndRemove(string $key): bool 147204fd306cSNickeau { 147304fd306cSNickeau $hasAttribute = $this->hasComponentAttribute($key); 147404fd306cSNickeau if ($hasAttribute) { 147504fd306cSNickeau $this->removeComponentAttribute($key); 147604fd306cSNickeau } 147704fd306cSNickeau return $hasAttribute; 147804fd306cSNickeau } 147904fd306cSNickeau 148004fd306cSNickeau /** 148104fd306cSNickeau * @param string $text - the text node content 148204fd306cSNickeau * @return $this 148304fd306cSNickeau */ 148404fd306cSNickeau public function setInnerText(string $text): TagAttributes 148504fd306cSNickeau { 148604fd306cSNickeau $this->innerText = $text; 148704fd306cSNickeau return $this; 148804fd306cSNickeau } 148904fd306cSNickeau 149004fd306cSNickeau /** 149104fd306cSNickeau * @throws ExceptionNotFound 149204fd306cSNickeau */ 149304fd306cSNickeau public function getInnerText(): string 149404fd306cSNickeau { 149504fd306cSNickeau if (!isset($this->innerText)) { 149604fd306cSNickeau throw new ExceptionNotFound("No inner text is set"); 149704fd306cSNickeau } 149804fd306cSNickeau return $this->innerText; 149904fd306cSNickeau } 150004fd306cSNickeau 150104fd306cSNickeau 150204fd306cSNickeau public function setId(string $id): TagAttributes 150304fd306cSNickeau { 150404fd306cSNickeau return $this->setComponentAttributeValue("id", $id); 150504fd306cSNickeau } 150604fd306cSNickeau 150704fd306cSNickeau /** 150804fd306cSNickeau * @throws ExceptionNotFound 150904fd306cSNickeau */ 151004fd306cSNickeau public function getId() 151104fd306cSNickeau { 151204fd306cSNickeau $id = $this->getValue(TagAttributes::ID_KEY); 151304fd306cSNickeau if ($id === null) { 151404fd306cSNickeau throw new ExceptionNotFound("no id"); 151504fd306cSNickeau } 151604fd306cSNickeau return $id; 151704fd306cSNickeau 151804fd306cSNickeau } 151904fd306cSNickeau 152037748cd8SNickeau 152137748cd8SNickeau} 1522