xref: /template/strap/ComboStrap/ConditionalLength.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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