Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since Feb 2022 */ namespace IPS\core\DataLayer; /* To prevent PHP errors (extending class does not exist) revealing path */ use IPS\Data\Store; use IPS\Settings; use OutOfRangeException; use UnexpectedValueException; use function defined; use function is_array; use function preg_replace; use function str_replace; if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Data Layer Handler class */ #[\AllowDynamicProperties] class Handler { public static string $cicHandlerClass = '\IPS\cloud\DataLayer\Handler'; public static string $handlerCacheKey = 'dataLayerHandlers'; public string $id = 'gtm'; /** * Returns the enabled handlers to be used in the global template as an array of head and body HTML snippets. * * @return array */ public static function loadForTemplates() : array { $return = array(); /* try to get from cache */ $key = static::$handlerCacheKey; try { $cached = json_decode( Store::i()->$key, true ); if ( is_array( $cached ) ) { return $cached; } } catch ( OutOfRangeException $e ) {} $handlers = static::loadEnabled(); if ( Settings::i()->core_datalayer_use_gtm ) { $handlers[] = static::gtm(); } $headInserts = "\n\n\n"; $bodyInserts = "\n"; $headCodes = ""; $eventCallbacks = array(); $propertiesCallbacks = array(); foreach ( $handlers as $handler ) { if ( $handler->head_code ) { $headCodes .= "\n{$handler->head_code}\n"; } if ( $handler->body_code ) { $bodyInserts .= "\n{$handler->body_code}\n"; } if ( $handler->initializer_code AND $handler->use_js ) { $formatted = str_replace( '{dataLayer}', $handler->datalayer_key, $handler->initializer_code ); $code = << let initcode{$handler->id} = context => { if ( !(context instanceof Object) ) { return; } /* Set the key and time */ let ips_time = IpsDataLayerConfig._properties.ips_time.enabled ? IpsDataLayerConfig._properties.ips_time.formatted_name : false; let ips_key = IpsDataLayerConfig._properties.ips_key.enabled ? IpsDataLayerConfig._properties.ips_key.formatted_name : false; if ( ips_time ) { context[ips_time] = Math.floor( Date.now() / 1000 ); } if ( ips_key ) { let s = i => { return Math.floor((1 + Math.random()) * Math.pow(16, i)) .toString(16) .substring(1); }; let mt = Date.now(); let sec = Math.floor(mt / 1000); let secString = sec.toString(16); secString = secString.substring( secString.length - 8 ); let ms = ( mt - ( sec * 1000 ) ) * 1000; // milliseconds let msString = (ms + 0x100000).toString(16).substring(1); let randomId = secString + msString + s(1) + '.' + s(4) + s(4); context[ips_key] = randomId; } for ( let i in context ) { if ( context[i] === null ) { context[i] = undefined; } } try { $formatted } catch (e) { Debug.error('Bad Data Layer Initializer: Event initializer failed!'); } }; initcode{$handler->id}(IpsDataLayerContext || {}); HTML; $code = preg_replace( '/\/\/(.*)[\n|\r]/', '/*$1*/', $code ); $code = preg_replace( '/\s+/', ' ', $code); $headInserts .= str_replace("\n", ' ', $code ) . "\n"; } if ( $handler->event_handler AND $handler->use_js ) { $formatted = str_replace( '{dataLayer}', $handler->datalayer_key, $handler->event_handler ); $code = << _event => { try { $formatted } catch (e) { Debug.error( e ); } } ) JS; $code = preg_replace( '/\/\/(.*)[\n|\r]?/', '/*$1*/', $code ); $code = preg_replace( '/\s+/', ' ', $code); $eventCallbacks[] = str_replace( "\n", ' ', $code ); } if ( $handler->properties_handler AND $handler->use_js ) { $formatted = str_replace( '{dataLayer}', $handler->datalayer_key, $handler->properties_handler ); $code = << _properties => { try { $formatted } catch (e) { Debug.error( e ); } } ) JS; $code = preg_replace( '/\/\/(.*)[\n|\r]?/', '/*$1*/', $code ); $code = preg_replace( '/\s+/', ' ', $code); $propertiesCallbacks[] = str_replace( "\n", ' ', $code ); } } $headInserts .= "\n"; $headInserts .= "\n\n$headCodes\n\n"; $eventsJoined = implode( ",\n\t", $eventCallbacks ); $headInserts .= << HTML; $propertiesJoined = implode( ",\n\t", $propertiesCallbacks ); $headInserts .= << HTML; $headInserts .= "\n"; $bodyInserts .= "\n"; $return = array( 'headInserts' => $headInserts, 'bodyInserts' => $bodyInserts, ); /* add to cache before returning */ Store::i()->$key = json_encode( $return ); return $return; } /** * Get all enabled handlers * * @return static[] */ public static function loadEnabled() : array { if ( class_exists( static::$cicHandlerClass ) ) { $class = static::$cicHandlerClass; return $class::loadEnabled(); } return array(); } /** * Get a built in gtm handler * * @return static */ public static function gtm() : static { $gtm = new static(); $gtm->initializer_code = <<head_code = Settings::i()->googletag_head_code; $gtm->use_js = true; $gtm->body_code = Settings::i()->googletag_noscript_code; $gtm->datalayer_key = Settings::i()->core_datalayer_gtmkey ?: 'window.dataLayer'; $gtm->event_handler = <<properties_handler = <<