e noscript iframe while keeping the data layer active - e.g.
* from an mu-plugin on a staging/clone copy of the site.
*
* @since 2.0.0
*
* @param bool $output_container Whether to output the container. Default true.
*/
if ( ! apply_filters( GTM4WP_WPFILTER_OUTPUT_CONTAINER, true ) ) {
return false;
}
if ( $this->options->get( GTM4WP_OPTION_PRODUCTIONONLY ) ) {
// wp_get_environment_type() ships with WordPress 5.5+; the guard
// keeps the unit tests (which never load WordPress) working.
$environment = function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : 'production';
if ( 'production' !== $environment ) {
return false;
}
}
return true;
}
/**
* Returns the first role of the current user that the "Exclude user roles"
* option turns the container code off for, or an empty string when the
* container may be emitted for this user.
*
* Both container sinks ask this - the
loader in header_begin() and
* the iframe in get_tag() - so an excluded role suppresses the
* whole container. The check used to live inline in header_begin() only,
* which left the iframe (the very fallback that loads GTM when JavaScript
* does not run) firing for an excluded user.
*
* @return string The matching excluded role, or '' when none matches.
*/
private function excluded_user_role(): string {
$disabled_roles = explode( ',', (string) $this->options->get( GTM4WP_OPTION_NOGTMFORLOGGEDIN ) );
$current_user = wp_get_current_user();
foreach ( (array) ( $current_user->roles ?? array() ) as $user_role ) {
if ( in_array( $user_role, $disabled_roles, true ) ) {
return (string) $user_role;
}
}
return '';
}
/**
* Returns the environment query string fragment of one container row,
* empty unless both the gtm_auth and gtm_preview parameters are set.
*
* @param array $container One container row.
* @return string
*/
private function container_environment( array $container ): string {
$gtm_auth = (string) ( $container[ ContainerRows::COLUMN_AUTH ] ?? '' );
$gtm_preview = (string) ( $container[ ContainerRows::COLUMN_PREVIEW ] ?? '' );
if ( ( '' === $gtm_auth ) || ( '' === $gtm_preview ) ) {
return '';
}
// Re-validate at the output sink (PA-2), not just on save: these values
// also reach the row from a 1.x migration and from the
// GTM4WP_HARDCODED_GTM_ENV_* wp-config constants, neither of which went
// through the admin schema sanitizer. A stray & would otherwise inject
// extra query parameters into the container loader URL and the noscript
// iframe src. Malformed values are dropped whole (both parameters are
// meaningless alone) - Notices warns the admin so this is never silent.
if ( ! preg_match( ContainerRows::AUTH_PATTERN, $gtm_auth )
|| ! preg_match( ContainerRows::PREVIEW_PATTERN, $gtm_preview )
) {
return '';
}
return '>m_auth=' . esc_attr( $gtm_auth ) . '>m_preview=' . esc_attr( $gtm_preview ) . '>m_cookies_win=x';
}
/**
* Tells whether the GTM container ID must be left out of the container
* loader URL of one container row.
*
* This is only honored together with a custom loader path: it targets
* server side GTM setups where the container is selected by the request
* path/domain and the ID is configured on the server. Without a custom
* path the checkbox has no effect, so the default www.googletagmanager.com
* loader never ends up without an ID.
*
* @param array $container One container row.
* @return bool
*/
private function container_omit_id( array $container ): bool {
$no_id = '' !== (string) ( $container[ ContainerRows::COLUMN_NO_ID ] ?? '' );
$custom_path = '' !== (string) ( $container[ ContainerRows::COLUMN_PATH ] ?? '' );
return $no_id && $custom_path;
}
/**
* Returns the validated GTM container domain of one container row,
* falling back to the default Google domain on invalid custom values.
*
* @param array $container One container row.
* @return string
*/
private function container_domain( array $container ): string {
$custom_domain = (string) ( $container[ ContainerRows::COLUMN_DOMAIN ] ?? '' );
$_gtm_domain_test = ( '' === $custom_domain ) ? 'www.googletagmanager.com' : strtolower( $custom_domain );
$_gtm_domain_name = filter_var( $_gtm_domain_test, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME );
if ( false === $_gtm_domain_name ) {
return 'www.googletagmanager.com';
}
return $_gtm_domain_name;
}
/**
* Returns the validated GTM container script path of one container row,
* falling back to gtm.js on invalid custom values.
*
* @param array $container One container row.
* @return string
*/
private function container_path( array $container ): string {
$custom_path = (string) ( $container[ ContainerRows::COLUMN_PATH ] ?? '' );
$_gtm_domain_path = ( '' === $custom_path ) ? 'gtm.js' : $custom_path;
// Use the shared constant rather than a retyped copy: this allow-list is
// security relevant, and the inline duplicate had already drifted from it.
if ( ! preg_match( ContainerRows::PATH_PATTERN, $_gtm_domain_path ) ) {
return 'gtm.js';
}
return $_gtm_domain_path;
}
}