PluginProbe
Lead Generation Contact Widget & AI Chatbot: Chat Button, Phone Call, Telegram, Email – SiteLeads / trunk
Lead Generation Contact Widget & AI Chatbot: Chat Button, Phone Call, Telegram, Email – SiteLeads vtrunk
1.2.1 1.2.0 1.1.7 trunk 1.0.0 1.1.0 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6
siteleads / lib / Utils.php

Utils.php in Lead Generation Contact Widget & AI Chatbot: Chat Button, Phone Call, Telegram, Email – SiteLeads trunk, at lib/Utils.php

410 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SiteLeads;
4
5 use SiteLeads\Admin\Admin;
6 use SiteLeads\Core\AssetsRegistry;
7 use SiteLeads\Core\Flags;
8
9 class Utils {
10
11
12 public static function getFilePath( $path ) {
13 return SITELEADS_ROOT_DIR . "$path";
14 }
15
16 public static function getUrl( $path ) {
17 return SITELEADS_ROOT_URL . "/$path";
18 }
19
20
21 public static function getAssetName( $name ) {
22 return AssetsRegistry::getAssetHandle( $name );
23 }
24
25 public static function isSiteLeadsAdminPage() {
26 global $pagenow;
27
28 if ( substr( $pagenow, 0, - 4 ) === 'admin' ) {
29 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
30 $page = isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false;
31
32 $available_pages = apply_filters(
33 'siteleads_admin_pages',
34 array(
35 'siteleads',
36 'siteleads-analytics',
37 'siteleads-messages',
38 'siteleads-leads',
39 'siteleads-my-account',
40 'siteleads-upgrade',
41 )
42 );
43
44 return in_array( $page, $available_pages, true );
45 }
46
47 return false;
48 }
49
50 public static function getIconAsset( $filename ) {
51
52 $svg_path = SITELEADS_ROOT_DIR . 'assets/icons/' . $filename;
53 $ret = '<!-- SVG not found -->';
54 if ( file_exists( $svg_path ) ) {
55 $ret = file_get_contents( $svg_path );
56 }
57
58 return $ret;
59 }
60
61 public static function printIconAsset( $filename ) {
62 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We trust our own assets
63 echo self::getIconAsset( $filename );
64 }
65
66 public static function getAssetFile( $filename ) {
67
68 $file_path = SITELEADS_ROOT_DIR . 'assets/' . $filename;
69
70 if ( file_exists( $file_path ) ) {
71 return file_get_contents( $file_path );
72 } else {
73 return '<!-- FILE not found -->';
74 }
75 }
76
77 /**
78 * Returns the widget ID if in preview mode, null if preview mode but no widget ID, false if not in preview mode
79 *
80 * @return string|null|false
81 */
82 public static function getPreviewedWidgetId() {
83
84 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we don't verify for nonce, as this is only used to detect if we're in preview mode, and the presence of a valid widget ID is not a security concern
85 $preview = isset( $_GET['siteleads-preview'] ) ? filter_var( sanitize_text_field( wp_unslash( $_GET['siteleads-preview'] ) ), FILTER_VALIDATE_BOOLEAN ) : false;
86
87 if ( $preview ) {
88 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we don't verify for nonce, as this is only used to detect if we're in preview mode, and the presence of a valid widget ID is not a security concern
89 $widgetId = isset( $_GET['widgetId'] ) ? sanitize_text_field( wp_unslash( $_GET['widgetId'] ) ) : null;
90
91 if ( $widgetId ) {
92 return $widgetId;
93 }
94
95 return null;
96 }
97
98 return false;
99 }
100
101 /**
102 * Returns the widget ID if in live preview mode, null if preview mode but no widget ID, false if not in preview mode
103 *
104 * @return string|null|false
105 */
106 public static function getLivePreviewedWidgetId() {
107
108 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we don't verify for nonce, as this is only used to detect if we're in live preview mode, and the presence of a valid widget ID is not a security concern
109 $livePreview = isset( $_GET['siteleads-live-preview'] ) ? filter_var( sanitize_text_field( wp_unslash( $_GET['siteleads-live-preview'] ) ), FILTER_VALIDATE_BOOLEAN ) : false;
110
111 if ( $livePreview ) {
112 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we don't verify for nonce, as this is only used to detect if we're in live preview mode, and the presence of a valid widget ID is not a security concern
113 $widgetId = isset( $_GET['widgetId'] ) ? sanitize_text_field( wp_unslash( $_GET['widgetId'] ) ) : null;
114
115 if ( $widgetId ) {
116 return $widgetId;
117 }
118
119 return null;
120 }
121
122 return false;
123 }
124
125 public static function isInsideBoundingBox( $bbox, $coordinates ) {
126
127 try {
128 if ( $bbox && $coordinates && is_array( $bbox ) && is_array( $coordinates ) && isset( $coordinates['lat'] ) && isset( $coordinates['long'] ) ) {
129 list( $min_latitude, $max_latitude, $min_longitude, $max_longitude ) = $bbox;
130 $lat = $coordinates['lat'];
131 $long = $coordinates['long'];
132
133 return $lat >= $min_latitude && $lat <= $max_latitude && $long >= $min_longitude && $long <= $max_longitude;
134 }
135 } catch ( \Exception $e ) {
136 return false;
137 }
138
139 return false;
140 }
141
142 public static function isBlogPage() {
143 global $wp_query;
144 $blogPageID = intval( get_option( 'page_for_posts' ) );
145 $currentPageID = isset( $wp_query->query_vars['page_id'] ) ? $wp_query->query_vars['page_id'] : false;
146 if ( isset( $wp_query->queried_object_id ) && $currentPageID == false ) {
147 $currentPageID = $wp_query->queried_object_id;
148 }
149
150 if ( $currentPageID == $blogPageID ) {
151 return $blogPageID;
152 }
153
154 return false;
155 }
156
157 public static function getShopPageId() {
158 if ( ! function_exists( 'wc_get_page_id' ) ) {
159 return null;
160 }
161 $shopPageID = wc_get_page_id( 'shop' );
162
163 return $shopPageID;
164 }
165
166
167 public static function normalizeTimezone( $timezone ) {
168 if ( $timezone === 'website' ) {
169 return wp_timezone();
170 }
171
172 return new \DateTimeZone( $timezone );
173 }
174
175 private static function convertTimeToSeconds( $time ) {
176 $parts = explode( ':', $time );
177 if ( count( $parts ) === 2 ) {
178 $hours = intval( $parts[0] );
179 $minutes = intval( $parts[1] );
180
181 return $hours * 3600 + $minutes * 60;
182 }
183
184 return false;
185 }
186
187 /**
188 * Check if a given timestamp is within a date+time range in a specific timezone.
189 *
190 * @param string $timezone "label#offsetHours" OR "website"
191 * @param string|null $startDate "YYYY-MM-DD"
192 * @param string|null $endDate "YYYY-MM-DD"
193 * @param string|null $startTime "HH:MM"
194 * @param string|null $endTime "HH:MM"
195 * @param int|null $timestamp UTC timestamp (defaults to now)
196 *
197 * @return bool
198 */
199 public static function isBetweenDatesWithTimezone(
200 $timezone,
201 $startDate,
202 $startTime,
203 $endDate,
204 $endTime
205 ) {
206 if ( empty( $startDate ) || empty( $startTime ) ) {
207 return true;
208 }
209
210 $tz = self::normalizeTimezone( $timezone );
211 $current = new \DateTime( 'now', $tz );
212
213 $startDateTime = new \DateTime( $startDate . ' ' . $startTime, $tz );
214 $endDateTime = new \DateTime( ( $endDate ?? $startDate ) . ' ' . ( $endTime ?? $startTime ), $tz );
215
216 return $current >= $startDateTime && $current < $endDateTime;
217 }
218
219 /**
220 * Check if the current time (based on timezone) is within a start–end range.
221 *
222 * @param string $timezone "Europe\Bucharest" or "website"
223 * @param string $startTime e.g. "09:00"
224 * @param string $endTime e.g. "17:00"
225 *
226 * @return bool
227 */
228 public static function isTimeWithinRange( $timezone, $startTime, $endTime ) {
229
230 $tz = self::normalizeTimezone( $timezone );
231 $dt = new \DateTime( 'now', $tz );
232
233 // get number of secconds since midnight in $dt's timezone
234 $currentSeconds = (int) $dt->format( 'H' ) * 3600 + (int) $dt->format( 'i' ) * 60 + (int) $dt->format( 's' );
235
236 $startTimeSeconds = self::convertTimeToSeconds( $startTime );
237 $endTimeSeconds = self::convertTimeToSeconds( $endTime );
238
239 if ( $startTimeSeconds === false || $endTimeSeconds === false ) {
240 return true; // if time format is invalid, ignore time targeting
241 }
242
243 return $currentSeconds >= $startTimeSeconds && $currentSeconds < $endTimeSeconds;
244 }
245
246 /**
247 * Get the day of the week for a given UTC timestamp and timezone.
248 *
249 * @param string $timezone "Europe\Bucharest" OR "website"
250 *
251 * @return string Day of week in lowercase, e.g. "monday"
252 */
253 public static function getDayOfWeekFromTimezone( $timezone = 'website' ) {
254 $dt = new \DateTime( 'now', self::normalizeTimezone( $timezone ) );
255
256 return strtolower( $dt->format( 'l' ) );
257 }
258
259
260
261 private static function compose_site_url( $path, $args = array() ) {
262 $root_url = untrailingslashit( SITELEADS_WEBSITE_URL );
263 $path = ltrim( $path, '/' );
264
265 $default_args = array(
266 'sl_key' => get_option( 'siteleads_ai_api_key', '' ),
267 'utm_theme' => get_template(),
268 'utm_childtheme' => get_stylesheet(),
269 'utm_install_source' => Flags::get( 'start_source', 'other' ),
270 'utm_activated_on' => Flags::get( 'activation_time', '' ),
271 'utm_pro_activated_on' => Flags::get( 'pro_activation_time', '' ),
272 );
273
274 $args = array_merge( $default_args, $args );
275
276 $args = array_map( 'urlencode', $args );
277 $args = array_filter( $args );
278
279 return add_query_arg( $args, "{$root_url}/{$path}" );
280 }
281
282 public static function getWebsiteURL( $target = 'homepage' ) {
283
284 $args = apply_filters(
285 'siteleads_website_root_url_args',
286 array()
287 );
288
289 switch ( $target ) {
290 case 'upgrade':
291 return self::compose_site_url( '/#pricing', $args );
292 case 'documentation':
293 return self::compose_site_url( '/docs', $args );
294 case 'support':
295 case 'contact':
296 return self::compose_site_url( '/contact', $args );
297 break;
298 default:
299 return self::compose_site_url( '/', $args );
300 }
301 }
302
303 public static function convertStyleArrayToString( $arr ) {
304 $vars = array();
305
306 foreach ( $arr as $key => $value ) {
307 if ( $value === null ) {
308 continue;
309 }
310 $vars[] = "$key: $value";
311 }
312
313 return implode( ';', $vars );
314 }
315
316 public static function hex2rgb( $hex ) {
317 $hex = str_replace( '#', '', $hex );
318
319 if ( strlen( $hex ) == 3 ) {
320 $r = hexdec( substr( $hex, 0, 1 ) . substr( $hex, 0, 1 ) );
321 $g = hexdec( substr( $hex, 1, 1 ) . substr( $hex, 1, 1 ) );
322 $b = hexdec( substr( $hex, 2, 1 ) . substr( $hex, 2, 1 ) );
323 } else {
324 $r = hexdec( substr( $hex, 0, 2 ) );
325 $g = hexdec( substr( $hex, 2, 2 ) );
326 $b = hexdec( substr( $hex, 4, 2 ) );
327 }
328
329 return array( $r, $g, $b );
330 }
331
332 public static function fontSettingsToStyle( $settings ) {
333 if ( ! $settings || ! count( $settings ) ) {
334 return '';
335 }
336
337 $raw_settings = array(
338 'color' => isset( $settings['color'] ) ? $settings['color'] : null,
339 'font-family' => isset( $settings['family'] ) ? $settings['family'] : null,
340 'font-weight' => isset( $settings['weight'] ) ? $settings['weight'] : null,
341 'font-size' => isset( $settings['size'] ) ? $settings['size'] . 'px' : null,
342 'text-align' => isset( $settings['align'] ) ? $settings['align'] : null,
343 );
344
345 $style = array_filter( $raw_settings );
346
347 return Utils::convertStyleArrayToString( $style );
348 }
349
350 public static function buildGoogleFontsURL( $fonts ) {
351 $href = 'https://fonts.googleapis.com/css';
352 $query_args = array(
353 'display' => 'swap',
354 );
355
356 $family_query_parts = array();
357 foreach ( $fonts as $family => $variants ) {
358
359 if ( empty( $variants ) ) {
360 continue;
361 }
362
363 $family_query_part = $family;
364 if ( count( $variants ) > 0 ) {
365 $family_query_part .= ':' . implode( ',', $variants );
366 }
367 $family_query_parts[] = $family_query_part;
368 }
369
370 $query_args['family'] = urlencode( implode( '|', $family_query_parts ) );
371
372 return add_query_arg( $query_args, $href );
373 }
374
375 public static function base64EncodeJWT( $str ) {
376 return str_replace( array( '+', '/', '=' ), array( '-', '_', '' ), base64_encode( $str ) );
377 }
378
379 public static function generateWidgetJWT() {
380 $private_key = Admin::getApiKey();
381 $private_key_identifier = Admin::getApiKeyIdentifier();
382
383 $header = json_encode(
384 array(
385 'alg' => 'HS256',
386 'typ' => 'JWT',
387 )
388 );
389
390 $time = time();
391
392 $payload = json_encode(
393 array(
394 'site-url' => site_url(),
395 'api-key-identifier' => $private_key_identifier,
396 'exp' => $time + 300,
397 'iat' => $time,
398 )
399 );
400
401 $base64UrlHeader = Utils::base64EncodeJWT( $header );
402 $base64UrlPayload = Utils::base64EncodeJWT( $payload );
403
404 $signature = hash_hmac( 'sha256', $base64UrlHeader . '.' . $base64UrlPayload, $private_key, true );
405 $base64UrlSignature = Utils::base64EncodeJWT( $signature );
406
407 return $base64UrlHeader . '.' . $base64UrlPayload . '.' . $base64UrlSignature;
408 }
409 }
410