<?php
require_once ABSPATH.'wp-admin/includes/image.php';		#	Needed for wp_generate_attachment_metadata()
require_once(ABSPATH.'wp-admin/includes/file.php');
require_once(ABSPATH.'wp-admin/includes/media.php');
require_once ABSPATH.'wp-admin/includes/taxonomy.php';	#	Needed for wp_create_category()
if( !class_exists( 'WP_Http', false ) )
	require_once( ABSPATH . WPINC. '/class-http.php' );
#require_once 'rover-database.php';


class Rover_IDX_SOCIAL {

	private $last_search_date				= null;
	private $now_search_date				= null;

	private $all_updated_properties			= null;

	private $thumb_width					= 110;
	private $image_width					= 400;

	private $wp_post_author					= null;
	private $post_to_wp_comments			= null;

	private $email_on_error					= false;
	private $email_on_error_to_user			= false;
	private $previous_user_id				= null;

	private $settings						= null;
	private $latest_prop_updated_date		= null;

	private $url_ends_in_slash				= true;
	private $ping_status					= null;
	private $ret_html						= array();

	private Rover_IDX $rover_idx;

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

		$permalink_structure				= get_option('permalink_structure');
		if (!is_null($permalink_structure) && substr($permalink_structure, -1) != '/')
			$this->url_ends_in_slash		= false;

		$this->ping_status					= get_option('default_ping_status');

