<?php

class Rover_IDX_SITEMAP {

	private Rover_IDX $rover_idx;
	private	$sitemap_opts					= null;
	private	$debug_log						= null;

	private const BUILD_TIME_LIMIT			= 300;  // 5 minutes
	private const SEARCH_ENGINE_PING_TIMEOUT	= 3;

	public function __construct(Rover_IDX $rover_idx)
		{
		$this->rover_idx						= $rover_idx;
		}

	public function build($force_refresh = false)
		{
		set_time_limit(self::BUILD_TIME_LIMIT);				#	60 = 1 minutes / 600 = 10 minutes / 1200 = 20 minutes

		$this->debug_log					= array();
		$this->sitemap_opts					= get_option(roverOPTIONS::SEO);
		$successful_notifications			= array();

		$this->log(__FUNCTION__, __LINE__, 'Starting...');

		if ($this->sitemap_is_disabled())
			{
			$this->log(__FUNCTION__, __LINE__, 'Sitemap refresh is disabled');
			return array(
						'success'			=> false,
						'blah'				=> 1,
						'log'				=> implode("<br>", array_map('esc_html', $this->debug_log)),
						);
			}

		foreach ($this->rover_idx->all_selected_regions as $one_region => $region_slugs)
			{
			if ($this->should_we_build_new_sitemap($one_region, $force_refresh))
				{
				if (($result_decoded = $this->fetch_sitemap_data($one_region)) === false)
					{
					return array(
						'success'			=> false,
						'log'				=> implode("<br>", array_map('esc_html', $this->debug_log)),
						);
					}

				$this->log(__FUNCTION__, __LINE__, 'sitemap = '.$result_decoded['sitemap_gz_url']	);
				$this->log(__FUNCTION__, __LINE__, 'count			= '.$result_decoded['count']);

				/*
					Create the sitemap file
				*/

				$sitemap_url_gz				= $this->sitemap_file_write($result_decoded);

				$search_engines				= array(
													'Google'	=> 'https://www.google.com/ping?sitemap=',
													'Bing'		=> 'https://www.bing.com/webmaster/ping.aspx?siteMap=',
													);

#	Shut down in 2011								'Yahoo'		=> 'http://search.yahooapis.com/SiteExplorerService/V1/ping?sitemap=',	//	Yahoo has merged with Bing ??
#	Defunct											'Ask'		=> 'http://submissions.ask.com/ping?sitemap='

				/*
					http://freds_real_estate.com/wp-content/uploads/rover_idx_sitemap/rover_sitemap_DESM.xml.gz
				*/

				foreach ($search_engines as $search_engine_name => $search_engine_submission_url)
					{
#					$ping_url				= $search_engine_submission_url.$sitemap_url_gz;
					$ping_url				= $search_engine_submission_url . rawurlencode($sitemap_url_gz);


					if ($this->notify_search_engine($ping_url))
						{
						$successful_notifications[] = $search_engine_name;

						$this->log(__FUNCTION__, __LINE__, $sitemap_url_gz.' submitted to '.$search_engine_name);
						}
					else
						{
						$this->log(__FUNCTION__, __LINE__, 'Attempt to ping '.$search_engine_name.' using '.$ping_url.' has failed');
						}
					}

				// Extract just filename from path (no query strings)
				$filename					= basename(parse_url($sitemap_url_gz, PHP_URL_PATH));

				// Validate format
				if (!preg_match('/^rover_sitemap_[A-Z0-9]+(-\d+)?\.(xml|gz)$/', $filename)) {
					$this->log(__FUNCTION__, __LINE__, 'Invalid sitemap filename: ' . $filename);
					continue;
					}

				$this->sitemap_opts[$one_region]['url']			= $filename;

				//	'desc' was set, above

				$this->sitemap_opts[$one_region]['timestamp']	= date('M d Y H:i:s');
				$this->sitemap_opts[$one_region]['desc']		= number_format($result_decoded['count']).' properties';

				update_option(roverOPTIONS::SEO, $this->sitemap_opts);
				}
			}

		return array(
					'success'	=> ((count($successful_notifications)) ? true : false),
					'log'		=> implode("<br>", array_map('esc_html', $this->debug_log)),
					);
		}

