<?php
/**
 * WordPress cron anomaly detector.
 *
 * @package LocalGuardScanner
 */

namespace LocalGuardScanner\Detection\Database;

use LocalGuardScanner\Domain\Detection\Detector;
use LocalGuardScanner\Domain\Detection\Evidence;
use LocalGuardScanner\Domain\Detection\ScannableObject;

/**
 * Reviews safely decoded cron hooks and arguments.
 */
final class CronDetector implements Detector {
	/** @return string */
	public function id() {
		return 'database.cron';
	}

	/** @inheritDoc */
	public function inspect( ScannableObject $scanned_object ) {
		if ( 'db_option' !== $scanned_object->type() ) {
			return array();
		}
		$data = $scanned_object->attributes();
		if ( 'cron' !== ( isset( $data['option_name'] ) ? $data['option_name'] : '' ) || ! isset( $data['safe_value'] ) || ! is_array( $data['safe_value'] ) ) {
			return array();
		}
		$evidence = array();
		foreach ( $data['safe_value'] as $timestamp => $hooks ) {
			if ( ! is_numeric( $timestamp ) || ! is_array( $hooks ) ) {
				continue;
			}
			foreach ( $hooks as $hook => $events ) {
				if ( ! is_string( $hook ) || ! is_array( $events ) ) {
					continue;
				}
				if ( preg_match( '/^[a-f0-9]{20,}$/i', $hook ) ) {
					$evidence[] = new Evidence( 'database.random_cron_hook', 36, 72, 'random_looking_cron_hook', substr( $hook, 0, 32 ) );
				}
				$encoded = json_encode( $events, JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
				if ( is_string( $encoded ) && preg_match( '~(?:base64_decode|<\?php|https?://[^"]{1,200}\.php)~i', $encoded ) ) {
					$evidence[] = new Evidence( 'database.cron_encoded_payload', 58, 84, 'suspicious_cron_arguments', substr( hash( 'sha256', $hook ), 0, 16 ) );
				}
			}
		}
		return $evidence;
	}
}
