<?php
/**
 * One-click importers for the plugins people are leaving:
 *
 *  - Redirection (redirection_items table)
 *  - Quick Page/Post Redirect Plugin (closed by wordpress.org 2026-04;
 *    its ~200k sites are orphaned — options + per-post meta)
 *  - WP 301 Redirects / Simple 301 Redirects ('301_redirects' option and
 *    the eps custom table)
 *  - Safe Redirect Manager (redirect_rule post type)
 *
 * Every import snapshots the current rules first (one-click restore), and
 * imported rules are queued for verification immediately, so five minutes
 * after migrating the user can SEE that every redirect still works —
 * something none of the source plugins ever told them.
 *
 * @package RedirectZen
 */

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

class RDZ_Importers {

	/**
	 * Hooks.
	 */
	public static function init() {
		add_action( 'admin_post_rdz_import', array( __CLASS__, 'handle_import' ) );
	}

	/**
	 * Importer registry.
	 *
	 * @return array key => label.
	 */
	public static function sources() {
		return array(
			'redirection' => 'Redirection',
			'qppr'        => 'Quick Page/Post Redirect Plugin',
			'eps301'      => 'WP 301 Redirects / Simple 301 Redirects',
			'srm'         => 'Safe Redirect Manager',
		);
	}

	/**
	 * Detects importable data.
	 *
	 * @return array key => array{ label: string, count: int }
	 */
	public static function detect() {
		$found = array();

		foreach ( self::sources() as $key => $label ) {
			$count = (int) call_user_func( array( __CLASS__, 'count_' . $key ) );
			if ( $count > 0 ) {
				$found[ $key ] = array(
					'label' => $label,
					'count' => $count,
				);
			}
		}

		return $found;
	}

	/**
	 * Import form handler.
	 */
	public static function handle_import() {
		if ( ! current_user_can( RDZ_Admin::CAPABILITY ) ) {
			wp_die( esc_html__( 'You are not allowed to do that.', 'redirectzen-301-redirect-manager' ) );
		}
		check_admin_referer( 'rdz_import' );

		$source = isset( $_POST['rdz_source'] ) ? sanitize_key( wp_unslash( $_POST['rdz_source'] ) ) : '';
		if ( ! array_key_exists( $source, self::sources() ) ) {
			wp_die( esc_html__( 'Unknown import source.', 'redirectzen-301-redirect-manager' ) );
		}

		$result = self::import( $source );

		wp_safe_redirect(
			add_query_arg(
				array(
					'page'       => 'redirectzen-import',
					'rdz_notice' => 'import_done',
					'imported'   => (int) $result['imported'],
					'skipped'    => (int) $result['skipped'],
					'src'        => $source,
				),
				admin_url( 'admin.php' )
			)
		);
		exit;
	}

	/**
	 * Runs one importer with a backup first.
	 *
	 * @param string $key Importer key.
	 * @return array { imported: int, skipped: int }
	 */
	public static function import( $key ) {
		/* translators: %s: source plugin name. */
		RDZ_Backups::snapshot( sprintf( __( 'Before import from %s', 'redirectzen-301-redirect-manager' ), self::sources()[ $key ] ) );

		$entries = call_user_func( array( __CLASS__, 'entries_' . $key ) );

		return self::insert_entries( $entries );
	}

	/**
	 * Inserts normalized entries, counting skips (dupes/invalid).
	 *
	 * @param array[] $entries { source, target, type }.
	 * @return array { imported: int, skipped: int }
	 */
	private static function insert_entries( $entries ) {
		$imported = 0;
		$skipped  = 0;

		foreach ( (array) $entries as $entry ) {
			$result = RDZ_Redirects::add_rule( $entry['source'], $entry['target'], isset( $entry['type'] ) ? (int) $entry['type'] : 301, array( 'queue_verify' => true ) );
			if ( is_wp_error( $result ) ) {
				$skipped++;
			} else {
				$imported++;
			}
		}

		return array(
			'imported' => $imported,
			'skipped'  => $skipped,
		);
	}

	/* ---------------------------------------------------------------------
	 * Redirection
	 * ------------------------------------------------------------------ */

	private static function redirection_table() {
		global $wpdb;

		return $wpdb->prefix . 'redirection_items';
	}