	public function history()
		{
		$include_region_label				= (count($this->rover_idx->all_selected_regions) > 1) ? true : false;
		$never								= true;
		$wp_settings						= get_option(roverOPTIONS::SEO);
		$domain								= get_site_url();

		$the_html							= array();
		$the_html[]							= '<div class="container-sitemap-history">';

		foreach ($this->rover_idx->all_selected_regions as $one_region => $region_slugs)
			{
			if (isset($wp_settings[$one_region]))
				{
				$never						= false;
				$filename					= isset($wp_settings[$one_region]['url']) ? $wp_settings[$one_region]['url'] : '';


				$the_html[]					=	'<div class="idx-row">';

				if ($include_region_label)
					$the_html[]				=		'<div class="idx-col-12">['.$one_region.']</div>';

				$the_html[]					=		'<div class="idx-col-12"><span style="color:green;">'.esc_html( $wp_settings[$one_region]['timestamp'] ).'</span><span> / '.esc_html($wp_settings[$one_region]['desc']).'</span></div>';
				$the_html[]					=		'<div class="idx-col-12"><a href="/'.esc_attr($filename).'" target="_blank">'.esc_attr($filename).'</a></div>';

				$the_html[]					=	'</div><!-- row -->';
				}
			}

		if ($never)
			$the_html[]						=	'<div style="font-weight:bold;">Never</div>';

		$the_html[]							= '</div><!-- container -->';

		return array(
					'html'					=> implode('', $the_html),
					'never'					=> $never
					);
		}

	private function sitemap_is_disabled()
		{
		if (
			is_array($this->sitemap_opts) &&
			isset($this->sitemap_opts['sitemap_enabled']) &&
			($this->sitemap_opts['sitemap_enabled'] === "disabled")
			)
			{
			$this->log(__FUNCTION__, __LINE__, 'Sitemap refresh is disabled');
			return true;
			}

		return false;
		}

	private function should_we_build_new_sitemap($one_region, $force_refresh = false)
		{
		$build_it							= true;
		$today_start						= strtotime('today');

		if ($force_refresh)
			{
			$this->log(__FUNCTION__, __LINE__, 'force_refresh is true.  We will build a sitemap');
			return true;
			}

		if (is_array($this->sitemap_opts) &&
			isset($this->sitemap_opts[$one_region]) &&
			isset($this->sitemap_opts[$one_region]['timestamp']))
			{
			$last_successful_date	= strtotime($this->sitemap_opts[$one_region]['timestamp']);

			if ($last_successful_date >= $today_start)
				{
				$this->log(__FUNCTION__, __LINE__, 'We already built a '.$one_region.' sitemap today (on '.$this->sitemap_opts[$one_region]['timestamp'].')');
				$build_it	= false;
				}
			else
				{
				$this->log(__FUNCTION__, __LINE__, 'timestamp '.$this->sitemap_opts[$one_region]['timestamp'].' is not today.  We will build a sitemap');
				}
			}
		else
			{
			if (is_array($this->sitemap_opts))
				{
				if (isset($this->sitemap_opts[$one_region]) && !isset($this->sitemap_opts[$one_region]['timestamp']))
					$this->log(__FUNCTION__, __LINE__, '"timestamp" is not a key in sitemaps_opts['.$one_region.'] - we will build sitemap');
				else if (isset($this->sitemap_opts[$one_region]))
					$this->log(__FUNCTION__, __LINE__, $one_region.' is not a key in sitemaps_opts - we will build sitemap');
				}
			else
				{
				$this->log(__FUNCTION__, __LINE__, 'sitemaps_opts is not an array - we will build sitemap');
				}
			}

		return $build_it;
		}

	private function fetch_sitemap_data($one_region)
		{
		$perm								= get_option('permalink_structure');
		$url_ends_with_slash				= ($perm && substr($perm, -1) != '/')
													? false
													: true;

		$rover_content						= $this->rover_idx->CONTENT->rover_content(
																				'rover-seo-regenerate-sitemap',
																				array(
																					'region'				=> $one_region,
																					'regions'				=> implode(',', array($one_region)),
																					'url_ends_with_slash'	=> $url_ends_with_slash,
																					'sitemap_domain'		=> get_site_url(),
																					'is_multisite'			=> (is_multisite() ? true : false)
																					)
																				);

		$result_decoded						= json_decode($rover_content['the_html'], true);

		if ($result_decoded === null)
			{
			$this->log(__FUNCTION__, __LINE__, 'xml did not decode correctly');
			$this->log(__FUNCTION__, __LINE__, print_r($rover_content, true));
			return false;
			}

		if (!is_array($result_decoded))
			{
			$this->log(__FUNCTION__, __LINE__, 'Output is not an array - aborting sitemap creation');
			return false;
			}

		return $result_decoded;
		}

