HEX
Server: nginx
System: Linux pool195-106-36.bur.atomicsites.net 6.12.57+deb12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1~bpo12+1 (2025-11-17) x86_64
User: (0)
PHP: 8.3.32
Disabled: pcntl_fork
Upload Files
File: /wordpress/plugins/wp-cloud-client/beta/src/Cleanup/GtmCleanup.php
<?php

declare(strict_types=1);

namespace VPlugins\WPCloudClient\Cleanup;

use VPlugins\WPCloudClient\Support\Logger;

/**
 * One-time removal of GTM-5VJ33FV6 from WPCode / Insert Headers and Footers options.
 *
 * WSP-3395: GTM-5VJ33FV6 was injected into customer sites via the Insert Headers and
 * Footers (WPCode) plugin. This class surgically strips it from the ihaf_insert_header,
 * ihaf_insert_body, and ihaf_insert_footer options, preserving any other scripts present.
 * It runs exactly once per site via Plugin::activate() and sets a flag so it never repeats.
 */
final class GtmCleanup {

	private const GTM_ID       = 'GTM-5VJ33FV6';
	private const CLEANUP_FLAG = 'wpcc_gtm_cleanup_done';

	/**
	 * Constructor.
	 *
	 * @param Logger $logger The logger instance.
	 */
	public function __construct(
		private readonly Logger $logger,
	) {}

	/**
	 * Scan ihaf_insert_header/body/footer and strip GTM-5VJ33FV6 from any that contain it.
	 *
	 * Exits early without flagging if the cleanup flag is already set or neither WPCode
	 * plugin variant is active (deferred to the next activation when the plugin is present).
	 * Surgically strips the GTM block so any surrounding scripts are preserved. Sets the
	 * cleanup flag only after a successful pass so the method never re-runs.
	 *
	 * @return void
	 */
	public function run(): void {
		if ( get_option( self::CLEANUP_FLAG ) ) {
			return;
		}

		if ( ! function_exists( 'is_plugin_active' ) ) {
			if ( ! defined( 'ABSPATH' ) || ! file_exists( ABSPATH . 'wp-admin/includes/plugin.php' ) ) {
				$this->logger->error( 'GTM cleanup skipped — wp-admin/includes/plugin.php unavailable' );
				return;
			}
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		if (
			! is_plugin_active( 'insert-headers-and-footers/ihaf.php' ) &&
			! is_plugin_active( 'insert-headers-and-footers/wpcode.php' )
		) {
			return;
		}

		foreach ( [ 'ihaf_insert_header', 'ihaf_insert_body', 'ihaf_insert_footer' ] as $option_name ) {
			$value = get_option( $option_name );

			// false = option does not exist; skip silently.
			if ( false === $value || '' === $value ) {
				continue;
			}

			if ( ! is_string( $value ) ) {
				$this->logger->warning(
					'GTM cleanup: unexpected option type',
					[
						'option' => $option_name,
						'type'   => gettype( $value ),
					]
				);
				continue;
			}

			if ( ! str_contains( $value, self::GTM_ID ) ) {
				continue;
			}

			$stripped = trim( $this->stripGtmSnippet( $value ) );

			if ( '' === $stripped ) {
				if ( ! delete_option( $option_name ) ) {
					$this->logger->error( 'GTM cleanup: failed to delete ' . $option_name );
					return;
				}
			} elseif ( ! update_option( $option_name, $stripped ) ) {
				$this->logger->error( 'GTM cleanup: failed to update ' . $option_name );
				return;
			}
		}

		if ( ! update_option( self::CLEANUP_FLAG, 1 ) ) {
			$this->logger->error( 'GTM cleanup: failed to set cleanup flag' );
			return;
		}

		$this->logger->info( 'GTM cleanup completed successfully' );
	}

	/**
	 * Strip GTM-5VJ33FV6 script blocks from the given value, preserving any surrounding content.
	 *
	 * Handles the standard WPCode comment-wrapped format and bare script blocks.
	 * GTM script bodies do not contain literal < characters, so [^<]* is safe and avoids
	 * crossing into adjacent script tags.
	 *
	 * @param string $value Raw option value from Insert Headers and Footers.
	 * @return string The value with GTM blocks removed.
	 */
	private function stripGtmSnippet( string $value ): string {
		// Comment-wrapped GTM script block (standard WPCode/IHAF format)
		$value = (string) preg_replace(
			'/<!--\s*Google Tag Manager\s*-->[\s\S]*?<!--\s*End Google Tag Manager\s*-->/i',
			'',
			$value
		);
		// Comment-wrapped GTM noscript block
		$value = (string) preg_replace(
			'/<!--\s*Google Tag Manager \(noscript\)\s*-->[\s\S]*?<!--\s*End Google Tag Manager \(noscript\)\s*-->/i',
			'',
			$value
		);
		// Bare <script> block — GTM scripts contain no < in their body so [^<]* won't cross tag boundaries
		$value = (string) preg_replace(
			'/<script\b[^>]*>[^<]*' . preg_quote( self::GTM_ID, '/' ) . '[^<]*<\/script>/i',
			'',
			$value
		);
		return $value;
	}
}