<?php
/**
 * WPMR_Definitions_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_Definitions {

	function maybe_load_default_definitions() {
		$definitions = $this->get_setting( 'signatures' );
		if ( ! $definitions ) {
			$definitions = file_get_contents( trailingslashit( $this->dir ) . 'wpmr.json' );
			$definitions = json_decode( $definitions, true );
			$update      = $this->update_setting( 'signatures', $definitions );
			$this->update_setting( 'sig_time', 0 );
			return $definitions;
		}
	}

	function get_definitions() {
		$definitions = $this->get_setting( 'signatures' );
		if ( ! $definitions ) {
			$definitions = $this->maybe_load_default_definitions();
		}
		unset( $definitions['v'] );
		$severe     = array();
		$high       = array();
		$suspicious = array();
		foreach ( $definitions['definitions']['files'] as $definition => $signature ) {
			if ( $signature['severity'] == 'severe' ) {
				$severe[ $definition ] = $definitions['definitions']['files'][ $definition ];
			}
			if ( $signature['severity'] == 'high' ) {
				$high[ $definition ] = $definitions['definitions']['files'][ $definition ];
			}
			if ( $signature['severity'] == 'suspicious' ) {
				$suspicious[ $definition ] = $definitions['definitions']['files'][ $definition ];
			}
		}
		$files      = array_merge( $severe, $high, $suspicious ); // always return definitions in this sequence else suspicious matches are returned first without scanning for severe infections.
		$severe     = array();
		$high       = array();
		$suspicious = array();
		foreach ( $definitions['definitions']['db'] as $definition => $signature ) {
			if ( $signature['severity'] == 'severe' ) {
				$severe[ $definition ] = $definitions['definitions']['db'][ $definition ];
			}
			if ( $signature['severity'] == 'high' ) {
				$high[ $definition ] = $definitions['definitions']['db'][ $definition ];
			}
			if ( $signature['severity'] == 'suspicious' ) {
				$suspicious[ $definition ] = $definitions['definitions']['db'][ $definition ];
			}
		}
		$db                                  = array_merge( $severe, $high, $suspicious );
		$definitions['definitions']['files'] = $files;
		$definitions['definitions']['db']    = $db;
		return $definitions;
	}

	function get_definition_count() {
		$defs  = $this->get_definitions();
		$count = 0;
		while ( count( $defs['definitions'] ) ) {
			$count += count( array_shift( $defs['definitions'] ) );
		}
		return $count;
	}

	function get_definition_version() {
		$sigs = $this->get_setting( 'signatures' );

		if ( empty( $sigs ) ) {
			$sigs = $this->maybe_load_default_definitions();
		}

		if ( is_array( $sigs ) && array_key_exists( 'v', $sigs ) && $sigs['v'] !== '' ) {
			return $sigs['v'];
		}

		return '';
	}

	function get_last_updated_ago() {
		$updated = $this->get_setting( 'sig_time' );
		if ( ! $updated ) {
			return 'Never';
		} else {
			return human_time_diff( gmdate( 'U', $updated ), gmdate( 'U' ) ) . ' ago';
		}
	}

	function fetch_definitions( $options = array() ) {
		$options = wp_parse_args(
			$options,
			array(
				'blocking'    => true,
				'timeout'     => $this->timeout,
			)
		);

		return $this->saas_request(
			'saas_update_definitions',
			array(
				'method'      => 'GET',
				'send_state'  => 'query',
				'query'       => array(
					'cachebust' => time(),
				),
				'blocking'    => (bool) $options['blocking'],
				'timeout'     => (float) $options['timeout'],
			)
		);
	}

	function check_definitions( $async = false ) {
		$blocking = empty( $async );
		$timeout  = $blocking ? $this->timeout : 0.5;

		$response = $this->fetch_definitions_version(
			array(
				'blocking'    => $blocking,
				'timeout'     => $timeout,
			)
		);

		if ( is_wp_error( $response ) ) {
			return;
		}

		if ( ! $blocking ) {
			return true;
		}

		$version = isset( $response['response'] ) ? $response['response'] : null;
		if ( empty( $version ) || empty( $version['success'] ) ) {
			return;
		}

		$payload = isset( $response['payload'] ) ? $response['payload'] : null;

		if ( empty( $payload ) || empty( $payload['server_defver'] ) ) {
			return;
		}

		$this->update_setting( 'update-version', $payload['server_defver'] );
		return true;
	}

	function fetch_definitions_version( $options = array() ) {
		$options = wp_parse_args(
			$options,
			array(
				'blocking'    => true,
				'timeout'     => $this->timeout,
			)
		);

		$def_version = $this->get_definition_version();
		$this->flog( 'Checking definitions. Current version: ' . ( empty( $def_version ) ? 'none' : $def_version ) );
		if ( ! is_scalar( $def_version ) || null === $def_version ) {
			$def_version = '';
		} else {
			$def_version = (string) $def_version;
		}

		return $this->saas_request(
			'saas_check_definitions',
			array(
				'method'      => 'GET',
				'send_state'  => 'query',
				'query'       => array(
					'cachebust' => time(),
				),
				'state_extra' => array( 'defver' => $def_version ),
				'blocking'    => (bool) $options['blocking'],
				'timeout'     => (float) $options['timeout'],
			)
		);
	}

	function update_definitions( $force = false ) {
		check_ajax_referer( 'wpmr_update_sigs', 'wpmr_update_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			return;
		}
		$this->raise_limits_conditionally();
		$response = $this->fetch_definitions();
		if ( is_wp_error( $response ) ) {
			return wp_send_json_error( $response->get_error_message() );
		}

		$definitions = isset( $response['response'] ) ? $response['response'] : null;
		if ( empty( $definitions ) || empty( $definitions['success'] ) ) {
			return wp_send_json_error( 'Unparsable definition-update.' );
		}

		$payload = isset( $response['payload'] ) ? $response['payload'] : null;

		if ( empty( $payload ) || empty( $payload['signatures'] ) ) {
			return wp_send_json_error( 'Empty definition payload.' );
		}

		$definitions_data = $payload['signatures'];
		$this->update_setting( 'signatures', $definitions_data );
		$time = gmdate( 'U' );
		$this->update_setting( 'sig_time', $time );
		return wp_send_json_success(
			array(
				'count'    => $this->get_definition_count(),
				'version'  => $this->get_definition_version(),
				'sig_time' => $this->get_last_updated_ago(),
			)
		);
	}

	function update_definitions_cli( $echo = false ) {
		$this->raise_limits_conditionally();
		$response = $this->fetch_definitions();
		
		if ( is_wp_error( $response ) ) {
			if ( $echo ) {
				if ( $this->wpmr_iscli() ) {
					WP_CLI::error( $response->get_error_message() );
				} else {
					echo 'Error: ' . esc_html( $response->get_error_message() );
				}
			} else {
				return false;
			}
		}

		$definitions = isset( $response['response'] ) ? $response['response'] : null;
		if ( empty( $definitions ) || empty( $definitions['success'] ) ) {
			if ( $echo ) {
				if ( $this->wpmr_iscli() ) {
					WP_CLI::error( 'Unparsable definition-update.' );
				} else {
					echo 'Unparsable definition-update.';
				}
			} else {
				return false;
			}
		}

		$payload = isset( $response['payload'] ) ? $response['payload'] : null;

		if ( empty( $payload ) || empty( $payload['signatures'] ) ) {
			if ( $echo ) {
				if ( $this->wpmr_iscli() ) {
					WP_CLI::error( 'Empty definition payload.' );
				} else {
					echo 'Empty definition payload.';
				}
			} else {
				return false;
			}
		}

		$definitions_data = $payload['signatures'];
		$this->update_setting( 'signatures', $definitions_data );
		$time = gmdate( 'U' );
		$this->update_setting( 'sig_time', $time );
		if ( $echo ) {
			if ( $this->wpmr_iscli() ) {
				WP_CLI::success( 'Updated Malcure definitions to version: ' . WP_CLI::colorize( '%Y' . $definitions_data['v'] . '. %nCount: %Y' . $this->get_definition_count() . '%n' ) . ' definitions.' );
			} else {
				echo 'Updated Malcure definitions to version <strong>' . esc_html( $definitions_data['v'] ) . '</strong>. Count: <strong>' . esc_html( $this->get_definition_count() ) . '</strong> definitions.';
			}
		} else {
			return true;
		}
	}

	function definition_updates_available() {
		$current = $this->get_definition_version();
		$new     = $this->get_setting( 'update-version' );
		// $this->flog( 'Definition versions: current=' . $current . '= new=' . $new . '=' );
		if ( ! empty( $current ) && ! empty( $new ) && $current != $new ) {
			return array(
				'new'     => $new,
				'current' => $current,
			);
		}
	}
}
