<?php
/**
 * WPMR_Checksums_Trait
 *
 * Generated during Phase 2 restructuring
 * This trait contains methods extracted from the original monolithic wpmr.php
 *
 * @package WP_Malware_Removal
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;

}

trait WPMR_Checksums {

	function fails_checksum( $local_file ) {
		if ( $this->is_invalid_file( $local_file ) ) {
			if ( $this->is_scannable_file( $local_file ) ) {
				return true;
			}
			return;
		}
		if ( ! empty( $GLOBALS['WPMR']['regex'] ) ) {
			remove_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ), 11 );
		}

		$hash = @hash_file( 'sha256', $local_file );

		// ensure that for files in core wp dir, only the respective checksums are matched.
		if ( $this->is_in_core_wp_dir( $local_file ) ) {
			$checksums = $GLOBALS['WPMR']['core_checksums'];
		} else { // file not in core wp dir
			$checksums = $GLOBALS['WPMR']['checksums'];
		}

		// $checksums = $GLOBALS['WPMR']['checksums']; // testing if file is shown on rescan
		// For core files, check if the file path exists in checksums AND hash matches
		// For non-core files, just check if hash exists in checksums values
		$checksum_failed = false;
		if ( $this->is_in_core_wp_dir( $local_file ) ) {
			// Core file: must have both the path registered AND matching hash
			$relative_path   = $this->get_core_relative_path( $local_file );
			$checksum_failed = ! isset( $checksums[ $relative_path ] ) || $checksums[ $relative_path ] !== $hash;
		} else {
			// Non-core file: just check if hash exists in known checksums
			$checksum_failed = ! in_array( $hash, $checksums );
		}

		if ( $checksum_failed || $this->is_file_blacklisted( $hash ) ) {
			if ( ! $this->is_registered() ) {
				if (
					$this->normalise_path( $local_file ) == $this->normalise_path( trailingslashit( ABSPATH ) . 'wp-config.php' ) ||
					$this->normalise_path( $local_file ) == $this->normalise_path( trailingslashit( ABSPATH ) . '.htaccess' ) ||
					$this->normalise_path( $local_file ) == $this->normalise_path( trailingslashit( WP_CONTENT_DIR ) . 'index.php' ) ||
					$this->normalise_path( $local_file ) == $this->normalise_path( trailingslashit( WP_PLUGIN_DIR ) . 'index.php' ) ||
					$this->normalise_path( $local_file ) == $this->normalise_path( trailingslashit( get_theme_root() ) . 'index.php' )
				) {
					// uncommenting the next line could lead to false negatives
					// return false; // ignore known files that may not have a checksum at all
				}
			}
			return 'missing';
		}
		return;
	}

	function update_cached_checksums( $clean_files ) {
		if ( empty( $clean_files ) ) {
			return;
		}
		$checksums     = $this->get_checksums();
		$new_checksums = array();
		foreach ( $clean_files as $file ) {
			if ( empty( $checksums[ $file ] ) && ! $this->is_invalid_file( $file ) ) {
				$sha256 = @hash_file( 'sha256', $file );
				if ( ! empty( $sha256 ) ) {
					$new_checksums[ $file ] = $sha256;
				}
			}
		}
		$cached_checksums = get_option( 'WPMR_files_checksums_cache' );
		if ( $cached_checksums && is_array( $cached_checksums ) && ! empty( $cached_checksums ) ) {
			update_option( 'WPMR_files_checksums_cache', array_merge( $cached_checksums, $new_checksums ) );
		} else {
			update_option( 'WPMR_files_checksums_cache', $new_checksums );
		}
	}

	function get_checksums_values() {
		$checksums       = $this->get_checksums();
		$checksum_values = array_values( $checksums );
		$checksum_values = array_unique( $checksum_values, SORT_REGULAR );
		return $checksum_values;
	}

	function get_cached_checksums( $checksums ) {
		if ( ! empty( $GLOBALS['WPMR']['debug'] ) ) { // This element doesn't exist on page load, only exists on ajax requests
			return $checksums;
		}
		$cached_checksums = get_option( 'WPMR_files_checksums_cache' );
		if ( $cached_checksums && is_array( $cached_checksums ) && ! empty( $cached_checksums ) ) {
			return array_merge( $checksums, $cached_checksums );
		}
		return $checksums;
	}

	function delete_generated_checksums() {
		delete_option( 'WPMR_files_checksums_cache' );
		delete_option( 'WPMR_db_checksums_cache' );
	}

	function delete_core_checksums( $upgrader, $hook_extra ) {
		$this->flog( '$upgrader' );
		$this->flog( $upgrader );
		$this->flog( '$hook_extra' );
		$this->flog( $hook_extra );
		delete_option( 'WPMR_checksums' );
	}

	function get_checksums( $cached = true ) {
		$this->raise_limits_conditionally();
		$checksums = get_option( 'WPMR_checksums' );

        if ( get_transient( 'wpmr_checksum_fetch_lock' ) ) {
            $checksums = $checksums ? $checksums : array();
			return apply_filters( 'serve_checksums', $checksums );
        }
		
		if ( ! $checksums || ! $cached ) {
			$manifest = $this->build_checksums_manifest();
			if ( empty( $manifest ) ) {
				return array();
			}

			$request = $this->saas_request(
				'saas_get_checksums',
				array(
					'method'     => 'POST',
					'send_state' => 'body',
					'body'       => array(
						'components' => wp_json_encode( $manifest ),
					),
					'timeout'    => max( 60, (int) $this->timeout ),
				)
			);

			if ( is_wp_error( $request ) ) {
				$this->flog( 'Checksum batch request failed: ' . $request->get_error_message() );
				set_transient( 'wpmr_checksum_fetch_lock', true, MINUTE_IN_SECONDS );
				return array();
			}

			$payload = null;
			if ( isset( $request['payload'] ) && is_array( $request['payload'] ) ) {
				$payload = $request['payload'];
			}

			$checksums = $this->flatten_checksum_payload( $payload );
			if ( ! empty( $checksums ) ) {
				update_option( 'WPMR_checksums', $checksums );
				delete_transient( 'wpmr_checksum_fetch_lock' );
			}
			else {
				set_transient( 'wpmr_checksum_fetch_lock', true, MINUTE_IN_SECONDS );
			}
		}
		return apply_filters( 'serve_checksums', $checksums );
	}

	function build_checksums_manifest() {
		global $wp_version;

		$locale = $this->get_locale();
		if ( empty( $locale ) ) {
			$locale = get_locale();
		}
		if ( empty( $locale ) ) {
			$locale = 'en_US';
		}

		$manifest = array(
			'core'    => array(
				'slug'    => 'wordpress',
				'version' => $wp_version,
				'locale'  => $locale,
			),
			'plugins' => array(),
			'themes'  => array(),
		);

		$install_path = trailingslashit( wp_normalize_path( ABSPATH ) );
		$all_plugins  = get_plugins();
		foreach ( $all_plugins as $key => $plugin_data ) {
			if ( false === strpos( $key, '/' ) ) {
				continue;
			}

			$plugin_file = trailingslashit( dirname( $this->dir ) ) . $key;
			$plugin_file = wp_normalize_path( $plugin_file );
			$plugin_file = ltrim( str_replace( $install_path, '', $plugin_file ), '/' );
			$manifest['plugins'][] = array(
				'slug'      => dirname( $key ),
				'version'   => isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : '',
				'base_path' => $this->normalise_component_path( dirname( $plugin_file ) ),
			);
		}

		$themes = wp_get_themes();
		foreach ( $themes as $slug => $theme ) {
			$theme_dir = $theme->get_stylesheet_directory();
			if ( empty( $theme_dir ) ) {
				continue;
			}
			$manifest['themes'][] = array(
				'slug'      => $slug,
				'version'   => $theme->get( 'Version' ),
				'base_path' => $this->normalise_component_path( $theme_dir ),
			);
		}

		return $manifest;
	}

	function flatten_checksum_payload( $payload ) {
		if ( empty( $payload ) || ! is_array( $payload ) ) {
			return array();
		}

		$flat = array();

		if ( ! empty( $payload['core'] ) && ! empty( $payload['core']['files'] ) && is_array( $payload['core']['files'] ) ) {
			foreach ( $payload['core']['files'] as $file => $hashes ) {
				$hash = $this->extract_checksum_hash( $hashes );
				if ( empty( $hash ) ) {
					continue;
				}
				$relative = $this->normalise_component_path( $file );
				if ( '' !== $relative ) {
					$flat[ $relative ] = $hash;
				}
			}
		}

		foreach ( array( 'plugins', 'themes' ) as $section ) {
			if ( empty( $payload[ $section ] ) || ! is_array( $payload[ $section ] ) ) {
				continue;
			}
			foreach ( $payload[ $section ] as $component ) {
				if ( empty( $component['files'] ) || empty( $component['base_path'] ) ) {
					continue;
				}
				$base_path = $this->normalise_component_path( $component['base_path'] );
				if ( '' === $base_path ) {
					continue;
				}
				$base_path = trailingslashit( $base_path );
				foreach ( $component['files'] as $file => $hashes ) {
					$hash = $this->extract_checksum_hash( $hashes );
					if ( empty( $hash ) ) {
						continue;
					}
					$relative = ltrim( $this->normalise_component_path( $file ), '/' );
					$flat[ $base_path . $relative ] = $hash;
				}
			}
		}

		return $flat;
	}

	function extract_checksum_hash( $entry ) {
		if ( empty( $entry ) ) {
			return '';
		}
		if ( is_array( $entry ) && ! empty( $entry['sha256'] ) ) {
			return $entry['sha256'];
		}
		return '';
	}

	function normalise_component_path( $path ) {
		if ( empty( $path ) ) {
			return '';
		}
		$normalised = wp_normalize_path( $path );
		$abspath    = trailingslashit( wp_normalize_path( ABSPATH ) );
		if ( 0 === strpos( $normalised, $abspath ) ) {
			$normalised = substr( $normalised, strlen( $abspath ) );
		}
		$normalised = ltrim( $normalised, '/' );
		$normalised = preg_replace( '#/+#', '/', $normalised );
		return untrailingslashit( $normalised );
	}


	function map_core_checksums( $checksums ) {
		$real_abspath = trailingslashit( $this->normalise_path( ABSPATH ) );
		foreach ( $checksums as $f => $c ) {
			$checksums[ $real_abspath . $f ] = $c;
			unset( $checksums[ $f ] );
		}
		return $checksums;
	}

	function checksums_delete_invalid() {
		$cached_checksums = get_option( 'WPMR_files_checksums_cache' );
		if ( ! $cached_checksums || ! is_array( $cached_checksums ) ) {
			return;
		}
		foreach ( $cached_checksums as $file => $checksum ) {
			if ( $this->is_invalid_file( $file ) ) {
				unset( $cached_checksums[ $file ] );
			}
		}
		update_option( 'WPMR_files_checksums_cache', $cached_checksums );
	}

	function get_db_checksums() {
		return get_option( 'WPMR_db_checksums_cache', array() );
	}
}
