<?php

require_once 'rover-shared.php';
require_once 'rover-content.php';
require_once 'widgets/_init.php';

require_once 'blocks/listings.php';


class Rover_IDX
	{
	public $CONTENT										= null;
	public $COMMON										= null;

	public $roveridx_regions							= null;
	public $roveridx_theming							= null;
	public $curr_path									= null;
	public $curr_path_parts								= null;
	public $debug_html									= array();
	public $all_selected_regions						= null;
	public $post_id										= null;

	public	$rover_404_regions							= null;
	public	$rover_404_slugs							= null;
	public	$found_slug									= null;

	private $slug_cache									= [];
	private $slug_lookup								= null;
	private $standard_slugs_lookup						= null;

	private $path_by_page								= null;
	private	$first_selected_region						= null;
	private	$bootstrap_ready							= null;
	private	$checked_for_404_content					= false;

	private	const OPTIONS_KEY_CSS_DEFAULT				= 'roveridx_css_default';

	private const IDX_REST_NAME							= 'roveridx/v1';

	private const ROVER_IP_RANGE_START					= '67.227.230.0';
	private const ROVER_IP_RANGE_END					= '67.227.230.255';

	function __construct() {

		global											$wp;

		if (!is_array($this->roveridx_regions = get_option(roverOPTIONS::REGIONS)))
			$this->roveridx_regions						= array();
		if (!is_array($this->roveridx_theming = get_option(roverOPTIONS::THEME)))
			$this->roveridx_theming						= array();

		$this->curr_path								= (empty($wp->request))
																? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
																: $wp->request;
		$this->curr_path_parts							= (is_null($this->curr_path))
																? []
																: array_filter(explode('/', $this->curr_path));

		#	Is this a static page, or a dynamic page
		$this->path_by_page								= get_page_by_path($this->curr_path, OBJECT);

		if (!isset($this->roveridx_regions['domain_id']))
			$this->roveridx_regions['domain_id']		= null;
		if (!isset($this->roveridx_regions['regions']))
			$this->roveridx_regions['regions']			= null;

		$this->CONTENT									= new Rover_IDX_Content($this);
		$this->COMMON									= new Rover_IDX_COMMON($this);

		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, __CLASS__);

		add_action( 'wp_enqueue_scripts', 				[ $this->COMMON, 'css_and_js_front' ], 99 );

#		add_action( 'init',								[ $this,	'rewrite_rules']);
		add_action( 'after_setup_theme',				[ $this,	'init_block_theme_integration']);
		add_action( 'init',								[ $this,	'init_third_party_integration']);

#		add_filter( 'request', [ $this->CONTENT, 'dynamic_page_request' ] );
		add_action( 'parse_request',					[ $this->CONTENT, 'dynamic_page_router_style_parse_request' ]);
#		add_action( 'pre_get_posts',					[ $this->CONTENT, 'dynamic_page_router_style_pre_get_posts' ]);


#		Convert dynamic (3.0) menu to static (4.0) menu in upgrade process only
#		add_filter( 'wp_nav_menu_items',				array($this,	'add_login_menu'), 10, 2 );

		add_filter( 'script_loader_tag',				array($this,	'add_attributes_to_script'), 10, 3 );

		add_filter( 'validate_plugin_require', function($requirements) {
			#	Check PHP version
			if (version_compare(PHP_VERSION, '8.0.0', '<')) {
				$requirements[] = sprintf(
					__('This plugin requires PHP version 8.0 or greater. Your current PHP version is %s.', 'rover-idx'),
					PHP_VERSION
					);
				}
			return $requirements;
			});

		add_filter( 'rover_idx_resolve', function () {
			return $this;		#	This is $rover_idx
			}, 10, 0 );

		add_action(	'do_robots', 						array($this,	'robots'), 100, 0);
		add_action( 'wp_head',							array($this,	'load_css_and_preload'));
		add_action( 'admin_head',						array($this,	'load_css_and_preload'));

		if (isset($this->roveridx_theming['js_load']) && ($this->roveridx_theming['js_load'] == 'body'))
			{
			add_action( 'wp_footer',					array($this,	'load_js'));
			add_action( 'admin_footer',					array($this,	'load_js'));
			}
		else
			{
			add_action( 'wp_head',						array($this,	'load_js'));
			add_action( 'admin_head',					array($this,	'load_js'));
			}

		if (
			isset($this->roveridx_theming['load_emojis']) &&
			$this->roveridx_theming['load_emojis'] == 'No'
			)
			{
			#	A D M I N
			remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
			remove_action( 'admin_enqueue_scripts', 'wp_enqueue_emoji_styles' );
			remove_action( 'admin_print_styles', 'print_emoji_styles' ); /** Retained for backwards compatibility */

			#	F R O N T   E N D
			remove_action( 'wp_head', 'print_emoji_detection_script', 7);
			remove_action( 'wp_enqueue_scripts', 'wp_enqueue_emoji_styles' );
			remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
			remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );

			remove_action( 'embed_head', 'print_emoji_detection_script' );
			remove_action( 'enqueue_embed_scripts', 'wp_enqueue_emoji_styles' );

			/*	deprecated, here for backward compatibility	*/
			remove_action('wp_print_styles', 'print_emoji_styles');
			}

		add_action( 'roveridx_cron_hourly',				array($this,	'cron_hourly'));
		add_action( 'roveridx_cron_daily',				array($this,	'cron_daily'));

		add_action( 'rest_api_init',					array($this,	'rest_endpoints') );
		add_action( 'wp_ajax_idx_site_posts',			array($this,	'idx_site_posts_callback') );
		add_action( 'wp_ajax_nopriv_idx_site_posts',	array($this,	'idx_site_posts_callback') );

		add_filter( 'rocket_preload_exclude_urls', function( $regexes ) {

			#	WP Rocket
			#	Preloading of pages or links, especially in large MLS's, cause tens of thousands of
			#	page requests every few hours.  Prohibit these dynamic pages from preloading.

			$permalink_structure						= get_option('permalink_structure');
			$url_ends_in_slash							= (is_null($permalink_structure) && substr($permalink_structure, -1) != '/')
																? false
																: true;

			foreach(array_unique(array_map('strtolower', $this->all_selected_regions)) as $one_state)
				{
				$url_to_omit							= ($url_ends_in_slash)
																? get_site_url().$one_state
																: get_site_url().'/'.$one_state;

				$regexes[]								= $url_to_omit;
				}

			return $regexes;
			});


