PluginProbe
Property Hive / 2.3.0
Property Hive v2.3.0
2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 1.4.61 1.4.62 All 260 releases
propertyhive / includes / ph-formatting-functions.php

ph-formatting-functions.php in Property Hive 2.3.0, at includes/ph-formatting-functions.php

389 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:set WordPress.Security.ValidatedSanitizedInput customSanitizingFunctions[] ph_clean
3 // ph_clean() recursively sanitizes text; presence, shape and unslashing checks remain separate.
4
5 /**
6 * PropertyHive Formatting
7 *
8 * Functions for formatting data.
9 *
10 * @author PropertyHive
11 * @category Core
12 * @package PropertyHive/Functions
13 * @version 1.0.0
14 */
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // Exit if accessed directly
18 }
19
20 /**
21 * Clean stored description HTML while retaining ordinary links and tour frames.
22 */
23 function propertyhive_sanitize_description( $html ) {
24 $allowed = wp_kses_allowed_html( 'post' );
25 $allowed['iframe'] = array_fill_keys( array( 'src', 'title', 'width', 'height', 'allow', 'allowfullscreen', 'frameborder', 'loading', 'referrerpolicy' ), true );
26 $clean = wp_kses( $html, $allowed, wp_allowed_protocols() );
27
28 // KSES attribute callbacks are unavailable on WordPress 5.6. Check only the
29 // normalized iframe tags here, including quoted attributes containing >.
30 return preg_replace_callback( '~<iframe\b(?:[^>"\']++|"[^"]*+"|\'[^\']*+\')*>~i', static function( $match ) {
31 $attributes = wp_kses_hair( substr( $match[0], 7, -1 ), wp_allowed_protocols() );
32 $sources = array();
33 foreach ( $attributes as $name => $attribute ) {
34 if ( strtolower( $name ) === 'src' ) {
35 $sources[] = $attribute['value'];
36 }
37 }
38 if ( empty( $sources ) ) {
39 return $match[0];
40 }
41 if ( count( $sources ) !== 1 ) {
42 return '<iframe>';
43 }
44 $url = html_entity_decode( $sources[0], ENT_QUOTES, 'UTF-8' );
45 $parts = wp_parse_url( $url );
46 if ( ! is_array( $parts ) || empty( $parts['host'] ) || ! preg_match( '~^(?:https?:)?//~i', $url ) || ( isset( $parts['scheme'] ) && ! in_array( strtolower( $parts['scheme'] ), array( 'http', 'https' ), true ) ) ) {
47 return '<iframe>';
48 }
49 if ( ! apply_filters( 'propertyhive_description_iframe_url_allowed', true, $url ) ) {
50 return '<iframe>';
51 }
52 return $match[0];
53 }, $clean );
54 }
55
56 /**
57 * Translate built-in rent frequency labels, preserving custom labels.
58 *
59 * @param string $frequency Stored frequency.
60 * @return string Display label.
61 */
62 function propertyhive_get_rent_frequency_label( $frequency ) {
63 $labels = array(
64 'pd' => __( 'pd', 'propertyhive' ),
65 'pppw' => __( 'pppw', 'propertyhive' ),
66 'pw' => __( 'pw', 'propertyhive' ),
67 'pcm' => __( 'pcm', 'propertyhive' ),
68 'pq' => __( 'pq', 'propertyhive' ),
69 'pa' => __( 'pa', 'propertyhive' ),
70 );
71 return isset( $labels[$frequency] ) ? $labels[$frequency] : $frequency;
72 }
73
74 /**
75 * Translate built-in CRM statuses without passing dynamic text to gettext.
76 *
77 * @param string $status Stored status.
78 * @return string Display label.
79 */
80 function propertyhive_get_status_label( $status ) {
81 $labels = array(
82 'pending' => __( 'Pending', 'propertyhive' ),
83 'confirmed' => __( 'Confirmed', 'propertyhive' ),
84 'unconfirmed' => __( 'Unconfirmed', 'propertyhive' ),
85 'carried_out' => __( 'Carried Out', 'propertyhive' ),
86 'awaiting_feedback' => __( 'Awaiting Feedback', 'propertyhive' ),
87 'feedback_passed_on' => __( 'Feedback Passed On', 'propertyhive' ),
88 'feedback_not_passed_on' => __( 'Feedback Not Passed On', 'propertyhive' ),
89 'cancelled' => __( 'Cancelled', 'propertyhive' ),
90 'no_show' => __( 'No Show', 'propertyhive' ),
91 'offer_made' => __( 'Offer Made', 'propertyhive' ),
92 'accepted' => __( 'Accepted', 'propertyhive' ),
93 'declined' => __( 'Declined', 'propertyhive' ),
94 'current' => __( 'Current', 'propertyhive' ),
95 'exchanged' => __( 'Exchanged', 'propertyhive' ),
96 'completed' => __( 'Completed', 'propertyhive' ),
97 'fallen_through' => __( 'Fallen Through', 'propertyhive' ),
98 'interested' => __( 'Interested', 'propertyhive' ),
99 'not_interested' => __( 'Not Interested', 'propertyhive' ),
100 );
101 return isset( $labels[$status] ) ? $labels[$status] : ucwords( str_replace( '_', ' ', $status ) );
102 }
103
104 /**
105 * Clean variables using sanitize_text_field.
106 * @param string|array $var
107 * @return string|array
108 */
109 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_clean; the established callable name is part of the plugin/extension API and must remain stable.
110 function ph_clean( $var ) {
111
112 if ( is_array( $var ) ) {
113 return array_map( 'ph_clean', $var );
114 } else {
115 return is_scalar( $var ) ? sanitize_text_field( $var ) : $var;
116 }
117 }
118
119 /**
120 * Strip none numeric or comma chars from phone numbers
121 * @param string
122 * @return string
123 */
124
125 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_clean_telephone_number; the established callable name is part of the plugin/extension API and must remain stable.
126 function ph_clean_telephone_number( $var ) {
127
128 return preg_replace( "/[^0-9,]/", "", $var );
129 }
130
131 /**
132 * Format monetary number value with decimal and thousands separators for display in a form field
133 * @param string
134 * @return string
135 */
136 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_display_price_field; the established callable name is part of the plugin/extension API and must remain stable.
137 function ph_display_price_field( $var, $use_separator_setting = false )
138 {
139 $float_var = (float)$var;
140
141 // If stored value isn't a valid number with decimals, display as it's stored
142 if ( $float_var !== floatval(0) )
143 {
144 // If there are decimals on the number, display them. If not, display none
145 $decimals = $float_var == intval($var) ? 0 : 2;
146
147 if ( !$use_separator_setting )
148 {
149 $decimal_separator = '.';
150 $thousands_separator = ',';
151 }
152 else
153 {
154 // Get custom thousands and decimal and separators, if set, when not displaying in an input
155 $thousands_separator = get_option('propertyhive_price_thousand_separator', ',');
156 $decimal_separator = get_option('propertyhive_price_decimal_separator', '.');
157 }
158
159 $var = number_format( $float_var, $decimals, $decimal_separator, $thousands_separator );
160 }
161
162 return (string)$var;
163 }
164
165 if ( ! function_exists( 'ph_rgb_from_hex' ) ) {
166
167 /**
168 * Hex darker/lighter/contrast functions for colours.
169 *
170 * @param mixed $color
171 * @return string
172 */
173 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_rgb_from_hex; the established callable name is part of the plugin/extension API and must remain stable.
174 function ph_rgb_from_hex( $color ) {
175 $color = str_replace( '#', '', $color );
176 // Convert shorthand colors to full format, e.g. "FFF" -> "FFFFFF"
177 $color = preg_replace( '~^(.)(.)(.)$~', '$1$1$2$2$3$3', $color );
178
179 $rgb = array();
180 $rgb['R'] = hexdec( $color[0] . $color[1] );
181 $rgb['G'] = hexdec( $color[2] . $color[3] );
182 $rgb['B'] = hexdec( $color[4] . $color[5] );
183
184 return $rgb;
185 }
186 }
187
188 if ( ! function_exists( 'ph_hex_darker' ) ) {
189
190 /**
191 * Hex darker/lighter/contrast functions for colours.
192 *
193 * @param mixed $color
194 * @param int $factor (default: 30)
195 * @return string
196 */
197 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_hex_darker; the established callable name is part of the plugin/extension API and must remain stable.
198 function ph_hex_darker( $color, $factor = 30 ) {
199 $base = ph_rgb_from_hex( $color );
200 $color = '#';
201
202 foreach ( $base as $k => $v ) {
203 $amount = $v / 100;
204 $amount = round( $amount * $factor );
205 $new_decimal = $v - $amount;
206
207 $new_hex_component = dechex( $new_decimal );
208 if ( strlen( $new_hex_component ) < 2 ) {
209 $new_hex_component = "0" . $new_hex_component;
210 }
211 $color .= $new_hex_component;
212 }
213
214 return $color;
215 }
216 }
217
218 if ( ! function_exists( 'ph_hex_lighter' ) ) {
219
220 /**
221 * Hex darker/lighter/contrast functions for colours.
222 *
223 * @param mixed $color
224 * @param int $factor (default: 30)
225 * @return string
226 */
227 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_hex_lighter; the established callable name is part of the plugin/extension API and must remain stable.
228 function ph_hex_lighter( $color, $factor = 30 ) {
229 $base = ph_rgb_from_hex( $color );
230 $color = '#';
231
232 foreach ( $base as $k => $v ) {
233 $amount = 255 - $v;
234 $amount = $amount / 100;
235 $amount = round( $amount * $factor );
236 $new_decimal = $v + $amount;
237
238 $new_hex_component = dechex( $new_decimal );
239 if ( strlen( $new_hex_component ) < 2 ) {
240 $new_hex_component = "0" . $new_hex_component;
241 }
242 $color .= $new_hex_component;
243 }
244
245 return $color;
246 }
247 }
248
249 if ( ! function_exists( 'ph_light_or_dark' ) ) {
250
251 /**
252 * Detect if we should use a light or dark colour on a background colour.
253 *
254 * @param mixed $color
255 * @param string $dark (default: '#000000')
256 * @param string $light (default: '#FFFFFF')
257 * @return string
258 */
259 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_light_or_dark; the established callable name is part of the plugin/extension API and must remain stable.
260 function ph_light_or_dark( $color, $dark = '#000000', $light = '#FFFFFF' ) {
261
262 $hex = str_replace( '#', '', $color );
263
264 $c_r = hexdec( substr( $hex, 0, 2 ) );
265 $c_g = hexdec( substr( $hex, 2, 2 ) );
266 $c_b = hexdec( substr( $hex, 4, 2 ) );
267
268 $brightness = ( ( $c_r * 299 ) + ( $c_g * 587 ) + ( $c_b * 114 ) ) / 1000;
269
270 return $brightness > 155 ? $dark : $light;
271 }
272 }
273
274 if ( ! function_exists( 'ph_format_hex' ) ) {
275
276 /**
277 * Format string as hex.
278 *
279 * @param string $hex
280 * @return string
281 */
282 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_format_hex; the established callable name is part of the plugin/extension API and must remain stable.
283 function ph_format_hex( $hex ) {
284
285 $hex = trim( str_replace( '#', '', $hex ) );
286
287 if ( strlen( $hex ) == 3 ) {
288 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
289 }
290
291 return $hex ? '#' . $hex : null;
292 }
293 }
294
295 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_nl2br; the established callable name is part of the plugin/extension API and must remain stable.
296 function ph_nl2br($str)
297 {
298 // Match any <ul> or <ol> with their content
299 $pattern = '/(<ul[^>]*>.*?<\/ul>|<ol[^>]*>.*?<\/ol>)/is';
300 $parts = preg_split($pattern, $str, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
301
302 foreach ($parts as &$part) {
303 // If the part is not a list, apply nl2br
304 if (!preg_match($pattern, $part)) {
305 $part = nl2br($part);
306 }
307 }
308
309 // Reassemble the string
310 return implode('', $parts);
311 }
312
313 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Legacy public global helper ph_split_address_into_fields; the established callable name is part of the plugin/extension API and must remain stable.
314 function ph_split_address_into_fields( $address )
315 {
316 $fields = [
317 'address_name_number' => '',
318 'address_street' => '',
319 'address_two' => '',
320 'address_three' => '',
321 'address_four' => '',
322 'address_postcode' => '',
323 'address_country' => ''
324 ];
325
326 // Replace newlines with commas and remove consecutive commas
327 $address = preg_replace('/\s*,\s*/', ', ', $address);
328 $address = preg_replace('/\s*\n\s*/', ', ', $address);
329 $address = preg_replace('/,+/', ',', $address);
330
331 // Explode the address by comma
332 $parts = explode(',', $address);
333
334 // Trim whitespace from each part
335 $parts = array_map('trim', $parts);
336
337 // Remove empty parts
338 $parts = array_filter($parts);
339
340 // Check the first part for building number and street
341 if (isset($parts[0])) {
342 if (preg_match('/^(\d+)\s+(.*)$/', $parts[0], $matches)) {
343 $fields['address_name_number'] = $matches[1];
344 $fields['address_street'] = $matches[2];
345 } else {
346 $fields['address_street'] = $parts[0];
347 }
348 array_shift($parts);
349 }
350
351 // Detect postcode in the remaining parts
352 foreach ($parts as $index => $part) {
353 if (preg_match('/[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}/i', $part, $postcodeMatch)) {
354 $fields['address_postcode'] = $postcodeMatch[0];
355 // Split the part containing the postcode
356 $remainingPart = preg_replace('/[A-Z]{1,2}\d[A-Z\d]? \d[A-Z]{2}/i', '', $part);
357 if (!empty(trim($remainingPart))) {
358 array_splice($parts, $index, 1, trim($remainingPart));
359 } else {
360 unset($parts[$index]);
361 }
362 // Check if the next part is the country
363 if (isset($parts[$index + 1])) {
364 $fields['address_country'] = $parts[$index + 1];
365 unset($parts[$index + 1]);
366 }
367 break;
368 }
369 }
370
371 // Assign remaining parts to Address Line 2, Town/City, and County
372 $remainingParts = array_values($parts);
373 if (isset($remainingParts[0])) $fields['address_two'] = $remainingParts[0];
374 if (isset($remainingParts[1])) $fields['address_three'] = $remainingParts[1];
375 if (isset($remainingParts[2])) $fields['address_four'] = $remainingParts[2];
376
377 return $fields;
378 }
379 /**
380 * Resolve translated built-in or extension-provided department labels.
381 *
382 * @param string $department Department key.
383 * @return string Department label.
384 */
385 function propertyhive_get_department_label( $department ) {
386 $labels = ph_get_departments();
387 return isset( $labels[$department] ) ? $labels[$department] : ucwords( str_replace( '-', ' ', $department ) );
388 }
389