xref: /template/strap/ComboStrap/ConditionalLength.php (revision 30e068fcc31aee061f6a7260f1a15d5563b924f6)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeaunamespace ComboStrap;
404fd306cSNickeau
504fd306cSNickeauuse ComboStrap\TagAttribute\Align;
604fd306cSNickeau
704fd306cSNickeau/**
804fd306cSNickeau * Represents a conditional length / value
904fd306cSNickeau */
1004fd306cSNickeauclass ConditionalLength
1104fd306cSNickeau{
1204fd306cSNickeau
1304fd306cSNickeau
1404fd306cSNickeau    const PERCENTAGE = "%";
1504fd306cSNickeau
1604fd306cSNickeau    /**
17*30e068fcSgerardnico     * 'fit-content' value, ignore the max-width property
18*30e068fcSgerardnico     * (max-content does not)
19*30e068fcSgerardnico     */
20*30e068fcSgerardnico    const FIT_CONTENT = "fit-content";
21*30e068fcSgerardnico
22*30e068fcSgerardnico    /**
2304fd306cSNickeau     * @var string - the length value (may be breakpoint conditional)
2404fd306cSNickeau     */
2504fd306cSNickeau    private $conditionalLength;
2604fd306cSNickeau
2704fd306cSNickeau    /**
2804fd306cSNickeau     * @var string - the length value without breakpoint
2904fd306cSNickeau     */
3004fd306cSNickeau    private $length;
3104fd306cSNickeau    /**
3204fd306cSNickeau     * @var string
3304fd306cSNickeau     */
3404fd306cSNickeau    private $unitInLength;
3504fd306cSNickeau    /**
3604fd306cSNickeau     * The number in a length string
3704fd306cSNickeau     * @var float
3804fd306cSNickeau     */
3904fd306cSNickeau    private $numerator;
4004fd306cSNickeau    /**
4104fd306cSNickeau     * @var string
4204fd306cSNickeau     */
4304fd306cSNickeau    private $breakpoint;
4404fd306cSNickeau    private $defaultBreakpoint = "sm";
4504fd306cSNickeau    private $denominator;
4604fd306cSNickeau    /**
4704fd306cSNickeau     * @var string
4804fd306cSNickeau     */
4904fd306cSNickeau    private $axis;
5004fd306cSNickeau    /**
5104fd306cSNickeau     * @var bool
5204fd306cSNickeau     */
5304fd306cSNickeau    private $isRatio = false;
5404fd306cSNickeau
5504fd306cSNickeau
5604fd306cSNickeau    /**
5704fd306cSNickeau     * @throws ExceptionBadArgument
5804fd306cSNickeau     */
5904fd306cSNickeau    public function __construct($value, $defaultBreakpoint)
6004fd306cSNickeau    {
6104fd306cSNickeau        $this->conditionalLength = $value;
6204fd306cSNickeau        if ($defaultBreakpoint !== null) {
6304fd306cSNickeau            $this->defaultBreakpoint = $defaultBreakpoint;
6404fd306cSNickeau        }
6504fd306cSNickeau
6604fd306cSNickeau        /**
6704fd306cSNickeau         * Breakpoint Suffix
6804fd306cSNickeau         */
6904fd306cSNickeau        $this->length = $value;
7004fd306cSNickeau        try {
7104fd306cSNickeau            $conditionalValue = ConditionalValue::createFrom($value);
7204fd306cSNickeau            $this->length = $conditionalValue->getValue();
7304fd306cSNickeau            $this->breakpoint = $conditionalValue->getBreakpoint();
7404fd306cSNickeau        } catch (ExceptionBadSyntax $e) {
7504fd306cSNickeau            // not conditional
7604fd306cSNickeau        }
7704fd306cSNickeau
7804fd306cSNickeau        /**
7904fd306cSNickeau         * Axis prefix
8004fd306cSNickeau         */
8104fd306cSNickeau        $axis = substr($value, 0, 2);
8204fd306cSNickeau        switch ($axis) {
8304fd306cSNickeau            case "x-":
8404fd306cSNickeau                $this->axis = "x";
8504fd306cSNickeau                break;
8604fd306cSNickeau            case "y-";
8704fd306cSNickeau                $this->axis = "y";
8804fd306cSNickeau                break;
8904fd306cSNickeau        }
9004fd306cSNickeau
9104fd306cSNickeau        try {
9204fd306cSNickeau            $this->parseAsNumberWithOptionalUnit();
9304fd306cSNickeau        } catch (ExceptionBadSyntax $e) {
9404fd306cSNickeau            try {
9504fd306cSNickeau                $this->parseAsRatio();
9604fd306cSNickeau            } catch (ExceptionBadSyntax $e) {
9704fd306cSNickeau                // string only
9804fd306cSNickeau            }
9904fd306cSNickeau        }
10004fd306cSNickeau
10104fd306cSNickeau
10204fd306cSNickeau    }
10304fd306cSNickeau
10404fd306cSNickeau    /**
10504fd306cSNickeau     * @throws ExceptionBadArgument
10604fd306cSNickeau     */
10704fd306cSNickeau    public static function createFromString(string $widthLength, string $defaultBreakpoint = null): ConditionalLength
10804fd306cSNickeau    {
10904fd306cSNickeau        return new ConditionalLength($widthLength, $defaultBreakpoint);
11004fd306cSNickeau    }
11104fd306cSNickeau
11204fd306cSNickeau    public function getLengthUnit(): ?string
11304fd306cSNickeau    {
11404fd306cSNickeau        return $this->unitInLength;
11504fd306cSNickeau    }
11604fd306cSNickeau
11704fd306cSNickeau    /**
11804fd306cSNickeau     * @throws ExceptionBadArgument
11904fd306cSNickeau     */
12004fd306cSNickeau    public function toPixelNumber(): int
12104fd306cSNickeau    {
12204fd306cSNickeau
12304fd306cSNickeau        switch ($this->unitInLength) {
12404fd306cSNickeau            case "rem":
12504fd306cSNickeau                $remValue = ExecutionContext::getActualOrCreateFromEnv()->getConfig()->getRemFontSizeOrDefault();
12604fd306cSNickeau                $targetValue = $this->numerator * $remValue;
12704fd306cSNickeau                break;
12804fd306cSNickeau            case "px":
12904fd306cSNickeau            default:
13004fd306cSNickeau                $targetValue = $this->numerator;
13104fd306cSNickeau        }
13204fd306cSNickeau        return DataType::toInteger($targetValue);
13304fd306cSNickeau
13404fd306cSNickeau    }
13504fd306cSNickeau
13604fd306cSNickeau    public function getNumerator(): ?float
13704fd306cSNickeau    {
13804fd306cSNickeau        return $this->numerator;
13904fd306cSNickeau    }
14004fd306cSNickeau
14104fd306cSNickeau    /**
14204fd306cSNickeau     * @throws ExceptionBadArgument
14304fd306cSNickeau     */
14404fd306cSNickeau    public function toColClass(): string
14504fd306cSNickeau    {
14604fd306cSNickeau
14704fd306cSNickeau        $ratio = $this->getRatio();
14804fd306cSNickeau        if ($ratio > 1) {
14904fd306cSNickeau            throw new ExceptionBadArgument("The length ratio ($ratio) is greater than 1. It should be less than 1 to get a col class.");
15004fd306cSNickeau        }
15104fd306cSNickeau        $colsNumber = floor(GridTag::GRID_TOTAL_COLUMNS * $this->numerator / $this->denominator);
15204fd306cSNickeau        $breakpoint = $this->getBreakpointOrDefault();
15304fd306cSNickeau        if ($breakpoint === "xs") {
15404fd306cSNickeau            return "col-$colsNumber";
15504fd306cSNickeau        }
15604fd306cSNickeau        return "col-{$breakpoint}-$colsNumber";
15704fd306cSNickeau
15804fd306cSNickeau
15904fd306cSNickeau    }
16004fd306cSNickeau
16104fd306cSNickeau    /**
16204fd306cSNickeau     * @throws ExceptionBadArgument
16304fd306cSNickeau     */
16404fd306cSNickeau    public function toRowColsClass(): string
16504fd306cSNickeau    {
16604fd306cSNickeau
16704fd306cSNickeau        if ($this->numerator === null) {
16804fd306cSNickeau            if ($this->getLength() === "auto") {
16904fd306cSNickeau                if (Bootstrap::getBootStrapMajorVersion() != Bootstrap::BootStrapFiveMajorVersion) {
17004fd306cSNickeau                    // row-cols-auto is not in 4.0
17104fd306cSNickeau                    PluginUtility::getSnippetManager()->attachCssInternalStyleSheet("row-cols-auto");
17204fd306cSNickeau                }
17304fd306cSNickeau                return "row-cols-auto";
17404fd306cSNickeau            }
17504fd306cSNickeau            throw new ExceptionBadArgument("A row col class can be calculated only from a number ({$this}) or from the `auto` value");
17604fd306cSNickeau        }
17704fd306cSNickeau
17804fd306cSNickeau        $colsNumber = intval($this->numerator);
17904fd306cSNickeau        $totalColumns = GridTag::GRID_TOTAL_COLUMNS;
18004fd306cSNickeau        if ($colsNumber > $totalColumns) {
18104fd306cSNickeau            throw new ExceptionBadArgument("A row col class can be calculated only from a number below $totalColumns ({$this}");
18204fd306cSNickeau        }
18304fd306cSNickeau        $breakpoint = $this->getBreakpointOrDefault();
18404fd306cSNickeau        if ($breakpoint === "xs") {
18504fd306cSNickeau            return "row-cols-$colsNumber";
18604fd306cSNickeau        }
18704fd306cSNickeau        return "row-cols-{$breakpoint}-$colsNumber";
18804fd306cSNickeau    }
18904fd306cSNickeau
19004fd306cSNickeau    public
19104fd306cSNickeau    function getBreakpoint(): ?string
19204fd306cSNickeau    {
19304fd306cSNickeau        return $this->breakpoint;
19404fd306cSNickeau    }
19504fd306cSNickeau
19604fd306cSNickeau
19704fd306cSNickeau    public
19804fd306cSNickeau    function getLength()
19904fd306cSNickeau    {
20004fd306cSNickeau        return $this->length;
20104fd306cSNickeau    }
20204fd306cSNickeau
20304fd306cSNickeau    public
20404fd306cSNickeau    function __toString()
20504fd306cSNickeau    {
20604fd306cSNickeau        return $this->conditionalLength;
20704fd306cSNickeau    }
20804fd306cSNickeau
20904fd306cSNickeau    /**
21004fd306cSNickeau     * For CSS a unit is mandatory (not for HTML or SVG attributes)
21104fd306cSNickeau     * @throws ExceptionBadArgument
21204fd306cSNickeau     */
21304fd306cSNickeau    public
21404fd306cSNickeau    function toCssLength()
21504fd306cSNickeau    {
21604fd306cSNickeau        switch ($this->unitInLength){
21704fd306cSNickeau            case "vh":
21804fd306cSNickeau            case "wh":
21904fd306cSNickeau            case "rem":
22004fd306cSNickeau                return $this->length;
22104fd306cSNickeau        }
22204fd306cSNickeau        /**
22304fd306cSNickeau         * A length value may be also `fit-content`
22404fd306cSNickeau         * we just check that if there is a number,
22504fd306cSNickeau         * we add the pixel
22604fd306cSNickeau         */
22704fd306cSNickeau        if ($this->numerator !== null) {
22804fd306cSNickeau            return $this->toPixelNumber() . "px";
22904fd306cSNickeau        } else {
23004fd306cSNickeau            if ($this->length === "fit") {
231*30e068fcSgerardnico                return self::FIT_CONTENT;
23204fd306cSNickeau            }
23304fd306cSNickeau            return $this->length;
23404fd306cSNickeau        }
23504fd306cSNickeau    }
23604fd306cSNickeau
23704fd306cSNickeau    public
23804fd306cSNickeau    function getBreakpointOrDefault(): string
23904fd306cSNickeau    {
24004fd306cSNickeau        if ($this->breakpoint !== null) {
24104fd306cSNickeau            return $this->breakpoint;
24204fd306cSNickeau        }
24304fd306cSNickeau        return $this->defaultBreakpoint;
24404fd306cSNickeau    }
24504fd306cSNickeau
24604fd306cSNickeau
24704fd306cSNickeau    public
24804fd306cSNickeau    function getDenominator(): ?float
24904fd306cSNickeau    {
25004fd306cSNickeau        return $this->denominator;
25104fd306cSNickeau    }
25204fd306cSNickeau
25304fd306cSNickeau    /**
25404fd306cSNickeau     * @throws ExceptionBadSyntax
25504fd306cSNickeau     */
25604fd306cSNickeau    private
25704fd306cSNickeau    function parseAsNumberWithOptionalUnit()
25804fd306cSNickeau    {
25904fd306cSNickeau        /**
26004fd306cSNickeau         * Not a numeric alone
26104fd306cSNickeau         * Does the length value has an unit ?
26204fd306cSNickeau         */
26304fd306cSNickeau        preg_match("/^([0-9.]+)([^0-9]*)$/i", $this->length, $matches, PREG_OFFSET_CAPTURE);
26404fd306cSNickeau        if (sizeof($matches) === 0) {
26504fd306cSNickeau            throw new ExceptionBadSyntax("Length is not a number with optional unit");
26604fd306cSNickeau        }
26704fd306cSNickeau        $localNumber = $matches[1][0];
26804fd306cSNickeau        try {
26904fd306cSNickeau            $this->numerator = DataType::toFloat($localNumber);
27004fd306cSNickeau        } catch (ExceptionBadArgument $e) {
27104fd306cSNickeau            // should not happen due to the match but yeah
27204fd306cSNickeau            throw new ExceptionBadSyntax("The number value ($localNumber) of the length value ($this->length) is not a valid float format.");
27304fd306cSNickeau        }
27404fd306cSNickeau        $this->denominator = 1;
27504fd306cSNickeau
27604fd306cSNickeau        $secondMatch = $matches[2][0];
27704fd306cSNickeau        if ($secondMatch == "") {
27804fd306cSNickeau            return;
27904fd306cSNickeau        }
28004fd306cSNickeau        $this->unitInLength = $secondMatch;
28104fd306cSNickeau        if ($this->unitInLength === self::PERCENTAGE) {
28204fd306cSNickeau            $this->denominator = 100;
28304fd306cSNickeau        }
28404fd306cSNickeau
28504fd306cSNickeau    }
28604fd306cSNickeau
28704fd306cSNickeau    /**
28804fd306cSNickeau     * @throws ExceptionBadSyntax
28904fd306cSNickeau     */
29004fd306cSNickeau    private
29104fd306cSNickeau    function parseAsRatio()
29204fd306cSNickeau    {
29304fd306cSNickeau        preg_match("/^([0-9]+):([0-9]+)$/i", $this->length, $matches, PREG_OFFSET_CAPTURE);
29404fd306cSNickeau        if (sizeof($matches) === 0) {
29504fd306cSNickeau            throw new ExceptionBadSyntax("Length is not a ratio");
29604fd306cSNickeau        }
29704fd306cSNickeau        $numerator = $matches[1][0];
29804fd306cSNickeau        try {
29904fd306cSNickeau            $this->numerator = DataType::toFloat($numerator);
30004fd306cSNickeau        } catch (ExceptionBadArgument $e) {
30104fd306cSNickeau            // should not happen due to the match but yeah
30204fd306cSNickeau            throw new ExceptionBadSyntax("The number value ($numerator) of the length value ($this->length) is not a valid float format.");
30304fd306cSNickeau        }
30404fd306cSNickeau        $denominator = $matches[2][0];
30504fd306cSNickeau        try {
30604fd306cSNickeau            $this->denominator = DataType::toFloat($denominator);
30704fd306cSNickeau        } catch (ExceptionBadArgument $e) {
30804fd306cSNickeau            // should not happen due to the match but yeah
30904fd306cSNickeau            throw new ExceptionBadSyntax("The number value ($denominator) of the length value ($this->length) is not a valid float format.");
31004fd306cSNickeau        }
31104fd306cSNickeau        $this->isRatio = true;
31204fd306cSNickeau    }
31304fd306cSNickeau
31404fd306cSNickeau    /**
31504fd306cSNickeau     * @throws ExceptionBadArgument
31604fd306cSNickeau     */
31704fd306cSNickeau    public
31804fd306cSNickeau    function getRatio()
31904fd306cSNickeau    {
32004fd306cSNickeau        if (!$this->isRatio()) {
32104fd306cSNickeau            return null;
32204fd306cSNickeau        }
32304fd306cSNickeau        if ($this->numerator == null) {
32404fd306cSNickeau            return null;
32504fd306cSNickeau        }
32604fd306cSNickeau        if ($this->denominator == null) {
32704fd306cSNickeau            return null;
32804fd306cSNickeau        }
32904fd306cSNickeau        if ($this->denominator == 0) {
33004fd306cSNickeau            throw new ExceptionBadArgument("The denominator of the conditional length ($this) is 0. You can't ask a ratio.");
33104fd306cSNickeau        }
33204fd306cSNickeau        return $this->numerator / $this->denominator;
33304fd306cSNickeau    }
33404fd306cSNickeau
33504fd306cSNickeau    public function getAxis(): string
33604fd306cSNickeau    {
33704fd306cSNickeau        return $this->axis;
33804fd306cSNickeau    }
33904fd306cSNickeau
34004fd306cSNickeau    public function getAxisOrDefault(): string
34104fd306cSNickeau    {
34204fd306cSNickeau        if ($this->axis !== null) {
34304fd306cSNickeau            return $this->axis;
34404fd306cSNickeau        }
34504fd306cSNickeau        return Align::DEFAULT_AXIS;
34604fd306cSNickeau    }
34704fd306cSNickeau
34804fd306cSNickeau    public function isRatio(): bool
34904fd306cSNickeau    {
35004fd306cSNickeau        if ($this->getLengthUnit() === self::PERCENTAGE) {
35104fd306cSNickeau            return true;
35204fd306cSNickeau        }
35304fd306cSNickeau        return $this->isRatio;
35404fd306cSNickeau    }
35504fd306cSNickeau
35604fd306cSNickeau    /**
35704fd306cSNickeau     * @return string - the breakpoint value that should be added into a bootstrap class
35804fd306cSNickeau     *
35904fd306cSNickeau     * For instance, for ''xs'', you would get ''-xs''
36004fd306cSNickeau     * If there is no breakpoint, the empty string is returned
36104fd306cSNickeau     */
36204fd306cSNickeau    public function getBreakpointForBootstrapClass(): string
36304fd306cSNickeau    {
36404fd306cSNickeau
36504fd306cSNickeau        if ($this->breakpoint !== null) {
36604fd306cSNickeau            if($this->breakpoint==="xs"){
36704fd306cSNickeau                return "";
36804fd306cSNickeau            }
36904fd306cSNickeau            return "-{$this->breakpoint}";
37004fd306cSNickeau        } else {
37104fd306cSNickeau            return "";
37204fd306cSNickeau        }
37304fd306cSNickeau    }
37404fd306cSNickeau
37504fd306cSNickeau
37604fd306cSNickeau}
377