/*
add_action('amp_post_template_css','ampforwp_add_custom_css_example', 11);

function ampforwp_add_custom_css_example() {

	'body {
		background: red;
	}';
}
*/



//		add_filter( 'language_attributes',				array($this, 'fbml_add_namespace'));
//		add_filter( 'opengraph_type', 					array($this, 'fb_og_type' ));

		if ( !defined( 'WP_INSTALLING' ) || WP_INSTALLING === false )
			{
			$this->all_selected_regions					= $this->get_selected_regions();

			if ( is_admin() )
				{
				add_action( 'plugins_loaded',			array($this, 'init_admin'), 15 );
				}
			else
				{
				add_action( 'plugins_loaded',			array($this, 'init_front'));
				}

			if (is_admin() && (!defined( 'DOING_AJAX' ) || ! DOING_AJAX))
				{
				if (isset($this->roveridx_regions['redirect_for_setup']))
					{
					if ( true === $this->roveridx_regions['redirect_for_setup'] )
						add_action( 'init', array( $this, 'redirect_to_setup' ) );
					}
				}
			}
		else
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'WP_INSTALLING is true - skipping init_admin() and init_front()' );
			}
		}

	public function get_first_region()	{
		return $this->first_selected_region;
		}

	public function refresh_css()	{

		$the_content						= $this->CONTENT->rover_content('rover-default-css');
		if (!empty($the_content['the_html']) && strpos($the_content['the_html'], 'request blocked') === false)
			update_option(self::OPTIONS_KEY_CSS_DEFAULT, $the_content['the_html'], true);

		#	AMP is dead
		if (get_option('roveridx_css_amp') !== false)
			delete_option('roveridx_css_amp');
		}

	public function admin_notice_upgraded()	{

		printf(
				'<div class="notice notice-info rover-notice is-dismissible"><p>Rover IDX has been upgraded to %s</p></div>',
				roverVERSIONS::VERSION
				);

		}

	public function permalinks_have_been_updated( $oldvalue, $_newvalue )
		{
		$url_ends_with_slash				= ($_newvalue && substr($_newvalue, -1) != '/')
													? false
													: true;

		$this->update_region_settings(__FUNCTION__, __LINE__, 'url_ends_with_slash', $url_ends_with_slash);
		}

	public function rewrite_rules() {

		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'add_rewrite_rules ');

		foreach(array_unique(array_map('strtolower', $this->all_selected_regions)) as $one_state)
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'add_rewrite_rules: adding ['.$one_state.']');

			add_rewrite_rule( '^(.*)/'.$one_state.'/?', 'index.php?p='.$this->CONTENT->get_rover_post_id(), 'top' );		/*	singlefamily/ma	*/
			add_rewrite_rule( '^'.$one_state.'/(.*)/?', 'index.php?p='.$this->CONTENT->get_rover_post_id(), 'top' );		/*	ma/brewster		*/
			}
		}

	public function init_block_theme_integration()
		{
		if (wp_is_block_theme())
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'DEBUG_BLOCKTEMPLATE wp_is_block_theme is [true]');

			if (is_null($this->path_by_page))	#	Not a static page
				{
				$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'DEBUG_BLOCKTEMPLATE is not a static page');

				if (false !== $this->check_url_for_idx_keys())
					{
					#	Your logic must run before the block template is resolved
					#	404 handling in block themes happens earlier than many developers expect.

					#	We are in a 404 loop - so set this block-theme page_template early

					$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'DEBUG_BLOCKTEMPLATE found a key - adding filter `pre_get_block_template`');

					add_filter( 'pre_get_block_templates', [ $this->CONTENT, 'pre_get_block_templates' ], 99, 3 );	#	block-based themes
					}
				}
			}
		else
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'DEBUG_BLOCKTEMPLATE wp_is_block_theme is [false]');
			}

		}

	public function init_third_party_integration()
		{
		require_once 'rover-third-party.php';

		$third									= new Rover_IDX_THIRDPARTY($this);
		$third->integrate();
		}

	public function check_url_for_idx_keys()	{

		$cache_key								= md5($this->curr_path);

		if (isset($this->slug_cache[$cache_key])) {
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'returning cached result');
			return $this->slug_cache[$cache_key];
	 		}

		//	Check if the requested page matches our target

		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'url ['.$this->curr_path.']');

		if (!is_null($this->found_slug)){
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'returning previously found slug ['.$this->found_slug.']');
			return $this->found_slug;
			}

		foreach ($this->curr_path_parts as $url_part)
			{
			#	So we don't serve up dynamic pages for example.com/2015/04/ma (it looks like Google likes
			#	to crawl these, and just specifying the state takes forever with MIDFLORIDA

#			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'Looking at urlpart '.$url_part);
#			$url_part							= str_replace('/', '', $url_part);

			$this->found_slug					= $this->match_slug($url_part);
			if ($this->found_slug !== false){
				$this->slug_cache[$cache_key]	= $this->found_slug;
				return $this->found_slug;
				}

			$this->found_slug					= $this->match_region_slug($url_part);
			if ($this->found_slug !== false){
				$this->slug_cache[$cache_key]	= $this->found_slug;
				return $this->found_slug;
				}

			$this->found_slug					= $this->match_standard_page_slug($url_part);
			if ($this->found_slug !== false){
				$this->slug_cache[$cache_key]	= $this->found_slug;
				return $this->found_slug;
				}
			}

		$this->found_slug						= false;