		$this->settings						= get_option(roverOPTIONS::SOCIAL);
		if (!is_array($this->settings))
			$this->settings					= array();
		}

	public function refresh($fix_missing_images = false) {

		try {

			add_action( 'http_api_curl', function ($ch, $parsed_args, $url) {

				if (strpos($url, 'wasabisys.com') !== false) {
					curl_setopt($ch, CURLOPT_HTTPHEADER, ['Referer:']);
					}

				}, 10, 3);

			add_filter("http_request_host_is_external", function ($is, $host, $url) {
				return true;
				}, 10, 3);


			//	*******
			//	It's time to make the donuts
			//	*******

			$this->start();

			if ($this->has_updated_properties())
				{
				if ($this->is_enabled('post_to_wp'))
					$this->post_to_wordpress();

				}
			else
				{
				$this->log(__FILE__, __FUNCTION__, __LINE__, 'There are no updates at this time.');
				}

			if ($fix_missing_images)
				{
				$this->fix_wordpress_posts();
				return $this->ret_html;
				}
			}
		finally {
			#	ALWAYS restore user context
			$this->finish();
			}

		return true;
		}

	private function start() {

		if (is_array($this->settings))
			{
			foreach(array('post_to_wp') as $key)
				{
				if (isset($this->settings[$key]))
					{
					if (is_bool($this->settings[$key]))
						$this->log(__FILE__, __FUNCTION__, __LINE__, $key.' is bool ['.(($this->settings[$key] === true) ? 'true' : 'false').']');
					else if (is_numeric($this->settings[$key]))
						$this->log(__FILE__, __FUNCTION__, __LINE__, $key.' is numeric ['.$this->settings[$key].']');
					else if (is_string($this->settings[$key]))
						$this->log(__FILE__, __FUNCTION__, __LINE__, $key.' is string ['.$this->settings[$key].']');
					}
				}

			$this->wp_post_author			= (isset($this->settings['post_to_wp_as_user']) && is_numeric($this->settings['post_to_wp_as_user']))
													? $this->settings['post_to_wp_as_user']
													: 1;		//	Admin

			$this->post_to_wp_comments		= (isset($this->settings['post_to_wp_comments']) && $this->settings['post_to_wp_comments'] == 'enabled')
													? 'open'
													: 'closed';	//	Default is 'No'



			$this->email_on_error			= (isset($this->settings['email_on_error']) && $this->settings['email_on_error'] == true) ? true : false;
			$this->email_on_error_to_user	= (isset($this->settings['email_on_error_to_user']))
													? $this->settings['email_on_error_to_user']
													: get_bloginfo('admin_email');

 			$this->previous_user_id			= get_current_user_id();
			$user							= get_user_by( 'id', $this->wp_post_author );

			if ($user === false)
				{
				$users						= get_users('role=administrator');
				if (count($users))
					{
					$this->log(__FILE__, __FUNCTION__, __LINE__, ' grabbed 1st administrator');
					$user					= reset($users);
					}
				}

			if ($user)
				{
				$this->log(__FILE__, __FUNCTION__, __LINE__, ' logging in as ['.$user->ID.']');

				#	wp_set_current_user - we MUST do this for wp_upload_bits()

				wp_set_current_user( $user->ID, $user->user_login );
#				wp_set_auth_cookie( $user->ID );	#	Setting the cookie globally sets the administrator capability for a whole day, which we do not want
#				do_action('wp_login', $user->user_login, $user);

				$user->add_cap('unfiltered_upload', true);

				$this->log(__FILE__, __FUNCTION__, __LINE__, ' logged in as ['.$user->ID.'] ['.print_r($user->user_login, true).']');
				if ($user->has_cap('unfiltered_upload'))
					$this->log(__FILE__, __FUNCTION__, __LINE__, ' logged in user has_cap [unfiltered_upload]');
				}
			else
				{
				$this->log(__FILE__, __FUNCTION__, __LINE__, ' Not logged in ['.$this->wp_post_author.']');
				}
			}

		$search_since						= $this->get_last_search_date();

		$this->get_updated_properties($search_since);
		}

	private function finish() {

		//	Update the search_datetime, so we don't find these same properties again

		if ($this->has_updated_properties())
			{
			$key								= $this->get_last_search_date_key();
			$this->settings[$key]				= $this->get_now_search_date();
			if (!is_null($this->latest_prop_updated_date))
				$this->settings['search_datetime_']	= $this->latest_prop_updated_date;

			update_option(roverOPTIONS::SOCIAL, $this->settings);
			}

		if (!empty($this->previous_user_id))
			wp_set_current_user( $this->previous_user_id );
		}

	private function has_updated_properties() {
		if (is_null($this->all_updated_properties))
			$this->log(__FILE__, __FUNCTION__, __LINE__, 'all_updated_properties is null');
		else
			$this->log(__FILE__, __FUNCTION__, __LINE__, 'all_updated_properties has '.count($this->all_updated_properties).' items');

		return (!is_null($this->all_updated_properties) && count($this->all_updated_properties)) ? true : false;
		}
	private function all_updated_properties() {
		return $this->all_updated_properties;
		}
	private function get_last_search_date_key()	{
		return 'search_datetime_';
		}

	private function get_last_search_date()	{
		if (is_null($this->last_search_date))
			{
			//	Get timestamp from Rover SQL Server.  This is important, because the timestamps
			//	we get must be in the same TZ as the timestamps we send to fetch data.

			$this->now_search_date		= current_time('mysql', true);	#	true = GMT

			$this->log(__FILE__, __FUNCTION__, __LINE__, 'it is currently '.$this->now_search_date);

			$key						= $this->get_last_search_date_key();

			$this->log(__FILE__, __FUNCTION__, __LINE__, $key);

			if (is_object($this->settings))
				$this->log(__FILE__, __FUNCTION__, __LINE__, 'this->settings is object');
			else if (is_array($this->settings))
				$this->log(__FILE__, __FUNCTION__, __LINE__, 'this->settings is array');

			if (is_array($this->settings) && isset($this->settings[$key]))
				{
				$search_datetime_unix	= strtotime($this->settings[$key]);
				$this->last_search_date	= date('Y-m-d H:i:s', $search_datetime_unix);

				$this->log(__FILE__, __FUNCTION__, __LINE__, 'key exists, using '.$this->last_search_date);
				}
			else
				{
				$search_datetime_unix	= strtotime("-1 day");
				$this->last_search_date	= date('Y-m-d H:i:s', $search_datetime_unix);

				$this->log(__FILE__, __FUNCTION__, __LINE__, 'key does not exist, creating '.$this->last_search_date);
				}
			}

		return $this->last_search_date;
		}
	private function get_now_search_date()	{
		return $this->now_search_date;
		}

	private function get_updated_properties($search_since)	{

		if (!$this->is_enabled('post_to_wp'))
			{
			$this->log(__FILE__, __FUNCTION__, __LINE__, 'social updates are disabled - Terminating.');
			return false;
			}

		$this->log(__FILE__, __FUNCTION__, __LINE__, 'Looking for updates since '.$search_since);
		$this->log(__FILE__, __FUNCTION__, __LINE__, 'There are '.count($this->rover_idx->all_selected_regions).' selected regions ['.implode(',', array_keys($this->rover_idx->all_selected_regions)).']');

		//	Specify HTML email message format

		if (function_exists('add_filter'))
			add_filter('wp_mail_content_type', function() { return "text/html"; } );

		foreach ($this->rover_idx->all_selected_regions as $one_region => $region_slugs)
			{
			$rover_content				= $this->rover_idx->CONTENT->rover_content(
																			'rover-social-fetch-updated',
																			array(
																				'region'			=> $one_region,
																				'domain'			=> get_site_url(),
																				'since'				=> $search_since
																				)
																			);

			$this->log(__FILE__, __FUNCTION__, __LINE__, '['.$rover_content['the_html'].']');

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

			if ($result_decoded === null)
				{
				$this->log(__FILE__, __FUNCTION__, __LINE__, 'xml did not decode correctly');
				$this->log(__FILE__, __FUNCTION__, __LINE__, print_r($rover_content['the_html'], true));

				if (function_exists('wp_mail') && $this->email_on_error)
					{
					wp_mail($this->email_on_error_to_user,
							get_site_url().': Rover IDX Social: xml did not decode correctly',
							'Oops');
					}

				return false;
				}

			$this->all_updated_properties	= array();

			if (is_array($result_decoded) && isset($result_decoded['properties']) && count($result_decoded['properties']))
				{
				//	Remove properties that have already been posted

				foreach ($result_decoded['properties'] as $prop)
					{
					$this->log(__FILE__, __FUNCTION__, __LINE__, '['.$prop['id'].'] ['.$prop['mlnumber'].']');

					if (is_array($prop))
						$this->log(__FILE__, __FUNCTION__, __LINE__, 'prop is [array] of ['.count($prop).'] items');
					else if (is_object($prop))
						$this->log(__FILE__, __FUNCTION__, __LINE__, 'prop is [object] of ['.count($prop).'] items');

					if (Rover_IDX_Database::already_in_tracking($prop['id'], $prop['region']))
						$this->log(__FILE__, __FUNCTION__, __LINE__, $prop['id'].' has already been posted');
					else
						$this->all_updated_properties[] = $prop;
					}

				if (count($this->all_updated_properties) === 0)
					{
					return false;
					}
				}
			}
		}

	private function is_enabled($key) {
		if (isset($this->settings[$key]))
			{
			if ($this->settings[$key] === true)
				return true;
			if ($this->settings[$key] == 'enabled')
				return true;
			}

		return false;
		}

	private function post_to_wordpress() {

		foreach ($this->all_updated_properties as $one_property)
			{
			if (!$this->validate_property($one_property)) {
				$this->log(__FILE__, __FUNCTION__, __LINE__, 'Skipping invalid property');
				continue;
				}

			$this->log(__FILE__, __FUNCTION__, __LINE__, $one_property['address']);

			#	Trim leading slash from 'link', if it exists.
			#	Avoid double slashes like https://realestate.com//fl/orlando/123-main-drive/

			$wp_post_content				= '<a href="'
											. esc_url(get_site_url() . '/' . ltrim($one_property['link'], "/"))
											. '" target="_blank">'
											. esc_html($one_property['address'])
											. ' offered at '
											. esc_html($one_property['price'])
											. '</a>';
			$wp_post_content				.=	'<div style="margin:20px 0 0 0;">'.wp_kses_post($one_property['desc']).'</div>';

			//	These Posts will all have a Rover category stamp (ease of finding them all later)

			$wp_cat							= 'Property';
			$wp_cat_value					= $wp_cat;
			if ($one_property['type'] == 'New') {
				$wp_cat						= 'New Property';
				$wp_cat_value				= $wp_cat;
				}
			else if ($one_property['type'] == 'Price') {
				$wp_cat						= 'Updated Property';
				$wp_cat_value				= 'price-'.preg_replace('/[^0-9]/', '', $one_property['price']);
				}
			else if ($one_property['type'] == 'Sold') {
				$wp_cat						= 'Sold Property';
				$wp_cat_value				= $wp_cat;
				}
			else if ($one_property['type'] == 'OpenHouse') {
				$wp_cat						= 'Open House';
				$wp_cat_value				= 'open-'.date('Y-m-d', strtotime($one_property['upd_date']));
				}

			if ($this->should_we_post($wp_cat, $one_property))
				{
				$cat_id						= get_cat_ID($wp_cat);
				if (!$cat_id)
					$cat_id					= wp_create_category($wp_cat);

				$rover_cat					= 'Rover IDX Property';
				$rover_cat_id				= get_cat_ID($rover_cat);
				if (!$rover_cat_id)
					$rover_cat_id			= wp_create_category($rover_cat);

				$rover_post					= array(
													'post_title'		=> sanitize_text_field($one_property['title']),
													'post_content'		=> $wp_post_content,
													'post_status'		=> 'publish',
													'post_author'		=> $this->wp_post_author,
													'ping_status'		=> $this->ping_status,
													'post_category'		=> array($cat_id, $rover_cat_id),
													'comment_status'	=> $this->post_to_wp_comments,
#													Security issue with not escaping post values
#													'filter' 			=> true		#	Don't sanitize the post_content
													);

				if (isset($one_property['ourListingDate']) && !empty($one_property['ourListingDate']))
					{
					$the_date								= strtotime($one_property['ourListingDate']);
					if ($the_date !== false)
						{
						$rover_post['post_date']			= date("Y-m-d H:i:s", $the_date);
						$rover_post['post_date_gmt']		= gmdate("Y-m-d H:i:s", $the_date);

						$this->latest_prop_updated_date		= (is_null($this->latest_prop_updated_date) || ($the_date > strtotime($this->latest_prop_updated_date)))
																	? date("Y-m-d H:i:s", $the_date)
																	: $this->latest_prop_updated_date;
						}
					}

				$post_id									= wp_insert_post( $rover_post );

				if (is_wp_error($post_id)) {
					$errors = $post_id->get_error_messages();
					foreach ($errors as $error) {
						$this->log(__FILE__, __FUNCTION__, __LINE__, 'wp_error: '.$error);
						}
					}
				else
					{
					add_post_meta($post_id, 'rover-idx-mlnumber', sanitize_text_field($one_property['mlnumber']));
					add_post_meta($post_id, 'rover-idx-cat', sanitize_text_field($wp_cat_value));

					$this->log(__FILE__, __FUNCTION__, __LINE__, 'Requesting ['.$one_property['image'].']');

					Rover_IDX_Database::update_tracking($one_property['id'], $one_property['region']);

					$this->add_featured_image($post_id, $one_property);
					}
				}
			}
		}

	private function fix_wordpress_posts() {

		$this->ret_html					= array();
		if (!$this->is_enabled('post_to_wp'))
			{
			$this->ret_html[]			= __FUNCTION__.' '.__LINE__.' - social updates are disabled';
			return $this->ret_html;
			}

		if (is_array($this->settings))
			{
			$this->wp_post_author		= (isset($this->settings['post_to_wp_as_user']) && is_numeric($this->settings['post_to_wp_as_user']))
												? $this->settings['post_to_wp_as_user']
												: 1;		//	Admin
			}

		$user							= get_user_by( 'id', $this->wp_post_author );

		if ($user === false)
			{
			$users						= get_users('role=administrator');
			if (count($users))
				{
				$this->ret_html[]		= __FUNCTION__.' '.__LINE__.' - grabbed 1st administrator';
				$user					= reset($users);
				}
			}

		if ($user)
			{
 			$this->previous_user_id		= get_current_user_id();
			$this->ret_html[]			= __FUNCTION__.' '.__LINE__.' - logging in as ['.$user->ID.']';

			#	wp_set_current_user - we MUST do this for wp_upload_bits()

			wp_set_current_user( $user->ID, $user->user_login );
#			wp_set_auth_cookie( $user->ID );	#	Setting the cookie globally sets the administrator capability for a whole day, which we do not want
#				do_action('wp_login', $user->user_login, $user);

			$user->add_cap('unfiltered_upload', true);

			$this->ret_html[]			= __FUNCTION__.' '.__LINE__.' - logged in as ['.$user->ID.'] ['.print_r($user->user_login, true).']';
			if ($user->has_cap('unfiltered_upload'))
				$this->ret_html[]		= __FUNCTION__.' '.__LINE__.' - logged in user has_cap [unfiltered_upload]';
			}
		else
			{
			$this->ret_html[]			= __FUNCTION__.' '.__LINE__.' - Not logged in ['.$this->wp_post_author.']';
			}

		$mlnumbers						= array();
		foreach (get_posts(array(
										'category'		=> get_cat_ID('Rover IDX Property'),
										'numberposts' 	=> 500,
										'order'			=> 'DESC'
										)) as $post)
			{
#			$this->ret_html[]			= __FUNCTION__.' '.__LINE__.' - considering post ['.$post->ID.']';
			if ( !has_post_thumbnail( $post->ID ))
				{
				$mln					= get_post_meta( $post->ID, 'rover-idx-mlnumber', $single = true );
				if (!empty($mln))
					{
					$mlnumbers[$mln]	= $post->ID;
					$this->ret_html[]	= __FUNCTION__.' '.__LINE__.' - post ['.$post->ID.'] / mlnumber ['.$mln.'] has no featured image';
					}
				}
			}

		$this->ret_html[]				= __FUNCTION__.' '.__LINE__.' - found: '.count($mlnumbers).' with missing featured image';
		foreach($mlnumbers as $mln => $post_id)
			{
			$this->ret_html[]			= __FUNCTION__.' '.__LINE__.' - mlnumber ['.$mln.'] / post ['.$post_id.']';
			}

		foreach ($this->rover_idx->all_selected_regions as $one_region => $region_slugs)
			{
			$rover_content				= $this->rover_idx->CONTENT->rover_content(
																			'rover-social-fix-posts',
																			array(
																				'region'			=> $one_region,
																				'domain'			=> get_site_url(),
																				'mlnumbers'			=> array_keys($mlnumbers)
																				)
																			);

			$this->log(__FILE__, __FUNCTION__, __LINE__, '['.$rover_content['the_html'].']');

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

			if ($result_decoded === null)
				{
				$this->ret_html[]		= __FUNCTION__.' '.__LINE__.' - json did not decode correctly';

				if (function_exists('wp_mail') && $this->email_on_error)
					wp_mail($this->email_on_error_to_user, get_site_url().': Rover IDX Social: xml did not decode correctly', 'Oops');

				return false;
				}

			$this->all_updated_properties	= array();

			if (is_array($result_decoded) && isset($result_decoded['properties']) && count($result_decoded['properties']))
				{
				#	Update featured image

				foreach ($result_decoded['properties'] as $prop)
					{
					$this->ret_html[]	= __FUNCTION__.' '.__LINE__.' - ['.$prop['id'].'] ['.$prop['mlnumber'].']';

					if (isset($mlnumbers[$prop['mlnumber']]))
						{
						$post_id		= $mlnumbers[$prop['mlnumber']];
						$this->add_featured_image($post_id, $prop);
						}
					else
						{
$this->ret_html[]	= __FUNCTION__.' '.__LINE__.' - ['.$prop['mlnumber'].'] is not in ['.print_r(array_keys($mlnumbers), true).']';
						}
#					foreach($mlnumbers as $post_id => $mln)
#						{
#						if ($mln == $prop['mlnumber'])
#							$this->add_featured_image($post_id, $prop);
#						}
					}
				}
			else
				{
				$this->ret_html[]		= __FUNCTION__.' '.__LINE__.' - no properties returned';
				}
			}

		if ($user)
			{
			if (!empty($this->previous_user_id))
				wp_set_current_user( $this->previous_user_id );
			}

		#	Update `search_datetime_`

		if (is_array($this->settings))
			{
			date_default_timezone_set('UTC');

			$key						= $this->get_last_search_date_key();
			$this->settings[$key]		= date('Y-m-d H:i:s', strtotime('now'));

			update_option(roverOPTIONS::SOCIAL, $this->settings);
			}
		}

	private function add_featured_image($post_id, $prop)
		{
		if (!$this->validate_image_url($prop['image'])) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, 'Invalid image URL: ' . $prop['image']);
			return false;
			}

		$dest_file						= sprintf("%s.jpg", strtolower(preg_replace('/[^a-zA-Z0-9\-]/', '', $prop['region'].'-'.$prop['mlnumber'])));

		$this->log(__FILE__, __FUNCTION__, __LINE__, 'photo url: '.$prop['image'].' -> '.$dest_file);

		$this->ret_html[]				= __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] about to download_url ['.$prop['image'].']';

		$tmp							= download_url( $prop['image'] );	#	put in a temp location

		if ( is_wp_error( $tmp ) ) {
			$errors						= $tmp->get_error_messages();
			foreach ($errors as $error) {

				#	download_url()

				$this->ret_html[]		= __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] download_url failed ['.print_r($error, true).']';
				$this->log(__FILE__, __FUNCTION__, __LINE__, 'download_url failed: '.$error);

				#	download_url failed.  Try with curl

#					$fp					= tmpfile();
				$tmp					= sprintf('%s/%s', sys_get_temp_dir(), $dest_file);

				$ch						= curl_init($prop['image']);

				curl_setopt($ch, CURLOPT_HEADER, 0);
				curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: image/jpeg"));
				curl_setopt($ch, CURLOPT_POST, true);
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
				curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
				curl_setopt($ch, CURLOPT_FILE, fopen($tmp, 'wb'));

				$raw					= curl_exec($ch);

#					if (file_put_contents($tmp, $raw))
				if (file_exists($tmp))
					{
#						$meta_data			= stream_get_meta_data($fp);
#						$tmp				= $meta_data["uri"];	#	for media_handle_sideload()

					$this->ret_html[]		= __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] curl saved file to ['.print_r($tmp, true).']';
				}
				else
					{
					$this->ret_html[]		= __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] curl failed';
					return false;
					}

