<?php
namespace SiteLeads;
use SiteLeads\Admin\Admin;
use SiteLeads\Core\AssetsRegistry;
use SiteLeads\Core\Flags;
class Utils {
public static function getFilePath( $path ) {
return SITELEADS_ROOT_DIR . "$path";
}
public static function getUrl( $path ) {
return SITELEADS_ROOT_URL . "/$path";
}
public static function getAssetName( $name ) {
return AssetsRegistry::getAssetHandle( $name );
}
public static function isSiteLeadsAdminPage() {
global $pagenow;
if ( substr( $pagenow, 0, - 4 ) === 'admin' ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$page = isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false;
$available_pages = apply_filters(
'siteleads_admin_pages',
array(
'siteleads',
'siteleads-analytics',
'siteleads-messages',
'siteleads-leads',
'siteleads-my-account',
'siteleads-upgrade',
)
);
return in_array( $page, $available_pages, true );
}
return false;
}
public static function getIconAsset( $filename ) {
$svg_path = SITELEADS_ROOT_DIR . 'assets/icons/' . $filename;
$ret = '<!-- SVG not found -->';
if ( file_exists( $svg_path ) ) {
$ret = file_get_contents( $svg_path );
}
return $ret;
}
public static function printIconAsset( $filename ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We trust our own assets
echo self::getIconAsset( $filename );
}
public static function getAssetFile( $filename ) {
$file_path = SITELEADS_ROOT_DIR . 'assets/' . $filename;
if ( file_exists( $file_path ) ) {
return file_get_contents( $file_path );
} else {
return '<!-- FILE not found -->';
}
}
/**
* Returns the widget ID if in preview mode, null if preview mode but no widget ID, false if not in preview mode
*
* @return string|null|false
*/
public static function getPreviewedWidgetId() {
// 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
$preview = isset( $_GET['siteleads-preview'] ) ? filter_var( sanitize_text_field( wp_unslash( $_GET['siteleads-preview'] ) ), FILTER_VALIDATE_BOOLEAN ) : false;
if ( $preview ) {
// 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
$widgetId = isset( $_GET['widgetId'] ) ? sanitize_text_field( wp_unslash( $_GET['widgetId'] ) ) : null;
if ( $widgetId ) {
return $widgetId;
}
return null;
}
return false;
}
/**
* Returns the widget ID if in live preview mode, null if preview mode but no widget ID, false if not in preview mode
*
* @return string|null|false
*/
public static function getLivePreviewedWidgetId() {
// 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
$livePreview = isset( $_GET['siteleads-live-preview'] ) ? filter_var( sanitize_text_field( wp_unslash( $_GET['siteleads-live-preview'] ) ), FILTER_VALIDATE_BOOLEAN ) : false;
if ( $livePreview ) {
// 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
$widgetId = isset( $_GET['widgetId'] ) ? sanitize_text_field( wp_unslash( $_GET['widgetId'] ) ) : null;
if ( $widgetId ) {
return $widgetId;
}
return null;
}
return false;
}
public static function isInsideBoundingBox( $bbox, $coordinates ) {
try {
if ( $bbox && $coordinates && is_array( $bbox ) && is_array( $coordinates ) && isset( $coordinates['lat'] ) && isset( $coordinates['long'] ) ) {
list( $min_latitude, $max_latitude, $min_longitude, $max_longitude ) = $bbox;
$lat = $coordinates['lat'];
$long = $coordinates['long'];
return $lat >= $min_latitude && $lat <= $max_latitude && $long >= $min_longitude && $long <= $max_longitude;
}
} catch ( \Exception $e ) {
return false;
}
return false;
}
public static function isBlogPage() {
global $wp_query;
$blogPageID = intval( get_option( 'page_for_posts' ) );
$currentPageID = isset( $wp_query->query_vars['page_id'] ) ? $wp_query->query_vars['page_id'] : false;
if ( isset( $wp_query->queried_object_id ) && $currentPageID == false ) {
$currentPageID = $wp_query->queried_object_id;
}
if ( $currentPageID == $blogPageID ) {
return $blogPageID;
}
return false;
}
public static function getShopPageId() {
if ( ! function_exists( 'wc_get_page_id' ) ) {
return null;
}
$shopPageID = wc_get_page_id( 'shop' );
return $shopPageID;
}
public static function normalizeTimezone( $timezone ) {
if ( $timezone === 'website' ) {
return wp_timezone();
}
return new \DateTimeZone( $timezone );
}
private static function convertTimeToSeconds( $time ) {
$parts = explode( ':', $time );
if ( count( $parts ) === 2 ) {
$hours = intval( $parts[0] );
$minutes = intval( $parts[1] );
return $hours * 3600 + $minutes * 60;
}
return false;
}
/**
* Check if a given timestamp is within a date+time range in a specific timezone.
*
* @param string $timezone "label#offsetHours" OR "website"
* @param string|null $startDate "YYYY-MM-DD"
* @param string|null $endDate "YYYY-MM-DD"
* @param string|null $startTime "HH:MM"
* @param string|null $endTime "HH:MM"
* @param int|null $timestamp UTC timestamp (defaults to now)
*
* @return bool
*/
public static function isBetweenDatesWithTimezone(
$timezone,
$startDate,
$startTime,
$endDate,
$endTime
) {
if ( empty( $startDate ) || empty( $startTime ) ) {
return true;
}
$tz = self::normalizeTimezone( $timezone );
$current = new \DateTime( 'now', $tz );
$startDateTime = new \DateTime( $startDate . ' ' . $startTime, $tz );
$endDateTime = new \DateTime( ( $endDate ?? $startDate ) . ' ' . ( $endTime ?? $startTime ), $tz );
return $current >= $startDateTime && $current < $endDateTime;
}
/**
* Check if the current time (based on timezone) is within a start–end range.
*
* @param string $timezone "Europe\Bucharest" or "website"
* @param string $startTime e.g. "09:00"
* @param string $endTime e.g. "17:00"
*
* @return bool
*/
public static function isTimeWithinRange( $timezone, $startTime, $endTime ) {
$tz = self::normalizeTimezone( $timezone );
$dt = new \DateTime( 'now', $tz );
// get number of secconds since midnight in $dt's timezone
$currentSeconds = (int) $dt->format( 'H' ) * 3600 + (int) $dt->format( 'i' ) * 60 + (int) $dt->format( 's' );
$startTimeSeconds = self::convertTimeToSeconds( $startTime );
$endTimeSeconds = self::convertTimeToSeconds( $endTime );
if ( $startTimeSeconds === false || $endTimeSeconds === false ) {
return true; // if time format is invalid, ignore time targeting
}
return $currentSeconds >= $startTimeSeconds && $currentSeconds < $endTimeSeconds;
}
/**
* Get the day of the week for a given UTC timestamp and timezone.
*
* @param string $timezone "Europe\Bucharest" OR "website"
*
* @return string Day of week in lowercase, e.g. "monday"
*/
public static function getDayOfWeekFromTimezone( $timezone = 'website' ) {
$dt = new \DateTime( 'now', self::normalizeTimezone( $timezone ) );
return strtolower( $dt->format( 'l' ) );
}
private static function compose_site_url( $path, $args = array() ) {
$root_url = untrailingslashit( SITELEADS_WEBSITE_URL );
$path = ltrim( $path, '/' );
$default_args = array(
'sl_key' => get_option( 'siteleads_ai_api_key', '' ),
'utm_theme' => get_template(),
'utm_childtheme' => get_stylesheet(),
'utm_install_source' => Flags::get( 'start_source', 'other' ),
'utm_activated_on' => Flags::get( 'activation_time', '' ),
'utm_pro_activated_on' => Flags::get( 'pro_activation_time', '' ),
);
$args = array_merge( $default_args, $args );
$args = array_map( 'urlencode', $args );
$args = array_filter( $args );
return add_query_arg( $args, "{$root_url}/{$path}" );
}
public static function getWebsiteURL( $target = 'homepage' ) {
$args = apply_filters(
'siteleads_website_root_url_args',
array()
);
switch ( $target ) {
case 'upgrade':
return self::compose_site_url( '/#pricing', $args );
case 'documentation':
return self::compose_site_url( '/docs', $args );
case 'support':
case 'contact':
return self::compose_site_url( '/contact', $args );
break;
default:
return self::compose_site_url( '/', $args );
}
}
public static function convertStyleArrayToString( $arr ) {
$vars = array();
foreach ( $arr as $key => $value ) {
if ( $value === null ) {
continue;
}
$vars[] = "$key: $value";
}
return implode( ';', $vars );
}
public static function hex2rgb( $hex ) {
$hex = str_replace( '#', '', $hex );
if ( strlen( $hex ) == 3 ) {
$r = hexdec( substr( $hex, 0, 1 ) . substr( $hex, 0, 1 ) );
$g = hexdec( substr( $hex, 1, 1 ) . substr( $hex, 1, 1 ) );
$b = hexdec( substr( $hex, 2, 1 ) . substr( $hex, 2, 1 ) );
} else {
$r = hexdec( substr( $hex, 0, 2 ) );
$g = hexdec( substr( $hex, 2, 2 ) );
$b = hexdec( substr( $hex, 4, 2 ) );
}
return array( $r, $g, $b );
}
public static function fontSettingsToStyle( $settings ) {
if ( ! $settings || ! count( $settings ) ) {
return '';
}
$raw_settings = array(
'color' => isset( $settings['color'] ) ? $settings['color'] : null,
'font-family' => isset( $settings['family'] ) ? $settings['family'] : null,
'font-weight' => isset( $settings['weight'] ) ? $settings['weight'] : null,
'font-size' => isset( $settings['size'] ) ? $settings['size'] . 'px' : null,
'text-align' => isset( $settings['align'] ) ? $settings['align'] : null,
);
$style = array_filter( $raw_settings );
return Utils::convertStyleArrayToString( $style );
}
public static function buildGoogleFontsURL( $fonts ) {
$href = 'https://fonts.googleapis.com/css';
$query_args = array(
'display' => 'swap',
);
$family_query_parts = array();
foreach ( $fonts as $family => $variants ) {
if ( empty( $variants ) ) {
continue;
}
$family_query_part = $family;
if ( count( $variants ) > 0 ) {
$family_query_part .= ':' . implode( ',', $variants );
}
$family_query_parts[] = $family_query_part;
}
$query_args['family'] = urlencode( implode( '|', $family_query_parts ) );
return add_query_arg( $query_args, $href );
}
public static function base64EncodeJWT( $str ) {
return str_replace( array( '+', '/', '=' ), array( '-', '_', '' ), base64_encode( $str ) );
}
public static function generateWidgetJWT() {
$private_key = Admin::getApiKey();
$private_key_identifier = Admin::getApiKeyIdentifier();
$header = json_encode(
array(
'alg' => 'HS256',
'typ' => 'JWT',
)
);
$time = time();
$payload = json_encode(
array(
'site-url' => site_url(),
'api-key-identifier' => $private_key_identifier,
'exp' => $time + 300,
'iat' => $time,
)
);
$base64UrlHeader = Utils::base64EncodeJWT( $header );
$base64UrlPayload = Utils::base64EncodeJWT( $payload );
$signature = hash_hmac( 'sha256', $base64UrlHeader . '.' . $base64UrlPayload, $private_key, true );
$base64UrlSignature = Utils::base64EncodeJWT( $signature );
return $base64UrlHeader . '.' . $base64UrlPayload . '.' . $base64UrlSignature;
}
}