	private function sitemap_file_write($result_decoded)
		{
		$return_gz_url						= $result_decoded['sitemap_gz_url'];

		if (
			isset($result_decoded['all_sitemaps']) &&
			isset($result_decoded['all_sitemaps']['version'])
#			version_compare($result_decoded['all_sitemaps']['version'], '3.0.0.2907', 'gt')
			)
			{
			if (
				isset($result_decoded['all_sitemaps']['sitemaps']) &&
				is_array($result_decoded['all_sitemaps']['sitemaps']) &&
				isset($result_decoded['all_sitemaps']['sitemap_index']) &&
				is_array($result_decoded['all_sitemaps']['sitemap_index'])
				)
				{
				##################################################################################
				#	This will copy each of the indexed (1,000 entries per file) sitemaps (gz & xml)
				#	rover_sitemap_CCIAOR-0.xml
				##################################################################################

				foreach($result_decoded['all_sitemaps']['sitemaps'] as $one_sitemap)
					{
					if (isset($one_sitemap['xml']))
						$this->copy_file_to_local($one_sitemap['xml']);
					if (isset($one_sitemap['gz']))
						$this->copy_file_to_local($one_sitemap['gz']);
					}

				##################################################################################
				#	This will copy the single sitemap index that references each indexed sitemap
				##################################################################################

				foreach($result_decoded['all_sitemaps']['sitemap_index'] as $one_sitemap)	#	There is only one of these
					{
					if (isset($one_sitemap['xml']))
						$this->copy_file_to_local($one_sitemap['xml']);
					if (isset($one_sitemap['gz']))
						{
						$this->copy_file_to_local($one_sitemap['gz']);
						$return_gz_url		= $one_sitemap['gz'];
						}
					}
				}
			}

		return $return_gz_url;
		}

	function copy_file_to_local($dest_url)
		{
		#	Validate URL is from trusted domain
		$parsed_url							= parse_url($dest_url);
		if (!$parsed_url || !isset($parsed_url['host']))
			{
			$this->log(__FUNCTION__, __LINE__, 'Invalid URL format');
			return false;
			}

		#	Only allow downloads from ep4.roveridx.com
		if ($parsed_url['host'] !== 'ep4.roveridx.com')
			{
			$this->log(__FUNCTION__, __LINE__, 'Untrusted domain: ' . $parsed_url['host']);
			return false;
			}

		#	Validate filename
		$filename							= basename($parsed_url['path']);
		if (empty($filename) || strpos($filename, '..') !== false)
			{
			$this->log(__FUNCTION__, __LINE__, 'Invalid filename');
			return false;
			}

		#	Ensure it's a sitemap file
		if (!preg_match('/^rover_sitemap_.*\.(xml|gz)$/', $filename))
			{
			$this->log(__FUNCTION__, __LINE__, 'Invalid sitemap filename format');
			return false;
			}

		$local_file							= ABSPATH.basename($dest_url);

		#	Check file size first
		$response							= wp_remote_head($dest_url, ['timeout' => 10]);
		if (is_wp_error($response))
			{
			$this->log(__FUNCTION__, __LINE__, $response->get_error_message());
			return false;
			}

		$content_length						= wp_remote_retrieve_header($response, 'content-length');
		if ($content_length > 10485760) { // 10MB limit
			$this->log(__FUNCTION__, __LINE__, 'File too large: ' . $content_length);
			return false;
			}

		$response							= wp_remote_get($dest_url, [
			'timeout'						=> 30,
			'stream'						=> true,
			'filename'						=> $local_file,
			]);

		if (is_wp_error($response))
			{
			$this->log(__FUNCTION__, __LINE__, $response->get_error_message());
			return false;
			}

		$this->log(__FUNCTION__, __LINE__, 'Copied ['.$dest_url.'] to ['.$local_file.'] [<span style="color:green;">'.number_format(filesize($local_file)).' bytes</span>]');
		return true;
		}

	function notify_search_engine($sitemap_url)
		{

		$response							= wp_remote_get(
												$sitemap_url,
												[
													'timeout'		=> self::SEARCH_ENGINE_PING_TIMEOUT,
													'redirection'	=> 3,
													'sslverify'		=> true,
													'headers'		=> [
																	'User-Agent' => 'Mozilla/5.0 (compatible; RoverIDX Sitemap Ping)',
																]
												]
												);

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

		$code								= wp_remote_retrieve_response_code($response);

		return ($code >= 200 && $code < 400);
		}

	private function log($func, $line, $str)
		{
		$this->debug_log[]	= sprintf(
									'%1$s [%2$s] %3$s: %4$s',
									basename(__FILE__),
									$func,
									$line,
									$str
									);
		}
	}

?>