1*04fd306cSNickeau<?php 2*04fd306cSNickeau 3*04fd306cSNickeaunamespace ComboStrap; 4*04fd306cSNickeau 5*04fd306cSNickeauuse ComboStrap\TagAttribute\Align; 6*04fd306cSNickeau 7*04fd306cSNickeau/** 8*04fd306cSNickeau * Represents a conditional length / value 9*04fd306cSNickeau */ 10*04fd306cSNickeauclass ConditionalLength 11*04fd306cSNickeau{ 12*04fd306cSNickeau 13*04fd306cSNickeau 14*04fd306cSNickeau const PERCENTAGE = "%"; 15*04fd306cSNickeau 16*04fd306cSNickeau /** 17*04fd306cSNickeau * @var string - the length value (may be breakpoint conditional) 18*04fd306cSNickeau */ 19*04fd306cSNickeau private $conditionalLength; 20*04fd306cSNickeau 21*04fd306cSNickeau /** 22*04fd306cSNickeau * @var string - the length value without breakpoint 23*04fd306cSNickeau */ 24*04fd306cSNickeau private $length; 25*04fd306cSNickeau /** 26*04fd306cSNickeau * @var string 27*04fd306cSNickeau */ 28*04fd306cSNickeau private $unitInLength; 29*04fd306cSNickeau /** 30*04fd306cSNickeau * The number in a length string 31*04fd306cSNickeau * @var float 32*04fd306cSNickeau */ 33*04fd306cSNickeau private $numerator; 34*04fd306cSNickeau /** 35*04fd306cSNickeau * @var string 36*04fd306cSNickeau */ 37*04fd306cSNickeau private $breakpoint; 38*04fd306cSNickeau private $defaultBreakpoint = "sm"; 39*04fd306cSNickeau private $denominator; 40*04fd306cSNickeau /** 41*04fd306cSNickeau * @var string 42*04fd306cSNickeau */ 43*04fd306cSNickeau private $axis; 44*04fd306cSNickeau /** 45*04fd306cSNickeau * @var bool 46*04fd306cSNickeau */ 47*04fd306cSNickeau private $isRatio = false; 48*04fd306cSNickeau 49*04fd306cSNickeau 50*04fd306cSNickeau /** 51*04fd306cSNickeau * @throws ExceptionBadArgument 52*04fd306cSNickeau */ 53*04fd306cSNickeau public function __construct($value, $defaultBreakpoint) 54*04fd306cSNickeau { 55*04fd306cSNickeau $this->conditionalLength = $value; 56*04fd306cSNickeau if ($defaultBreakpoint !== null) { 57*04fd306cSNickeau $this->defaultBreakpoint = $defaultBreakpoint; 58*04fd306cSNickeau } 59*04fd306cSNickeau 60*04fd306cSNickeau /** 61*04fd306cSNickeau * Breakpoint Suffix 62*04fd306cSNickeau */ 63*04fd306cSNickeau $this->length = $value; 64*04fd306cSNickeau try { 65*04fd306cSNickeau $conditionalValue = ConditionalValue::createFrom($value); 66*04fd306cSNickeau $this->length = $conditionalValue->getValue(); 67*04fd306cSNickeau $this->breakpoint = $conditionalValue->getBreakpoint(); 68*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 69*04fd306cSNickeau // not conditional 70*04fd306cSNickeau } 71*04fd306cSNickeau 72*04fd306cSNickeau /** 73*04fd306cSNickeau * Axis prefix 74*04fd306cSNickeau */ 75*04fd306cSNickeau $axis = substr($value, 0, 2); 76*04fd306cSNickeau switch ($axis) { 77*04fd306cSNickeau case "x-": 78*04fd306cSNickeau $this->axis = "x"; 79*04fd306cSNickeau break; 80*04fd306cSNickeau case "y-"; 81*04fd306cSNickeau $this->axis = "y"; 82*04fd306cSNickeau break; 83*04fd306cSNickeau } 84*04fd306cSNickeau 85*04fd306cSNickeau try { 86*04fd306cSNickeau $this->parseAsNumberWithOptionalUnit(); 87*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 88*04fd306cSNickeau try { 89*04fd306cSNickeau $this->parseAsRatio(); 90*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 91*04fd306cSNickeau // string only 92*04fd306cSNickeau } 93*04fd306cSNickeau } 94*04fd306cSNickeau 95*04fd306cSNickeau 96*04fd306cSNickeau } 97*04fd306cSNickeau 98*04fd306cSNickeau /** 99*04fd306cSNickeau * @throws ExceptionBadArgument 100*04fd306cSNickeau */ 101*04fd306cSNickeau public static function createFromString(string $widthLength, string $defaultBreakpoint = null): ConditionalLength 102*04fd306cSNickeau { 103*04fd306cSNickeau return new ConditionalLength($widthLength, $defaultBreakpoint); 104*04fd306cSNickeau } 105*04fd306cSNickeau 106*04fd306cSNickeau public function getLengthUnit(): ?string 107*04fd306cSNickeau { 108*04fd306cSNickeau return $this->unitInLength; 109*04fd306cSNickeau } 110*04fd306cSNickeau 111*04fd306cSNickeau /** 112*04fd306cSNickeau * @throws ExceptionBadArgument 113*04fd306cSNickeau */ 114*04fd306cSNickeau public function toPixelNumber(): int 115*04fd306cSNickeau { 116*04fd306cSNickeau 117*04fd306cSNickeau switch ($this->unitInLength) { 118*04fd306cSNickeau case "rem": 119*04fd306cSNickeau $remValue = ExecutionContext::getActualOrCreateFromEnv()->getConfig()->getRemFontSizeOrDefault(); 120*04fd306cSNickeau $targetValue = $this->numerator * $remValue; 121*04fd306cSNickeau break; 122*04fd306cSNickeau case "px": 123*04fd306cSNickeau default: 124*04fd306cSNickeau $targetValue = $this->numerator; 125*04fd306cSNickeau } 126*04fd306cSNickeau return DataType::toInteger($targetValue); 127*04fd306cSNickeau 128*04fd306cSNickeau } 129*04fd306cSNickeau 130*04fd306cSNickeau public function getNumerator(): ?float 131*04fd306cSNickeau { 132*04fd306cSNickeau return $this->numerator; 133*04fd306cSNickeau } 134*04fd306cSNickeau 135*04fd306cSNickeau /** 136*04fd306cSNickeau * @throws ExceptionBadArgument 137*04fd306cSNickeau */ 138*04fd306cSNickeau public function toColClass(): string 139*04fd306cSNickeau { 140*04fd306cSNickeau 141*04fd306cSNickeau $ratio = $this->getRatio(); 142*04fd306cSNickeau if ($ratio > 1) { 143*04fd306cSNickeau throw new ExceptionBadArgument("The length ratio ($ratio) is greater than 1. It should be less than 1 to get a col class."); 144*04fd306cSNickeau } 145*04fd306cSNickeau $colsNumber = floor(GridTag::GRID_TOTAL_COLUMNS * $this->numerator / $this->denominator); 146*04fd306cSNickeau $breakpoint = $this->getBreakpointOrDefault(); 147*04fd306cSNickeau if ($breakpoint === "xs") { 148*04fd306cSNickeau return "col-$colsNumber"; 149*04fd306cSNickeau } 150*04fd306cSNickeau return "col-{$breakpoint}-$colsNumber"; 151*04fd306cSNickeau 152*04fd306cSNickeau 153*04fd306cSNickeau } 154*04fd306cSNickeau 155*04fd306cSNickeau /** 156*04fd306cSNickeau * @throws ExceptionBadArgument 157*04fd306cSNickeau */ 158*04fd306cSNickeau public function toRowColsClass(): string 159*04fd306cSNickeau { 160*04fd306cSNickeau 161*04fd306cSNickeau if ($this->numerator === null) { 162*04fd306cSNickeau if ($this->getLength() === "auto") { 163*04fd306cSNickeau if (Bootstrap::getBootStrapMajorVersion() != Bootstrap::BootStrapFiveMajorVersion) { 164*04fd306cSNickeau // row-cols-auto is not in 4.0 165*04fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet("row-cols-auto"); 166*04fd306cSNickeau } 167*04fd306cSNickeau return "row-cols-auto"; 168*04fd306cSNickeau } 169*04fd306cSNickeau throw new ExceptionBadArgument("A row col class can be calculated only from a number ({$this}) or from the `auto` value"); 170*04fd306cSNickeau } 171*04fd306cSNickeau 172*04fd306cSNickeau $colsNumber = intval($this->numerator); 173*04fd306cSNickeau $totalColumns = GridTag::GRID_TOTAL_COLUMNS; 174*04fd306cSNickeau if ($colsNumber > $totalColumns) { 175*04fd306cSNickeau throw new ExceptionBadArgument("A row col class can be calculated only from a number below $totalColumns ({$this}"); 176*04fd306cSNickeau } 177*04fd306cSNickeau $breakpoint = $this->getBreakpointOrDefault(); 178*04fd306cSNickeau if ($breakpoint === "xs") { 179*04fd306cSNickeau return "row-cols-$colsNumber"; 180*04fd306cSNickeau } 181*04fd306cSNickeau return "row-cols-{$breakpoint}-$colsNumber"; 182*04fd306cSNickeau } 183*04fd306cSNickeau 184*04fd306cSNickeau public 185*04fd306cSNickeau function getBreakpoint(): ?string 186*04fd306cSNickeau { 187*04fd306cSNickeau return $this->breakpoint; 188*04fd306cSNickeau } 189*04fd306cSNickeau 190*04fd306cSNickeau 191*04fd306cSNickeau public 192*04fd306cSNickeau function getLength() 193*04fd306cSNickeau { 194*04fd306cSNickeau return $this->length; 195*04fd306cSNickeau } 196*04fd306cSNickeau 197*04fd306cSNickeau public 198*04fd306cSNickeau function __toString() 199*04fd306cSNickeau { 200*04fd306cSNickeau return $this->conditionalLength; 201*04fd306cSNickeau } 202*04fd306cSNickeau 203*04fd306cSNickeau /** 204*04fd306cSNickeau * For CSS a unit is mandatory (not for HTML or SVG attributes) 205*04fd306cSNickeau * @throws ExceptionBadArgument 206*04fd306cSNickeau */ 207*04fd306cSNickeau public 208*04fd306cSNickeau function toCssLength() 209*04fd306cSNickeau { 210*04fd306cSNickeau switch ($this->unitInLength){ 211*04fd306cSNickeau case "vh": 212*04fd306cSNickeau case "wh": 213*04fd306cSNickeau case "rem": 214*04fd306cSNickeau return $this->length; 215*04fd306cSNickeau } 216*04fd306cSNickeau /** 217*04fd306cSNickeau * A length value may be also `fit-content` 218*04fd306cSNickeau * we just check that if there is a number, 219*04fd306cSNickeau * we add the pixel 220*04fd306cSNickeau */ 221*04fd306cSNickeau if ($this->numerator !== null) { 222*04fd306cSNickeau return $this->toPixelNumber() . "px"; 223*04fd306cSNickeau } else { 224*04fd306cSNickeau if ($this->length === "fit") { 225*04fd306cSNickeau return "fit-content"; 226*04fd306cSNickeau } 227*04fd306cSNickeau return $this->length; 228*04fd306cSNickeau } 229*04fd306cSNickeau } 230*04fd306cSNickeau 231*04fd306cSNickeau public 232*04fd306cSNickeau function getBreakpointOrDefault(): string 233*04fd306cSNickeau { 234*04fd306cSNickeau if ($this->breakpoint !== null) { 235*04fd306cSNickeau return $this->breakpoint; 236*04fd306cSNickeau } 237*04fd306cSNickeau return $this->defaultBreakpoint; 238*04fd306cSNickeau } 239*04fd306cSNickeau 240*04fd306cSNickeau 241*04fd306cSNickeau public 242*04fd306cSNickeau function getDenominator(): ?float 243*04fd306cSNickeau { 244*04fd306cSNickeau return $this->denominator; 245*04fd306cSNickeau } 246*04fd306cSNickeau 247*04fd306cSNickeau /** 248*04fd306cSNickeau * @throws ExceptionBadSyntax 249*04fd306cSNickeau */ 250*04fd306cSNickeau private 251*04fd306cSNickeau function parseAsNumberWithOptionalUnit() 252*04fd306cSNickeau { 253*04fd306cSNickeau /** 254*04fd306cSNickeau * Not a numeric alone 255*04fd306cSNickeau * Does the length value has an unit ? 256*04fd306cSNickeau */ 257*04fd306cSNickeau preg_match("/^([0-9.]+)([^0-9]*)$/i", $this->length, $matches, PREG_OFFSET_CAPTURE); 258*04fd306cSNickeau if (sizeof($matches) === 0) { 259*04fd306cSNickeau throw new ExceptionBadSyntax("Length is not a number with optional unit"); 260*04fd306cSNickeau } 261*04fd306cSNickeau $localNumber = $matches[1][0]; 262*04fd306cSNickeau try { 263*04fd306cSNickeau $this->numerator = DataType::toFloat($localNumber); 264*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 265*04fd306cSNickeau // should not happen due to the match but yeah 266*04fd306cSNickeau throw new ExceptionBadSyntax("The number value ($localNumber) of the length value ($this->length) is not a valid float format."); 267*04fd306cSNickeau } 268*04fd306cSNickeau $this->denominator = 1; 269*04fd306cSNickeau 270*04fd306cSNickeau $secondMatch = $matches[2][0]; 271*04fd306cSNickeau if ($secondMatch == "") { 272*04fd306cSNickeau return; 273*04fd306cSNickeau } 274*04fd306cSNickeau $this->unitInLength = $secondMatch; 275*04fd306cSNickeau if ($this->unitInLength === self::PERCENTAGE) { 276*04fd306cSNickeau $this->denominator = 100; 277*04fd306cSNickeau } 278*04fd306cSNickeau 279*04fd306cSNickeau } 280*04fd306cSNickeau 281*04fd306cSNickeau /** 282*04fd306cSNickeau * @throws ExceptionBadSyntax 283*04fd306cSNickeau */ 284*04fd306cSNickeau private 285*04fd306cSNickeau function parseAsRatio() 286*04fd306cSNickeau { 287*04fd306cSNickeau preg_match("/^([0-9]+):([0-9]+)$/i", $this->length, $matches, PREG_OFFSET_CAPTURE); 288*04fd306cSNickeau if (sizeof($matches) === 0) { 289*04fd306cSNickeau throw new ExceptionBadSyntax("Length is not a ratio"); 290*04fd306cSNickeau } 291*04fd306cSNickeau $numerator = $matches[1][0]; 292*04fd306cSNickeau try { 293*04fd306cSNickeau $this->numerator = DataType::toFloat($numerator); 294*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 295*04fd306cSNickeau // should not happen due to the match but yeah 296*04fd306cSNickeau throw new ExceptionBadSyntax("The number value ($numerator) of the length value ($this->length) is not a valid float format."); 297*04fd306cSNickeau } 298*04fd306cSNickeau $denominator = $matches[2][0]; 299*04fd306cSNickeau try { 300*04fd306cSNickeau $this->denominator = DataType::toFloat($denominator); 301*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 302*04fd306cSNickeau // should not happen due to the match but yeah 303*04fd306cSNickeau throw new ExceptionBadSyntax("The number value ($denominator) of the length value ($this->length) is not a valid float format."); 304*04fd306cSNickeau } 305*04fd306cSNickeau $this->isRatio = true; 306*04fd306cSNickeau } 307*04fd306cSNickeau 308*04fd306cSNickeau /** 309*04fd306cSNickeau * @throws ExceptionBadArgument 310*04fd306cSNickeau */ 311*04fd306cSNickeau public 312*04fd306cSNickeau function getRatio() 313*04fd306cSNickeau { 314*04fd306cSNickeau if (!$this->isRatio()) { 315*04fd306cSNickeau return null; 316*04fd306cSNickeau } 317*04fd306cSNickeau if ($this->numerator == null) { 318*04fd306cSNickeau return null; 319*04fd306cSNickeau } 320*04fd306cSNickeau if ($this->denominator == null) { 321*04fd306cSNickeau return null; 322*04fd306cSNickeau } 323*04fd306cSNickeau if ($this->denominator == 0) { 324*04fd306cSNickeau throw new ExceptionBadArgument("The denominator of the conditional length ($this) is 0. You can't ask a ratio."); 325*04fd306cSNickeau } 326*04fd306cSNickeau return $this->numerator / $this->denominator; 327*04fd306cSNickeau } 328*04fd306cSNickeau 329*04fd306cSNickeau public function getAxis(): string 330*04fd306cSNickeau { 331*04fd306cSNickeau return $this->axis; 332*04fd306cSNickeau } 333*04fd306cSNickeau 334*04fd306cSNickeau public function getAxisOrDefault(): string 335*04fd306cSNickeau { 336*04fd306cSNickeau if ($this->axis !== null) { 337*04fd306cSNickeau return $this->axis; 338*04fd306cSNickeau } 339*04fd306cSNickeau return Align::DEFAULT_AXIS; 340*04fd306cSNickeau } 341*04fd306cSNickeau 342*04fd306cSNickeau public function isRatio(): bool 343*04fd306cSNickeau { 344*04fd306cSNickeau if ($this->getLengthUnit() === self::PERCENTAGE) { 345*04fd306cSNickeau return true; 346*04fd306cSNickeau } 347*04fd306cSNickeau return $this->isRatio; 348*04fd306cSNickeau } 349*04fd306cSNickeau 350*04fd306cSNickeau /** 351*04fd306cSNickeau * @return string - the breakpoint value that should be added into a bootstrap class 352*04fd306cSNickeau * 353*04fd306cSNickeau * For instance, for ''xs'', you would get ''-xs'' 354*04fd306cSNickeau * If there is no breakpoint, the empty string is returned 355*04fd306cSNickeau */ 356*04fd306cSNickeau public function getBreakpointForBootstrapClass(): string 357*04fd306cSNickeau { 358*04fd306cSNickeau 359*04fd306cSNickeau if ($this->breakpoint !== null) { 360*04fd306cSNickeau if($this->breakpoint==="xs"){ 361*04fd306cSNickeau return ""; 362*04fd306cSNickeau } 363*04fd306cSNickeau return "-{$this->breakpoint}"; 364*04fd306cSNickeau } else { 365*04fd306cSNickeau return ""; 366*04fd306cSNickeau } 367*04fd306cSNickeau } 368*04fd306cSNickeau 369*04fd306cSNickeau 370*04fd306cSNickeau} 371