#					fclose($fp);
				}
			} // check for errors
		else
			{
			$this->ret_html[]				= __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] download_url returned ['.print_r($tmp, true).']';
			}

		#	download image to this server
#			$attach_id						= media_handle_sideload( $prop['image'], $post_id, $prop['mlnumber']);
		$attach_id							= media_handle_sideload( array(
											'name'					=> $dest_file,
											'tmp_name'				=> $tmp
											), $post_id );

		#	Check for handle sideload errors.
		if ( is_wp_error( $attach_id ) ) {
			$errors							= $attach_id->get_error_messages();
			foreach ($errors as $error) {
				$this->log(__FILE__, __FUNCTION__, __LINE__, 'media_handle_sideload failed: '.$error);
				$mime_type					= mime_content_type($tmp);

				$this->ret_html[]			= __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] media_handle_sideload failed ['.print_r($error, true).'] - mime_type ['.print_r($mime_type, true).'] - allowed mime types are ['.print_r(get_allowed_mime_types(), true).']';
				}
			return false;
			}

		$this->ret_html[]					= __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] media_handle_sideload returned ['.print_r($attach_id, true).']';
		$this->log(__FILE__, __FUNCTION__, __LINE__, '[post_id '.$post_id.'] ['.$attach_id.']');

		$this->prepend_image_to_post($post_id, $prop, $attach_id);
		}

	private function prepend_image_to_post($post_id, $prop, $attach_id) {
		#	Validate post_id
		if (!is_numeric($post_id) || $post_id <= 0) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, 'Invalid post_id: ' . print_r($post_id, true));
			return false;
			}

		#	Validate attach_id
		if (!is_numeric($attach_id) || $attach_id <= 0) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, 'Invalid attach_id: ' . print_r($attach_id, true));
			return false;
			}

		#	Add image as Featured Image
		$ret = set_post_thumbnail($post_id, $attach_id);
		$this->ret_html[] = __FUNCTION__.' '.__LINE__.' - post_id ['.$post_id.'] set_post_thumbnail returned '.'['.(($ret === true) ? 'true' : 'false').']';

		if (!$ret) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "set_post_thumbnail failed for post $post_id, attachment $attach_id");
			#	Don't return false - we can still try to insert the image in content
			}

		#	Get the post
		$post = get_post($post_id, 'ARRAY_A');
		if (!$post || !is_array($post)) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "Post $post_id not found or invalid");
			return false;
			}

		if (!isset($post['post_content'])) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "Post $post_id has no post_content field");
			return false;
			}

		#	Get attachment image data
		$image = wp_get_attachment_image_src($attach_id);
		if (!$image || !is_array($image)) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "Failed to get image data for attachment $attach_id");
			return false;
			}

		#	Validate image array has required elements
		#	wp_get_attachment_image_src returns: [url, width, height, is_intermediate]
		if (count($image) < 3) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "Image array incomplete for attachment $attach_id: ". print_r($image, true));
			return false;
			}

		#	Extract image dimensions
		$image_url								= $image[0];
		$original_width							= intval($image[1]);
		$original_height						= intval($image[2]);

		#	Validate dimensions
		if ($original_width <= 0 || $original_height <= 0) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "Invalid image dimensions: {$original_width}x{$original_height}");
			return false;
			}

		#	Calculate display dimensions
		if ($original_width <= $this->image_width) {
			#	Image is smaller than desired width - use original size
			$image_width						= $original_width;
			$image_height						= $original_height;
			}
		else {
			#	Scale down proportionally
			$image_width						= $this->image_width;
			$image_height						= intval(($this->image_width / $original_width) * $original_height);
			}

		#	Validate prop data
		if (!isset($prop['link']) || empty($prop['link'])) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "Property missing 'link' field");
			return false;
			}

		#	Build image tag with proper escaping
		$image_tag = '<a href="'
					. esc_url(get_site_url() . '/' . ltrim($prop['link'], '/'))
					. '" target="_blank">'
					. '<img src="' . esc_url($image_url) . '" '
					. 'width="' . $image_width . '" '
					. 'height="' . $image_height . '" '
					. 'class="alignleft" '
					. 'alt="' . esc_attr(isset($prop['address']) ? $prop['address'] : 'Property Image') . '" />'
					. '</a>';

		#	Prepend image to content
		$post['post_content']					= $image_tag . $post['post_content'];

		#	Update the post
		$updated_post_id						= wp_update_post($post);

		if (is_wp_error($updated_post_id)) {
			$errors								= $updated_post_id->get_error_messages();
			foreach ($errors as $error) {
				$this->ret_html[] = __FUNCTION__.' '.__LINE__
								.' - post_id ['.$post_id.'] wp_update_post failed: '
								. print_r($error, true);
				$this->log(__FILE__, __FUNCTION__, __LINE__,
					'wp_update_post failed: ' . $error);
				}

			return false;
			}

		if ($updated_post_id == 0) {
			$this->log(__FILE__, __FUNCTION__, __LINE__, "wp_update_post returned 0 for post $post_id");
			return false;
			}

		$this->ret_html[] = __FUNCTION__.' '.__LINE__.' - Successfully updated post '.$post_id.' with image from attachment '.$attach_id;

		return true;
		}

	#	Prevent duplicate 'New' and 'Sold' postings

	private function should_we_post($wp_cat, $one_property)
		{
		global							$wpdb;

		$meta_for_mlnumber				= $wpdb->get_results(
											$wpdb->prepare(
												"SELECT * FROM $wpdb->postmeta
												WHERE meta_key = %s AND meta_value = %s",
												'rover-idx-mlnumber',
												$one_property['mlnumber']
												)
											);

		if ($wp_cat == 'New Property') {	#	If any meta exists, we've posted this listing previously.  Do not post it again.
			if (!is_null($meta_for_mlnumber))
				{
				$this->log(__FILE__, __FUNCTION__, __LINE__, ' Skipping '.$one_property['mlnumber'].' ['.$wp_cat.']');
				return false;
				}
			}
		else if ($wp_cat == 'Updated Property') {
			$lookup_price				= 'price-'.preg_replace('/[^0-9]/', '', $one_property['price']);
			foreach( $meta_for_mlnumber as $one_row) {
				if ($one_row->meta_key === 'rover-idx-cat' && $one_row->meta_value === $lookup_price)
					{
					$this->log(__FILE__, __FUNCTION__, __LINE__, ' Skipping '.$one_property['mlnumber'].' ['.$wp_cat.'] - post '.$one_row->meta_id.' '.$one_row->post_id.' already exists');
					return false;
					}
				}
			}
		else if ($wp_cat == 'Sold Property') {
			foreach( $meta_for_mlnumber as $one_row) {
				if ($one_row->meta_key === 'rover-idx-cat' && $one_row->meta_value === $wp_cat)
					{
					$this->log(__FILE__, __FUNCTION__, __LINE__, ' Skipping '.$one_property['mlnumber'].' ['.$wp_cat.'] - post '.$one_row->meta_id.' '.$one_row->post_id.' already exists');
					return false;
					}
				}
			}
		else if ($wp_cat == 'Open House') {
			$lookup_date				= 'open-'.date('Y-m-d', strtotime($one_property['upd_date']));
			foreach( $meta_for_mlnumber as $one_row) {
				if ($one_row->meta_key === 'rover-idx-cat' && $one_row->meta_value === $lookup_date)
					{
					$this->log(__FILE__, __FUNCTION__, __LINE__, ' Skipping '.$one_property['mlnumber'].' ['.$wp_cat.'] - post '.$one_row->meta_id.' '.$one_row->post_id.' already exists');
					return false;
					}
				}
			}
		else {
			#	Not sure what to do with this one
			}

		return true;
		}

	private function validate_property($property) {

		$required_fields = [
			'id', 'mlnumber', 'title', 'address',
			'link', 'price', 'desc', 'image',
			'type', 'region'
			];

		foreach ($required_fields as $field) {
			if (!isset($property[$field])) {
				$this->log(__FILE__, __FUNCTION__, __LINE__,
					"Missing required field: $field");
				return false;
				}

			#	Check for empty strings (but allow 0)
			if ($property[$field] === '' || $property[$field] === null) {
				$this->log(__FILE__, __FUNCTION__, __LINE__,
					"Empty value for required field: $field");
				return false;
				}
			}

		return true;
	}

	private function validate_image_url($url) {

		if (!is_string($url) || empty($url)) {
			return false;
			}

		if (!filter_var($url, FILTER_VALIDATE_URL)) {
			return false;
			}

		$scheme = parse_url($url, PHP_URL_SCHEME);
		if (!in_array($scheme, ['http', 'https'])) {
			return false;
			}

		#	Prevent SSRF attacks
		$host = parse_url($url, PHP_URL_HOST);
		if (filter_var($host, FILTER_VALIDATE_IP)) {
			if (!filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
				return false;
				}
			}

		return true;
		}

	private function log($file, $func, $line, $str)	{

		if ((isset($_GET[roverKEYS::DEBUG_KEY])) && (intval($_GET[roverKEYS::DEBUG_KEY]) > 0))
#		if (true)
			{
			error_log(
				sprintf( '[%s] %s <strong>%s</strong> %s: %s\n',
						get_site_url(),
						basename($file),
						$func,
						$line,
						$str
						)
				);
			}

		}
	}


?>