	private static function table_exists( $table ) {
		global $wpdb;

		return (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	public static function count_redirection() {
		global $wpdb;

		$table = self::redirection_table();
		if ( ! self::table_exists( $table ) ) {
			return 0;
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status = 'enabled' AND action_type = 'url' AND regex = 0" );
	}

	public static function entries_redirection() {
		global $wpdb;

		$table = self::redirection_table();
		if ( ! self::table_exists( $table ) ) {
			return array();
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$rows = $wpdb->get_results( "SELECT url, action_data, action_code FROM {$table} WHERE status = 'enabled' AND action_type = 'url' AND regex = 0", ARRAY_A );

		return self::map_redirection_rows( (array) $rows );
	}

	/**
	 * Maps Redirection rows to import entries. Pure logic, unit tested.
	 * action_data is usually the plain target URL, but newer Redirection
	 * versions serialize an array for advanced matches — those get their
	 * 'url' member, everything else is skipped.
	 *
	 * @param array[] $rows DB rows (url, action_data, action_code).
	 * @return array[]
	 */
	public static function map_redirection_rows( $rows ) {
		$entries = array();

		foreach ( $rows as $row ) {
			$target = isset( $row['action_data'] ) ? $row['action_data'] : '';
			$maybe  = function_exists( 'maybe_unserialize' ) ? maybe_unserialize( $target ) : $target;
			if ( is_array( $maybe ) ) {
				$target = isset( $maybe['url'] ) && is_string( $maybe['url'] ) ? $maybe['url'] : '';
			}
			$target = trim( (string) $target );
			$source = isset( $row['url'] ) ? trim( (string) $row['url'] ) : '';
			if ( '' === $source || '' === $target ) {
				continue;
			}

			$code = isset( $row['action_code'] ) ? (int) $row['action_code'] : 301;

			$entries[] = array(
				'source' => $source,
				'target' => $target,
				'type'   => in_array( $code, array( 301, 302, 307 ), true ) ? $code : 301,
			);
		}

		return $entries;
	}

	/* ---------------------------------------------------------------------
	 * Quick Page/Post Redirect Plugin
	 * ------------------------------------------------------------------ */

	public static function count_qppr() {
		$quick = get_option( 'quickppr_redirects', array() );
		$count = is_array( $quick ) ? count( $quick ) : 0;

		$count += count( self::qppr_post_meta_rows() );

		return $count;
	}

	public static function entries_qppr() {
		$quick = get_option( 'quickppr_redirects', array() );
		$meta  = get_option( 'quickppr_redirects_meta', array() );

		$entries = self::map_qppr_option( is_array( $quick ) ? $quick : array(), is_array( $meta ) ? $meta : array() );

		foreach ( self::qppr_post_meta_rows() as $row ) {
			$permalink = get_permalink( (int) $row['post_id'] );
			if ( ! $permalink ) {
				continue;
			}
			$entries[] = array(
				'source' => $permalink,
				'target' => $row['url'],
				'type'   => in_array( (int) $row['type'], array( 301, 302, 307 ), true ) ? (int) $row['type'] : 301,
			);
		}

		return $entries;
	}

	/**
	 * Maps QPPR's "quick redirects" option (request => destination) plus its
	 * meta option (request => type). Pure logic, unit tested.
	 *
	 * @param array $quick request => destination.
	 * @param array $meta  request => array|string with a redirect type.
	 * @return array[]
	 */
	public static function map_qppr_option( $quick, $meta ) {
		$entries = array();

		foreach ( $quick as $request => $destination ) {
			$request     = trim( (string) $request );
			$destination = trim( (string) $destination );
			if ( '' === $request || '' === $destination ) {
				continue;
			}

			$type = 301;
			if ( isset( $meta[ $request ] ) ) {
				$m    = $meta[ $request ];
				$type = is_array( $m ) && isset( $m['type'] ) ? (int) $m['type'] : (int) $m;
			}

			$entries[] = array(
				'source' => $request,
				'target' => $destination,
				'type'   => in_array( $type, array( 301, 302, 307 ), true ) ? $type : 301,
			);
		}

		return $entries;
	}

	/**
	 * Per-post QPPR redirects (post meta _pprredirect_*).
	 *
	 * @return array[] { post_id, url, type }
	 */
	private static function qppr_post_meta_rows() {
		global $wpdb;

		$rows = $wpdb->get_results(
			"SELECT pm.post_id, pm.meta_value AS url,
				(SELECT meta_value FROM {$wpdb->postmeta} t WHERE t.post_id = pm.post_id AND t.meta_key = '_pprredirect_type' LIMIT 1) AS type
			FROM {$wpdb->postmeta} pm
			INNER JOIN {$wpdb->postmeta} act ON act.post_id = pm.post_id AND act.meta_key = '_pprredirect_active' AND act.meta_value = '1'
			WHERE pm.meta_key = '_pprredirect_url' AND pm.meta_value <> ''",
			ARRAY_A
		); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		return is_array( $rows ) ? $rows : array();
	}

	/* ---------------------------------------------------------------------
	 * WP 301 Redirects (WebFactory) / Simple 301 Redirects
	 * ------------------------------------------------------------------ */

	public static function count_eps301() {
		global $wpdb;

		$count = 0;

		$option = get_option( '301_redirects', array() );
		if ( is_array( $option ) ) {
			$count += count( $option );
		}

		$table = $wpdb->prefix . '301_redirects';
		if ( self::table_exists( $table ) ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$count += (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
		}

		return $count;
	}

	public static function entries_eps301() {
		global $wpdb;

		$entries = array();

		// Simple 301 Redirects (and WP 301 Redirects' imported legacy data).
		$option = get_option( '301_redirects', array() );
		if ( is_array( $option ) ) {
			foreach ( $option as $request => $destination ) {
				$request     = trim( (string) $request );
				$destination = trim( (string) $destination );
				if ( '' === $request || '' === $destination || false !== strpos( $request, '*' ) ) {
					continue; // Wildcards need a regex rule (Pro) — skipped and reported.
				}
				$entries[] = array(
					'source' => $request,
					'target' => $destination,
					'type'   => 301,
				);
			}
		}

		// eps custom table (columns vary across versions: url/redirect_to
		// is the stable core).
		$table = $wpdb->prefix . '301_redirects';
		if ( self::table_exists( $table ) ) {
			$columns = (array) $wpdb->get_col( "SHOW COLUMNS FROM `{$table}`", 0 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$src     = in_array( 'url', $columns, true ) ? 'url' : ( in_array( 'request', $columns, true ) ? 'request' : '' );
			$dst     = in_array( 'redirect_to', $columns, true ) ? 'redirect_to' : ( in_array( 'destination', $columns, true ) ? 'destination' : '' );

			if ( $src && $dst ) {
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$rows = $wpdb->get_results( "SELECT `{$src}` AS s, `{$dst}` AS d FROM `{$table}`", ARRAY_A );
				foreach ( (array) $rows as $row ) {
					$source = trim( (string) $row['s'] );
					$target = trim( (string) $row['d'] );
					if ( '' === $source || '' === $target ) {
						continue;
					}
					$entries[] = array(
						'source' => $source,
						'target' => $target,
						'type'   => 301,
					);
				}
			}
		}

		return $entries;
	}

	/* ---------------------------------------------------------------------
	 * Safe Redirect Manager
	 * ------------------------------------------------------------------ */

	public static function count_srm() {
		$query = get_posts(
			array(
				'post_type'      => 'redirect_rule',
				'post_status'    => 'publish',
				'posts_per_page' => 1,
				'fields'         => 'ids',
			)
		);

		if ( empty( $query ) ) {
			return 0;
		}

		$counts = wp_count_posts( 'redirect_rule' );

		return isset( $counts->publish ) ? (int) $counts->publish : 0;
	}

	public static function entries_srm() {
		$posts = get_posts(
			array(
				'post_type'      => 'redirect_rule',
				'post_status'    => 'publish',
				'posts_per_page' => -1,
				'fields'         => 'ids',
			)
		);

		$entries = array();
		foreach ( $posts as $post_id ) {
			if ( get_post_meta( $post_id, '_redirect_rule_from_regex', true ) ) {
				continue; // Regex rules need Pro — skipped and reported.
			}

			$from = trim( (string) get_post_meta( $post_id, '_redirect_rule_from', true ) );
			$to   = trim( (string) get_post_meta( $post_id, '_redirect_rule_to', true ) );
			if ( '' === $from || '' === $to || false !== strpos( $from, '*' ) ) {
				continue;
			}

			$code = (int) get_post_meta( $post_id, '_redirect_rule_status_code', true );

			$entries[] = array(
				'source' => $from,
				'target' => $to,
				'type'   => in_array( $code, array( 301, 302, 307 ), true ) ? $code : 301,
			);
		}

		return $entries;
	}
}
