PluginProbe ʕ •ᴥ•ʔ
AlphaListing / 4.5.0
AlphaListing v4.5.0
4.5.0 trunk 4.3.4 4.3.5 4.3.6 4.3.7 4.4.0
alphalisting / src / Shortcode / QueryParts / CssLengthCommon.php
alphalisting / src / Shortcode / QueryParts Last commit date
Alphabet.php 2 weeks ago BackToTop.php 2 weeks ago ColumnGap.php 2 weeks ago ColumnWidth.php 2 weeks ago Columns.php 2 weeks ago CssLengthCommon.php 2 weeks ago ExcludePosts.php 2 weeks ago ExcludeTerms.php 2 weeks ago GroupBy.php 2 weeks ago HideEmptyTerms.php 2 weeks ago HideEmpty_Deprecated.php 2 weeks ago InstanceId.php 2 weeks ago ParentPost.php 2 weeks ago ParentTermCommon.php 2 weeks ago ParentTermId.php 2 weeks ago ParentTermSlugOrId.php 2 weeks ago PostType.php 2 weeks ago PostsTerms.php 2 weeks ago SymbolsFirst.php 2 weeks ago Taxonomy.php 2 weeks ago TermsCommon.php 2 weeks ago TermsTerms.php 2 weeks ago
CssLengthCommon.php
198 lines
1 <?php
2 /**
3 * CSS length Query Part common implementation.
4 *
5 * @package alphalisting
6 */
7
8 declare(strict_types=1);
9
10 namespace eslin87\AlphaListing\Shortcode\QueryParts;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 use \eslin87\AlphaListing\Shortcode\Extension;
17
18 /**
19 * Shared implementation for Query Parts whose attribute is a CSS length that is
20 * emitted as a custom property by the `alphalisting_styles` filter.
21 *
22 * Subclasses supply the attribute name, the default length, the CSS custom
23 * property, and where to store the sanitized value.
24 *
25 * @since 4.5.0
26 */
27 abstract class CssLengthCommon extends Extension {
28 /**
29 * The CSS units this attribute accepts.
30 *
31 * @since 4.5.0
32 * @var array<int,string>
33 */
34 protected const ALLOWED_UNITS = array( 'px', 'em', 'rem', '%', 'ch' );
35
36 /**
37 * Upper bound applied to values expressed in pixels.
38 *
39 * @since 4.5.0
40 * @var float
41 */
42 protected const MAX_PIXEL_VALUE = 1200.0;
43
44 /**
45 * Upper bound applied to values expressed in percent or ch units.
46 *
47 * @since 4.5.0
48 * @var float
49 */
50 protected const MAX_PERCENT_VALUE = 100.0;
51
52 /**
53 * The default length to fall back on when sanitization rejects a value.
54 *
55 * @since 4.5.0
56 * @return string The default CSS length.
57 */
58 abstract protected function get_default_length(): string;
59
60 /**
61 * The CSS custom property this attribute is emitted as.
62 *
63 * @since 4.5.0
64 * @return string The custom property name, including the leading `--`.
65 */
66 abstract protected function get_css_property(): string;
67
68 /**
69 * Store the sanitized length on the subclass.
70 *
71 * @since 4.5.0
72 * @param string $length The sanitized CSS length.
73 * @return void
74 */
75 abstract protected function set_length( string $length );
76
77 /**
78 * Retrieve the currently configured length.
79 *
80 * @since 4.5.0
81 * @return string The CSS length.
82 */
83 abstract protected function get_length(): string;
84
85 /**
86 * Sanitize the shortcode attribute.
87 *
88 * @param mixed $value The value of the shortcode attribute.
89 * @param array $attributes The complete set of shortcode attributes.
90 * @return string The sanitized CSS length.
91 */
92 public function sanitize_attribute( $value, array $attributes ) {
93 return $this->sanitize_css_length( $value, $this->get_default_length() );
94 }
95
96 /**
97 * Update the query with this extension's additional configuration.
98 *
99 * @param \eslin87\AlphaListing\Query $query The query.
100 * @param string $display The display/query type.
101 * @param string $key The name of the attribute.
102 * @param mixed $value The shortcode attribute value.
103 * @param array $attributes The complete set of shortcode attributes.
104 * @return mixed The updated query.
105 */
106 public function shortcode_query( $query, string $display, string $key, $value, array $attributes ) {
107 $this->set_length( $this->sanitize_css_length( $value, $this->get_default_length() ) );
108 $this->add_hook( 'filter', 'alphalisting_styles', array( $this, 'return_styles' ), 10, 3 );
109 return $query;
110 }
111
112 /**
113 * Return the stylesheet for this instance.
114 *
115 * @param string $styles The stylesheet.
116 * @param mixed $query The listing query instance passed by the filter.
117 * @param mixed $instance_id The listing instance id passed by the filter.
118 * @return string The stylesheet with this attribute's custom property appended.
119 */
120 public function return_styles( $styles, $query = null, $instance_id = null ): string {
121 return sprintf( '%s %s: %s; ', $styles, $this->get_css_property(), $this->get_length() );
122 }
123
124 /**
125 * Ensure the provided value is a safe CSS length.
126 *
127 * @since 4.5.0
128 * @param mixed $value Potential CSS length value.
129 * @param string $default Default value to use when sanitization fails.
130 * @return string The sanitized CSS length.
131 */
132 protected function sanitize_css_length( $value, string $default ): string {
133 if ( is_string( $value ) ) {
134 $value = trim( $value );
135 } elseif ( is_numeric( $value ) ) {
136 $value = (string) $value;
137 } else {
138 return $default;
139 }
140
141 if ( '' === $value ) {
142 return $default;
143 }
144
145 if ( preg_match( '/^0+(?:\.0+)?$/', $value ) ) {
146 return '0';
147 }
148
149 // The alternation is built from ALLOWED_UNITS so the constant stays the single
150 // source of truth for which units are accepted.
151 $units = implode(
152 '|',
153 array_map(
154 function ( string $unit ): string {
155 return preg_quote( $unit, '/' );
156 },
157 self::ALLOWED_UNITS
158 )
159 );
160
161 if ( ! preg_match( '/^([0-9]+(?:\.[0-9]+)?)\s*(' . $units . ')$/i', $value, $matches ) ) {
162 return $default;
163 }
164
165 $number = (float) $matches[1];
166 $unit = strtolower( $matches[2] );
167
168 if ( $number < 0 ) {
169 return $default;
170 }
171
172 if ( 'px' === $unit ) {
173 $number = min( $number, self::MAX_PIXEL_VALUE );
174 }
175
176 if ( in_array( $unit, array( '%', 'ch' ), true ) ) {
177 $number = min( $number, self::MAX_PERCENT_VALUE );
178 }
179
180 return $this->format_numeric_value( $number ) . $unit;
181 }
182
183 /**
184 * Normalize numeric values before concatenating with units.
185 *
186 * @since 4.5.0
187 * @param float $number The numeric value to format.
188 * @return string The formatted number.
189 */
190 private function format_numeric_value( float $number ): string {
191 if ( floor( $number ) === $number ) {
192 return (string) (int) $number;
193 }
194
195 return rtrim( rtrim( sprintf( '%.4f', $number ), '0' ), '.' );
196 }
197 }
198