#		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'Does not match any slugs');

		return false;
		}

#	public function add_login_menu($items, $args)	{
#
#		if (!isset($this->roveridx_theming['menu_has_been_added']))
#			{
#			require_once 'rover-menus.php';
#
#			$menus								= new Rover_IDX_Menus();
#			return $menus->add_login_menu($items, $args);
#			}
#
#		return $items;
#		}

	public function fbml_add_namespace( $output ) {

		//	Does not W3C validate

		$output .= ' xmlns:fb="' . esc_attr(roverKEYS::FBML_NS_URI) . '"';

		return $output;
		}

	public function fb_og_type( $type ) {
		return (is_singular()) ? "article" : "blog";
		}

	public function init_admin()
		{
		$this->upgrade_options();

		add_action('admin_notices', function() {

			if ($error = get_transient('roveridx_activation_error')) {
				?>
				<div class="notice notice-error is-dismissible">
					<p><strong>Rover IDX Activation Error:</strong> <?php echo esc_html($error); ?></p>
				</div>
				<?php

				// Delete the transient so it only shows once
				delete_transient('roveridx_activation_error');
				}
			else if ($success = get_transient('roveridx_activation_success')) {
				?>
				<div class="notice notice-success is-dismissible">
					<p><?php echo esc_html($success); ?></p>
				</div>
				<?php
				delete_transient('roveridx_activation_success');
				}
			});

		add_action( 'update_option_permalink_structure' , array($this, 'permalinks_have_been_updated'), 10, 2 );

		require_once 'admin/rover-admin-init.php';

		new Rover_IDX_ADMINBASE($this);
		}

	public function init_front()
		{
		global						$wp, $post;

		$this->upgrade_options();

		$http_accept				= (isset($_SERVER['HTTP_ACCEPT']))
											? strtolower($_SERVER['HTTP_ACCEPT'])
											: "";
#		$is_valid_request			= (strpos($http_accept, "text/html") === false && strpos($http_accept, "*/*") === false)
#											? false
#											: true;
		$is_valid_request			= true;


		$the_page_clean				= (is_null($this->curr_path))
											? null
											: preg_replace('/[^a-zA-Z0-9]/', '', $this->curr_path);

		//	We don't seem to have access to $post->ID this early.  So we have to test if the page
		//	exists manually.  If it does exist, DO NOT EXECUTE the 404 code.

		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'Starting... [php version '.phpversion().']');
		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'REQUEST_URI ['.$_SERVER["REQUEST_URI"].']');
		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'parsed REQUEST_URI ['.$this->curr_path.']');
#		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'the_page_clean ['.$the_page_clean.']');

#		foreach (debug_backtrace() as $btdKey => $btdVal)
#			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, '['.$btdKey.'] File: '.$btdVal['file'].' / Function: '.$btdVal['function'].' / Line: '.$btdVal['line']);

