PluginProbe
MarqueeX – Smooth Marquee Slider, News Ticker & Post Marquee for Block Editor & Elementor / 0.6.0
MarqueeX – Smooth Marquee Slider, News Ticker & Post Marquee for Block Editor & Elementor v0.6.0
0.6.0 0.5.4 0.5.3 0.5.2 0.5.1 0.5.0 0.4.0 0.3.3 0.3.2 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.1.0 0.1.1 0.1.2 0.2.0 0.2.1 0.2.2 0.2.3 0.3.0 0.3.1
marqueex / includes / Core / CSSBuilder.php

CSSBuilder.php in MarqueeX – Smooth Marquee Slider, News Ticker & Post Marquee for Block Editor & Elementor 0.6.0, at includes/Core/CSSBuilder.php

246 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Wpxero\Marqueex\Core;
4
5 use Wpxero\Marqueex\Core\Property;
6 use Wpxero\Marqueex\Core\Settings;
7
8 defined('ABSPATH') || exit;
9
10 const PROPERTIES = array(
11 'accentColor',
12 'alignItems',
13 'backgroundColor',
14 'backgroundImage',
15 'backgroundSize',
16 'borderBottom',
17 'borderBottomLeftRadius',
18 'borderBottomRightRadius',
19 'borderLeft',
20 'borderRight',
21 'borderTop',
22 'borderTopLeftRadius',
23 'borderTopRightRadius',
24 'bottom',
25 'boxShadow',
26 'color',
27 'customCSS',
28 'display',
29 'flexBasis',
30 'flexDirection',
31 'flexGrow',
32 'flexShrink',
33 'flexWrap',
34 'fontSize',
35 'fontStyle',
36 'fontWeight',
37 'gap',
38 'height',
39 'justifyContent',
40 'lineHeight',
41 'listStyleType',
42 'left',
43 'margin',
44 'marginBottom',
45 'marginLeft',
46 'marginRight',
47 'marginTop',
48 'maxHeight',
49 'maxWidth',
50 'minHeight',
51 'minWidth',
52 'padding',
53 'paddingBottom',
54 'paddingLeft',
55 'paddingRight',
56 'paddingTop',
57 'position',
58 'overflowX',
59 'overflowY',
60 'right',
61 'textAlign',
62 'textTransform',
63 'top',
64 'verticalAlign',
65 'whiteSpace',
66 'width',
67 );
68
69 /**
70 * Creates minimized CSS rulesets.
71 *
72 * Instantiate a fresh builder per CSS build — this object accumulates
73 * declarations in $state, so it must NOT be shared (it was previously a
74 * Singleton, which leaked declarations across attributes and blocks).
75 */
76 class CSSBuilder {
77
78 /**
79 * List of declarations indexed by media type.
80 *
81 * @var array
82 */
83 protected array $state = array(
84 'desktop' => array(),
85 'desktopHover' => array(),
86 'tablet' => array(),
87 'tabletHover' => array(),
88 'mobile' => array(),
89 'mobileHover' => array(),
90 );
91
92 /**
93 * Add all attributes/properties.
94 *
95 * @param array $attributes
96 * @return void
97 */
98 public function add_attributes(array $attributes) {
99 foreach ($attributes as $name => $value) {
100 if (!is_int($name)) {
101 $this->add_property($name, $value);
102 }
103 }
104 }
105
106 /**
107 * Add a new property to the right state.
108 *
109 * @param string $name
110 * @param string|integer|null $value
111 * @return void
112 */
113 protected function add_property(string $name, $value): void {
114 // Fresh Property each call so device/hover never leak from a previous
115 // property (Property::parse() does not reset those fields).
116 $property = new Property();
117 $property->set_property($name);
118 if (in_array($property->name, PROPERTIES) && (null !== $value)) {
119 $state = &$this->state[$this->state_name($property)];
120 $state[$property->name] = $value;
121 }
122 }
123
124 /**
125 * Gets the state key.
126 *
127 * @param Property $property
128 * @return string
129 */
130 private function state_name(Property $property): string {
131 switch ($property->device) {
132 case 'mobile':
133 return $property->hover ? 'mobileHover' : 'mobile';
134 case 'tablet':
135 return $property->hover ? 'tabletHover' : 'tablet';
136 }
137 return $property->hover ? 'desktopHover' : 'desktop';
138 }
139
140 /**
141 * Generate the CSS using the given selector.
142 *
143 * @param string $id
144 * @param array $options
145 * @return string
146 */
147 public function to_css(string $id, array $options = array()): string {
148 $selector = $options['selector'] ?? null;
149
150 // Honor the user-configured responsive breakpoints. Defaults match the
151 // previously hardcoded 1024px / 480px, so default output is unchanged.
152 $breakpoints = Settings::get('compatibility.responsive_breakpoints', array());
153 $tablet_bp = intval($breakpoints['tablet'] ?? 1024);
154 $mobile_bp = intval($breakpoints['mobile'] ?? 480);
155
156 $rulesets = array();
157
158 if (!empty($this->state['desktop'])) {
159 $ruleset = $this->ruleset($id, $this->state['desktop'], $selector);
160 if ($ruleset) {
161 $rulesets[] = $ruleset;
162 }
163 if ($this->state['desktop']['customCSS'] ?? '') {
164 $rulesets[] = $this->state['desktop']['customCSS'];
165 }
166 }
167 if (!empty($this->state['desktopHover'])) {
168 $rulesets[] = $this->ruleset($id, $this->state['desktopHover'], $selector, true);
169 }
170
171 if (!empty($this->state['tablet'])) {
172 $ruleset = $this->ruleset($id, $this->state['tablet'], $selector);
173 if ($ruleset) {
174 $rulesets[] = '@media (max-width:' . $tablet_bp . 'px){' . $ruleset . '}';
175 }
176 if ($this->state['tablet']['customCSS'] ?? '') {
177 $rulesets[] = '@media (max-width:' . $tablet_bp . 'px){' . $this->state['tablet']['customCSS'] . '}';
178 }
179 }
180 if (!empty($this->state['tabletHover'])) {
181 $rulesets[] = '@media (max-width:' . $tablet_bp . 'px){' . $this->ruleset($id, $this->state['tabletHover'], $selector, true) . '}';
182 }
183
184 if (!empty($this->state['mobile'])) {
185 $ruleset = $this->ruleset($id, $this->state['mobile'], $selector);
186 if ($ruleset) {
187 $rulesets[] = '@media (max-width:' . $mobile_bp . 'px){' . $ruleset . '}';
188 }
189 if ($this->state['mobile']['customCSS'] ?? '') {
190 $rulesets[] = '@media (max-width:' . $mobile_bp . 'px){' . $this->state['mobile']['customCSS'] . '}';
191 }
192 }
193 if (!empty($this->state['mobileHover'])) {
194 $rulesets[] = '@media (max-width:' . $mobile_bp . 'px){' . $this->ruleset($id, $this->state['mobileHover'], $selector, true) . '}';
195 }
196
197 return join("\n", $rulesets);
198 }
199
200 /**
201 * Creates a ruleset for the selector from the given style.
202 *
203 * @param string $id
204 * @param array $style
205 * @param string|null $selector
206 * @param bool $hover
207 * @return string
208 */
209 private function ruleset(string $id, array $style, ?string $selector = null, bool $hover = false): string {
210 $array = array();
211
212 foreach ($style as $name => $value) {
213 if ('customCSS' !== $name) {
214 $property = self::kebab_case($name);
215 // if (is_array($value)) {
216 // error_log(print_r($value, true)); // Log the array for debugging
217 // }
218 $array[] = is_array($value) ? "$property:" . json_encode($value) : "$property:$value";
219 }
220 }
221
222
223 $prefix = "#$id";
224 if ($selector) {
225 $prefix .= " $selector";
226 }
227 if ($hover) {
228 $prefix .= ':hover';
229 }
230
231 return (!empty($array)) ? "$prefix{" . join(';', $array) . '}' : '';
232 }
233
234 /**
235 * Converts the JS or underscore name to a CSS property name.
236 *
237 * @param string $name
238 *
239 * @return string
240 */
241 private static function kebab_case(string $name): string {
242 $property = strtolower(preg_replace('/(?<!^)[A-Z]/', '-$0', $name));
243 return str_replace('_', '-', $property);
244 }
245 }
246