#		$the_page					= get_page_by_path($this->curr_path, OBJECT);
		if (!is_null($this->path_by_page))
			{
			//	This page may contain one or more shortcodes

			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'path ['.$this->curr_path.'] exists in WP as page ['.$this->path_by_page->ID.']');
			}
		else if (empty($the_page_clean))
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'path ['.$this->curr_path.'] always exists and is not a 404');
			}
		else if (($part = $this->url_contains_troll_url_parts()) !== false)
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'path ['.$this->curr_path.'] contains troll part ['.$part.']');
			}
		else
			{
			if ($is_valid_request && !$this->checked_for_404_content)
				{
				$this->checked_for_404_content	= true;

				$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'Checking for 404 content');

				global							$wp_query;

				if (false !== $this->check_url_for_idx_keys())
					{
					#	For Rover dynamic pages - let Rover build the title / desc / canonical meta
					#	This needs to happen early enough in the process that SEO plugins accept the instruction

					require_once 'rover-third-party.php';
					$third						= new Rover_IDX_THIRDPARTY($this);
					$third->filters();

					if ($this->url_should_avoid_cache())
						{
						nocache_headers();

						$third->no_cache();
						}

					if ( defined( 'WPCF7_PLUGIN_DIR' ) )
						remove_action( 'init', 'wpcf7_init_block_editor_assets' );

					if ($wp_query && isset($wp_query->query_vars) && is_array($wp_query->query_vars))
						$wp_query->query_vars["error"]	= "";							#	Make sure this is not set to 404 until after we've checked for dynamic

					$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'path ['.$this->curr_path.'] does not exist in WP');

					$this->CONTENT->setup_404();

					//	https://roots.io/routing-wp-requests/

					if ($wp && isset($wp->query_vars))
						$wp->query_vars			= array('post_type' => 'page');
					}

				}
			else
				{
				$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'This REQUEST is looking for an image - ignore!');
				}
			}

		if ($is_valid_request) {
			require_once 'rover-shortcodes.php';
			$shortcodes				= new Rover_IDX_Shortcodes($this);
			$shortcodes->add_shortcodes();
			}
		else {
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'Not loading shortcodes. ['.$http_accept.'] is not a valid request.');
			}

		$widgets					= new Rover_IDX_Widgets($this);
		$widgets->init_widgets();
		}

	public function update_region_settings($fn, $ln, $key, $val)	{

		if (!is_array($region_options = get_option(roverOPTIONS::REGIONS)))
			$region_options			= array();

		foreach($region_options as $r_key => $r_val)
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, sprintf("[%s] [%s] [%s] => [%s]", $fn, $ln, $r_key, $r_val));

		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, sprintf("[%s] [%s] [%s] => [%s]", $fn, $ln, $key, $val));

		#	Remove old `slug` data
		$new_region_options			= array();
		foreach($region_options as $r_key => $r_val)
			{
			if ((strpos($r_key, "slug") === false) && ($r_val !== "region"))
				{
				$new_region_options[$r_key]	= $r_val;
				}
			else
				{
				$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, sprintf("[%s] [%s] new_region_options [remove] [%s] => [%s]", $fn, $ln, $r_key, $r_val));
				}
			}

		foreach($new_region_options as $r_key => $r_val)
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, sprintf("new_region_options [final] [%s] => [%s]", $r_key, $r_val));
			}

		$new_region_options[$key]	= $val;
		$ret						= update_option(roverOPTIONS::REGIONS, $new_region_options);

		foreach($region_options as $r_key => $r_val)
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, sprintf("[%s] [%s] [%s] => [%s]", $fn, $ln, $r_key, $r_val));

		$this->roveridx_regions		= $new_region_options;

		return $ret;
		}

	public function redirect_to_setup() {

		$this->update_region_settings(__FUNCTION__, __LINE__, 'redirect_for_setup', false);

		wp_safe_redirect( admin_url('admin.php?page=rover-idx') );

		exit;						#	This is not a REST endpoint, so exit is appropriate
		}

	public function cron_hourly() {

		require_once 'rover-social-common.php';

		$social						= new Rover_IDX_SOCIAL($this);
		$social->refresh();
		}

	public function cron_daily() {

		require_once 'rover-sitemap.php';

		$sitemap					= new Rover_IDX_SITEMAP($this);
		$sitemap->build();
		}

	public function rest_endpoints() {

		register_rest_route(
							self::IDX_REST_NAME,
							'/heartbeat',
							array(
								'methods'					=> 'GET',
								'callback'					=> function () {

																	return new WP_REST_Response(
																		array( 'status' => 'up' ),
																		200,
																		array(
																			'Content-Type' => 'text/html; charset=utf-8',
																			)
																		);
																	},
								'permission_callback'		=> function() {

																	if (strpos(gethostname(), 'roveridx.com') !== false)
																		return true;

																	return $this->is_allowed_ip();
																	}
								)
							);

		register_rest_route(
							self::IDX_REST_NAME,
							'/settings',
							array(
								'methods'					=> 'GET',
								'callback'					=> function () {

																	$the_html				= array();
																	$the_html[]				= '<table>';
																	foreach(array(
																				'Region'	=> roverOPTIONS::REGIONS,
																				'Styling'	=> roverOPTIONS::THEME,
																				'SEO'		=> roverOPTIONS::SEO,
																				'Social'	=> roverOPTIONS::SOCIAL,
																				)
																			as $label => $option_key)
																		{
																		if (!is_array($opts = get_option($option_key)))
																			$opts			= array();

																		$the_html[]			=	sprintf('<tr><td colspan=2><h4 style="margin:20px 0 0 0;">%s</h4></td></tr>', $label);
																		foreach ($opts as $key => $val) {
																			$the_html[]		=	sprintf('<tr><td>%s</td><td><pre>%s</pre></td></tr>', $key, print_r($val, true));
																			}
																		}
																	$the_html[]				= '</table>';
																	$the_html[]				= '<div style="color:#666;margin:40px 0;">';
																	$the_html[]				=	'<div>Request from <strong>'.$_SERVER['REMOTE_ADDR'].'</strong></div>';
																	$the_html[]				=	'<div>Origin <strong>'.$_SERVER['SERVER_NAME'].'</strong></div>';
																	$the_html[]				= '</div>';

																	return new WP_REST_Response(
																		implode('', $the_html),
																		200,
																		array(
																			'Content-Type' => 'text/html; charset=utf-8',
																			)
																		);

																	},
								'permission_callback'		=> function() { return $this->is_allowed_ip(); }
								)
							);

		register_rest_route(
							self::IDX_REST_NAME,
							'/settings/refresh/regions',
							array(
								'methods'					=> 'POST',
								'callback'					=> function ($request) {


																	$regions_str				= $this->CONTENT->build_regions_string();
																	$regions					= json_decode($regions_str, true);

																	$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, ' build_regions_string ['.print_r($regions, true).']');

#																	if (is_array($regions))
#																		{
#																		if (isset($regions['domain_id']))
#																			{
#																			$this->update_region_settings(__FUNCTION__, __LINE__, 'domain_id', $regions['domain_id']);
#																			}
#
#																		if (isset($regions['regions']))
#																			{
#																			$this->update_region_settings(__FUNCTION__, __LINE__, 'regions', $regions['regions']);
#																			$this->update_region_settings(__FUNCTION__, __LINE__, 'region', $regions['regions']);
#																			}
#																		}

																	return new WP_REST_Response(
																		$regions_str,
																		200,
																		array(
																			'Content-Type' => 'text/html; charset=utf-8',
																			)
																		);

																	},
								'permission_callback'		=> function() { return $this->is_allowed_ip(); }

								)
							);

		#	'(?P<id>\\d+)' // the full argument (parathenses are to group it)
		#	'?P' // denotes that this is a parameter
		#	'<id>' // the name of the parameter
		#	'\\d+' // indicates the paramter should be an integer

		#	(?P<search>[a-zA-Z\s]+)
		#	'?P'			#	denotes that this is a parameter
		#	'<search>'		// the name of the parameter
		#	'[a-zA-Z\s]'	// indicates the parameter can only be lowercase a-z
		#	'[\S]'			#	indicates the parameter can be [A-Za-z0-9]	(works)

#		register_rest_route(
#							self::IDX_REST_NAME,
#							'/settings/(?P<key>[a-z_-]+)',
#							array(
#								'methods'					=> 'GET',
#								'callback'					=> function ($request) {
#
#																	$result						= 'key is null';
#																	$key						= (isset($request['key']))
#																										? $request['key']
#																										: null;
#
#																	if (!is_null($key))
#																		{
#																		$result					= $key.' is not set';
#																		if (is_array($rr = get_option(roverOPTIONS::REGIONS)))
#																			{
#																			if (isset($rr[$key]))
#																				{
#																				$result			= $key.' is set to `'.$rr[$request['key']].'`';
#																				}
#																			}
#
#																		}
#
#																	return new WP_REST_Response(
#																		$result,
#																		200,
#																		array(
#																			'Content-Type' => 'text/html; charset=utf-8',
#																			)
#																		);
#
#																	},
#								'permission_callback'		=> function() { return ''; }
#								)
#							);
#
#		register_rest_route(
#							self::IDX_REST_NAME,
#							'/settings/(?P<key>[a-z_-]+)/unset',
#							array(
#								'methods'					=> 'POST',
#								'callback'					=> function ($request) {
#
#																	$result						= 'key is null';
#																	$key						= (isset($request['key']))
#																										? $request['key']
#																										: null;
#
#																	if (!is_null($key))
#																		{
#																		if (is_array($rr = get_option(roverOPTIONS::REGIONS)))
#																			{
#																			if (isset($rr[$key]))
#																				{
#																				unset($rr[$key]);
#
#																				update_option(roverOPTIONS::REGIONS, $rr);
#
#																				$result			= $key.' has been unset';
#																				}
#																			}
#																		else
#																			{
#																			$result				= $key.' was not set';
#																			}
#																		}
#
#																	return new WP_REST_Response(
#																		$result,
#																		200,
#																		array(
#																			'Content-Type' => 'text/html; charset=utf-8',
#																			)
#																		);
#
#																	},
#								'permission_callback'		=> function() { return ''; }
#								)
#							);

		register_rest_route(
							self::IDX_REST_NAME,
							'/settings/forced_encoding/unset',
							array(
								'methods'					=> 'POST',
								'callback'					=> function ($request) {

																	$key						= 'forced_encoding';
																	$result						= '';
																	if (is_array($rr = get_option(roverOPTIONS::REGIONS)))
																		{
																		if (isset($rr[$key]))
																			{
																			unset($rr[$key]);

																			update_option(roverOPTIONS::REGIONS, $rr);

																			$result				= $key.' has been unset';
																			}
																		else
																			{
																			$result				= $key.' was not set';
																			}
																		}

																	return new WP_REST_Response(
																		$result,
																		200,
																		array(
																			'Content-Type' => 'text/html; charset=utf-8',
																			)
																		);
																	},
								'permission_callback'		=> function() { return $this->is_allowed_ip(); }
								)
							);

		register_rest_route(
							self::IDX_REST_NAME,
							'/settings/clear_site_cache',
							array(
								'methods'					=> 'GET',
								'callback'					=> function () {

																	$clear_status				= [];

																	#	Astra Cache
																	if ( function_exists( 'astra_clear_all_assets_cache' ) ) {
																		astra_clear_all_assets_cache();
																		$clear_status[]			= 'Astra cache cleared';
																		}

																	#	Breeze - developed by Cloudways
																	if ( function_exists( 'do_action' ) && has_action( 'breeze_clear_all_cache' ) ) {
																		do_action( 'breeze_clear_all_cache' );
																		$clear_status[]			= 'Breeze cache cleared';
																		}

																	#	Comet Cache
																	if ( class_exists( 'comet_cache' ) ) {
																		comet_cache::clear();
																		$clear_status[]			= 'Comet cache cleared';
																		}

																	#	Elementor
																	if ( class_exists( '\Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance ) ) {
																		\Elementor\Plugin::$instance->files_manager->clear_files_data();	#	clears the CSS and data files
																		$clear_status[]			= 'Comet cache cleared';
																		}

																	#	LiteSpeed
																	if ( class_exists( '\LiteSpeed\Purge' ) ) {
																		\LiteSpeed\Purge::purge_all();
																		$clear_status[]			= 'LiteSpeed cache cleared';
																		}

																	#	WP Engine - unlikely this will work, as WP Engine disables rest endpoints
																	if ( class_exists( 'wpecommon' ) ) {
																		wpecommon::purge_varnish_cache();
																		$clear_status[]			= 'WP Engine Cache cleared';
																		}

																	#	WP Fastest Cache
																	if ( function_exists( 'wpfc_clear_all_cache' ) ) {
																		wpfc_clear_all_cache();
																		$clear_status[]			= 'WP Fastest Cache cleared';
																		}

																	#	WP Rocket
																	if ( function_exists('rocket_clean_domain' ) ) {
																		#	Now that we know it is available, let's clear the cache
																		rocket_clean_domain();
																		$clear_status[]			= 'WP Rocket cache cleared';
																		}

																	#	WP Super Cache
																	if ( function_exists( 'wp_cache_clear_cache' ) ) {
																		wp_cache_clear_cache();
																		$clear_status[]			= 'WP Super Cache cleared';
																		}

																	#	W3 Total Cache
																	if ( function_exists('w3tc_flush_all()' )) {
																		w3tc_flush_all()();
																		$clear_status[]			= 'W3 Total Cache cleared';
																		}

																	#	Clear the WordPress Object Cache
																	wp_cache_flush();
																	$clear_status[]				= 'WP Object Cache cleared';

																	return new WP_REST_Response(
																		array( 'status' => implode(', ', $clear_status) ),
																		200,
																		array(
																			'Content-Type' => 'text/html; charset=utf-8',
																			)
																		);
																	},
								'permission_callback'		=> function() { return $this->is_allowed_ip(); }
								)
							);

		register_rest_route(
							self::IDX_REST_NAME,
							'/settings/refresh_js_ver',
							array(
								'methods'					=> 'GET',
								'callback'					=> function () {

																	require_once 'rover-scheduled.php';

																	$scheduled				= new Rover_IDX_Schedule($this);

																	return new WP_REST_Response( $scheduled->refresh_js_ver($force_refresh = true), 200 );
																	},
								'permission_callback'		=> function() { return $this->is_allowed_ip(); }
								)
							);

		register_rest_route(
							self::IDX_REST_NAME,
							'/sitemap',
							array(
								'methods'					=> 'GET',
#								'callback'					=> array($this, 'cron_daily'),
								'callback'					=> function () {
																	require_once 'rover-sitemap.php';

																	$sitemap		= new Rover_IDX_SITEMAP($this);

																	$ret			= $sitemap->build($force_refresh = true);
																	if (isset($ret['log']))
#																		return new WP_REST_Response( str_replace("<br>", "\r\n", $ret['log']), 200 );
																		return new WP_REST_Response(
																			$ret['log'],
																			200,
																			array(
																				'Content-Type' => 'text/html; charset=utf-8',
																				)
																			);

																	return new WP_Error( 'sitemap-fail', __( 'message', '<pre>'.print_r($ret, true).'</pre>' ), array( 'status' => 500 ) );
																	},
								'permission_callback'		=> function() { return $this->is_allowed_ip(); }
								)
							);

		register_rest_route(
							self::IDX_REST_NAME,
							'/fub',
							array(
								'methods'					=> 'POST',
								'callback'					=> function () {

																	$ret		= $this->CONTENT->rover_content('webhook_fub', $_POST);

																	return new WP_REST_Response(
																		$ret,
																		200,
																		array(
																			'Content-Type' => 'text/html; charset=utf-8',
																			)
																		);

																	},
								'permission_callback'		=> function() { return ''; }
								)
							);

		}

	public function idx_site_posts_callback()	{

		check_ajax_referer(roverKEYS::NONCE_SS, 'security');

		global 						$wpdb;

		$have_all_pages				= true;
		$limit						= 250;

		$rows_pages					= array();
		$sql						= array();
		$sql[]						= "SELECT ID, post_title";
		$sql[]						= "FROM $wpdb->posts";
		$sql[]						= "WHERE post_type = 'page'";
		$sql[]						= "AND post_status = 'publish'";
		if (isset($_POST['search_text'])) {
			$like					= '%' . $wpdb->esc_like(sanitize_text_field($_POST['search_text'])) . '%';
			$sql[]					= $wpdb->prepare("AND (post_title LIKE %s OR post_content LIKE %s)", $like, $like);
			}
		$sql[]						= "LIMIT ".$limit;
		$rows						= $wpdb->get_results(implode(' ', $sql));
		foreach($rows as $row)
			{
			$rows_pages[]			= array(
									'key'				=> 'page',
									'name'				=> $row->post_title,
									'redirect'			=> get_permalink($row->ID)
									);
			}
		if (count($rows) === $limit)
			$have_all_pages			= false;

		$rows_post					= array();
		$sql						= array();
		$sql[]						= "SELECT ID, post_title";
		$sql[]						= "FROM $wpdb->posts";
		$sql[]						= "WHERE post_type = 'post'";
		$sql[]						= "AND post_status = 'publish'";
		if (isset($_POST['search_text'])){
			$like					= '%' . $wpdb->esc_like(sanitize_text_field($_POST['search_text'])) . '%';
			$sql[]					= $wpdb->prepare("AND (post_title LIKE %s OR post_content LIKE %s)", $like, $like);
			}
		$sql[]						= "LIMIT ".$limit;
		$rows						= $wpdb->get_results(implode(' ', $sql));
		foreach($rows as $row)
			{
			$rows_post[]			= array(
									'key'				=> 'post',
									'name'				=> $row->post_title,
									'redirect'			=> get_permalink($row->ID)
									);
			}

		if (count($rows) === $limit)
			$have_all_pages			= false;

		wp_send_json_success(
			[
				'pages'				=> $rows_pages,
				'posts'				=> $rows_post,
				'have_all_pages'	=> $have_all_pages,
			]);
		}

	public function add_attributes_to_script($tag, $handle, $src)	{

#		if ( 'rover-fontawesome-js' === $handle ) {
#			$tag = '<script type="text/javascript" src="' . esc_url( $src ) . '" defer class="fontawesome-js" crossorigin="anonymous"></script>';
#			}

		if ( 'rover-idx-block-data' === $handle ) {
			$tag					 = str_replace( 'type="text/javascript"', 'type="module"', $tag );
			}

		return $tag;
		}

	public function robots() {
//		header( 'Content-Type: text/plain; charset=utf-8' );

		$sitemap_opts				= get_option(roverOPTIONS::SEO);

		if ($sitemap_opts === false)
			return;

		if (!is_array($sitemap_opts))
			return;

		do_action( 'do_robotstxt' );

		$output						= null;
		$public						= get_option( 'blog_public' );
		if ( '0' != $public ) {

			foreach ($this->all_selected_regions as $one_region => $region_slugs)
				{
				if (isset($sitemap_opts[$one_region]))
					{
					$output .= "Sitemap: ".$sitemap_opts[$one_region]['url']."\n";
					}
				}

			}

		echo apply_filters('robots_txt', $output, $public);
		}


	public function load_css_and_preload() {

#		echo "<link rel='dns-prefetch' href='https://ep4.roveridx.com' />\n";
		echo "<link rel='preconnect' href='https://ep4.roveridx.com' crossorigin>\n";
#		echo "<link rel='preconnect' href='https://cdn-cciaor.roveridx.com/'>\n";
#		echo "<link rel='preconnect' href='https://s3.us-west-1.wasabisys.com/' crossorigin>\n";
		echo "<link rel='dns-prefetch' href='https://maps.googleapis.com' crossorigin>\n";

		#	Cloudflare ips come to us duplicated
		#	2a06:98c0:3600::103, 2a06:98c0:3600::103

		$user_ip					= array_unique( array_map('trim', explode(',', $_SERVER["REMOTE_ADDR"])) );
		$user_ip					= (is_array($user_ip) && isset($user_ip[0]))
											? $user_ip[0]
											: $_SERVER["REMOTE_ADDR"];

		$idx_sa			= array(
									"all_regions"			=> implode(",", array_keys($this->all_selected_regions)),
									"css_framework"			=> 'rover',
									"domain"				=> $this->COMMON->clean_domain(get_site_url()),
									"domain_id"				=> $this->roveridx_regions['domain_id'],
									"fav_requires_login"	=> "open",
									"is_multi_region"		=> ((count($this->all_selected_regions) > 1) ? 'true' : 'false'),
									"idx_url"				=> 'https://ep4.roveridx.com/',
									"items"					=> 25,
									"logged_in_email"		=> "",
									"logged_in_user_id"		=> "",
									"logged_in_authkey"		=> "",
									"page_url"				=> "/",
									"pdf_requires_login"	=> "open",
									"prop_anon_views_curr"	=> 0,
									"prop_open_mode"		=> "modal",
									"prop_requires_login"	=> "open",
									"region"				=> implode(",", array_keys($this->all_selected_regions)),
									"register_before_or_after_prop_display"	=> "after",
									"user_ip"				=> $user_ip,
									"joda"					=> array(
															".rover-range-slider|rover_ui_rangeslider"
															),
									"jodb"					=> array(
															"nav.idx-searchpanel|rover_ui_search",
															".ssc|rover_ui_slideshow",
															".tsc|rover_ui_slideshow",
															".lcc|rover_ui_carousel",
															".recaptcha-container|rover_captcha",
															".idx-tab-container|rover_ui_tabs",
															"#send_property_contact|rover_ui_contact",
															".rover-thumbnail|rover_ui_thumbnail"
															),
									"js_modern"				=> array("rover_ui_boot")
									);

		echo "<script type='text/javascript'>var idx_sa = ".json_encode($idx_sa)."</script>\n";

		echo "<style class='idx-rover'>".get_option(self::OPTIONS_KEY_CSS_DEFAULT)."</style>\n";
		}

	public function load_js() {

		$js_ver						= (isset($this->roveridx_theming['js_version']) && !empty($this->roveridx_theming['js_version']))
											? $this->roveridx_theming['js_version']
											: roverVERSIONS::JS;
		$js_head					= (isset($this->roveridx_theming['js_load']) && ($this->roveridx_theming['js_load'] == 'body'))
											? 'body'
											: 'head';

		#	https://js.roveridx.com/4.0.0/js/1800002/rover.min.js
		$js_url						= (isset($_GET['jscdn']))
											? sprintf("https://ep4.roveridx.com/js/rover.js")
											: sprintf("https://ep4.roveridx.com/js/%s/rover.min.js", $js_ver);

		$the_js						= array();
		$the_js[]					= '<script type="text/javascript" class="rover_idx_boot_js">';
		$the_js[]					= 'function rover_idx_boot_js() {';
		$the_js[]					=	'var element = document.createElement("script");';
		$the_js[]					=	'element.src = "'.$js_url.'";';
		$the_js[]					=	'element.className = "roveridx";';
		$the_js[]					=	'element.setAttribute("data-js_ver", "'.$js_ver.'");';
		$the_js[]					=	'document.'.$js_head.'.appendChild(element);';
		$the_js[]					=	'}';

		$the_js[]					=	'{window.addEventListener("load", rover_idx_boot_js, false)}';
		$the_js[]					= '</script>';

		echo implode('', $the_js);
		}

	private function is_allowed_ip(): bool {
		$ip							= ip2long($_SERVER['REMOTE_ADDR']);
		return $ip >= ip2long(self::ROVER_IP_RANGE_START) && $ip <= ip2long(self::ROVER_IP_RANGE_END);
		}

	private function build_slug_lookup() {

		if ($this->slug_lookup !== null) {
			return;
			}

		$this->slug_lookup					= [];

		foreach ($this->all_selected_regions as $one_region => $region_slugs) {
			foreach (explode(',', $region_slugs) as $one_slug) {

				if (empty($one_slug)) continue;

				$key						= strtolower($one_slug);

				$entry						= ['region' => $one_region, 'slug' => $one_slug];

				$this->slug_lookup[$key][]	= $entry;

				$key_with_spaces			= strtolower(str_replace('-', ' ', $one_slug));
				if ($key !== $key_with_spaces) {
					$this->slug_lookup[$key_with_spaces][] = $entry;
					}
				}
			}
		}

	private function get_standard_slugs_lookup() {

		if ($this->standard_slugs_lookup !== null) {
			return $this->standard_slugs_lookup;
			}

		$page_type_slugs = roverSLUGS::DEFAULT;
		if (isset($this->roveridx_regions['exclude_slugs']) && !empty($this->roveridx_regions['exclude_slugs'])) {
			$page_type_slugs = array_diff($page_type_slugs, explode(',', $this->roveridx_regions['exclude_slugs']));
			}

		// Create a hash map for O(1) lookup
		$this->standard_slugs_lookup = array_flip($page_type_slugs);
		return $this->standard_slugs_lookup;
		}

	private function match_slug($url_part) {
		$this->build_slug_lookup();

		$key									= strtolower($url_part);
		$key_with_spaces						= strtolower(str_replace('-', ' ', $url_part));

		$found_regions							= [];
		$found_slugs							= [];

		foreach ($this->slug_lookup[$key] ?? [] as $match) {
			$found_regions[]					= $match['region'];
			$found_slugs[]						= $match['slug'];
			}

		// Only check spaces variant if it's actually different
		if ($key !== $key_with_spaces) {
			foreach ($this->slug_lookup[$key_with_spaces] ?? [] as $match) {
				$found_regions[]				= $match['region'];
				$found_slugs[]					= $match['slug'];
				}
			}

		if (count($found_regions)) {
			$this->rover_404_regions			= implode(',', array_unique($found_regions));
			$this->rover_404_slugs				= implode(',', array_unique($found_slugs));
			return $found_slugs[0];
			}

		return false;
		}

	private function match_region_slug($url_part)
		{
		$matched_parts							= array();
		foreach ($this->all_selected_regions as $one_region => $region_slugs)
			{
			foreach(explode(',', $url_part) as $one_segment_of_part)
				{
				$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'Comparing '.$url_part.' to '.$one_region);

				if (strcasecmp($one_segment_of_part, $one_region) === 0)
					{
					$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, 'Found slug ('.$one_region.')');
					$matched_parts[]			= $one_region;
					}
				}
			}

		if (count($matched_parts))
			{
			$this->rover_404_regions			= implode(',', $matched_parts);
			$this->rover_404_slugs				= implode(',', $matched_parts);

			return $matched_parts;
			}

		return false;
		}

	private function match_standard_page_slug($url_part)
		{
		if ($url_part === '')
			return false;

		#	Check standard slug prefixes first (most common case)
		$first_char								= $url_part[0];
		if ($first_char === 'r' && strpos($url_part, 'rover-') === 0) {
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, $url_part.' may be standard slug');
			return $url_part;
			}
		if ($first_char === 'i' && strpos($url_part, 'idx-') === 0) {
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, $url_part.' may be standard slug');
			return $url_part;
			}


		$lookup									= $this->get_standard_slugs_lookup();
		if (isset($lookup[$url_part])) {
			$this->rover_404_regions			= implode(',', array_keys($this->all_selected_regions));
			$this->rover_404_slugs				= implode(',', array_keys($lookup));
			return $url_part;
			}

		return false;
		}

	private function get_selected_regions()	{

		$region_data							= array();
		if ($this->roveridx_regions === false || !is_array($this->roveridx_regions))
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, '[regions] is not set!');
			return $region_data;
			}

		if (isset($this->roveridx_regions['regions']))
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, '[regions] ['.$this->roveridx_regions['regions'].']');

			/*
				Single region:
					regions				INTERMOUNTAIN|ID|OR					( region|st|st|st )
				Multi-regions will look like:
					regions				INTERMOUNTAIN|ID|OR||BAINMLS|ID		( region|st||region|st )
			*/

			foreach(explode('||', $this->roveridx_regions['regions']) as $one_region)
				{
				$region_parts					= explode('|', $one_region);

				$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, '[regions] ['.print_r($region_parts, true).']');

				$region							= $region_parts[0];
				$region_data[$region]			= implode(',', array_slice($region_parts, 1));

				if (is_null($this->first_selected_region))
					$this->first_selected_region	= $region;
				}
			}

		return $region_data;
		}

	private function upgrade_options()	{

		$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, ' ');
		$perform_rebuild						= false;

		if (!isset($this->roveridx_regions['api_key']))
			{
			return false;						/*	initial setup not complete	*/
			}

		if (!isset($this->roveridx_regions['domain_id']))
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, ' `domain_id` is not set');
			$perform_rebuild					= true;
			}
		else if ($this->roveridx_regions['domain_id'] === 0)
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, ' `domain_id` is 0');
			$perform_rebuild					= true;
			}

		/*	Migrate regions to new schema in roverOPTIONS::REGIONS	*/

		if (!isset($this->roveridx_regions['regions']))
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, ' `regions` is not set');

			$perform_rebuild					= true;
			}
		else if (empty($this->roveridx_regions['regions']))
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, ' `regions` is empty');

			$perform_rebuild					= true;
			}

		if ($perform_rebuild)
			{
			$this->CONTENT->rebuild_regions($force = true);
			}

		$site_version						= (isset($this->roveridx_theming['site_version']) && !empty($this->roveridx_theming['site_version']))
													? $this->roveridx_theming['site_version']
													: null;

		if (is_null($site_version) || (version_compare(roverVERSIONS::VERSION, $site_version) === 1))
			{
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, ' site_version ['.roverVERSIONS::VERSION.'] / ['.$site_version.']');

			if (!is_array($theme_opts = get_option(roverOPTIONS::THEME)))
				$theme_opts					= array();

			#	We no longer need Bootstrap
			$theme_opts['css_framework']	= 'rover';
			$this->CONTENT->update_site_settings(array('css_framework'	=> 'rover'));

			$theme_opts['site_version']		= roverVERSIONS::VERSION;

			update_option(roverOPTIONS::THEME, $theme_opts);

			$this->roveridx_theming			= $theme_opts;

			add_action('admin_notices',	array($this, 'admin_notice_upgraded'));
			}

		$css								= get_option(self::OPTIONS_KEY_CSS_DEFAULT);

		if ($css === false)
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, '['.self::OPTIONS_KEY_CSS_DEFAULT.'] does not exist');
		else
			$this->COMMON->error_log(__FILE__, __FUNCTION__, __LINE__, '['.self::OPTIONS_KEY_CSS_DEFAULT.'] is currently ['.strlen($css).'] bytes');

		if (empty($css) || (strpos($css, 'Unable to communicate with server') !== false))
			$this->refresh_css();
		}

	private function url_should_avoid_cache()
		{
		$nocache_urls				= array(
											"rover-control-panel"			=> true,
											"idx-control-panel"				=> true,
											"rover-custom-listing-panel"	=> true,
											"idx-custom-listing-panel"		=> true,
											"rover-login-panel"				=> true,
											"idx-login-panel"				=> true,
											);

		foreach ($this->curr_path_parts as $url_part)
			{
			if (isset($nocache_urls[strtolower($url_part)]))
				return true;
			}

		return false;
		}

	private function url_contains_troll_url_parts()
		{
		$known_troll_parts			= array(
											"exclusive-dorder"				=> true,
											"price-dorder"					=> true,
											"prop-url"						=> true
											);

		foreach ($this->curr_path_parts as $url_part)
			{
			if (isset($known_troll_parts[strtolower($url_part)]))
				return $url_part;
			}

		return false;
		}
	}


#	Get $rover_idx in:
#	- blocks\listings.php
#	- templates\naked_page.php

function rover_idx_resolve() {
	return apply_filters( 'rover_idx_resolve', null );
	}


global			$rover_idx;

if (!is_object($rover_idx))
	{
	$rover_idx	= new Rover_IDX();
	}


?>