/**
  * Potentially process background index/purge requests
  *
  * @since 1.3.1
  */
 function update_index()
 {
     searchwp_check_for_stalled_indexer(360);
     // store the purge queue... just in case
     $toPurge = searchwp_get_option('purge_queue');
     $busy = searchwp_get_option('busy');
     // trigger background indexing
     if (isset($_REQUEST['swppurge']) && get_option('swppurge_transient') === sanitize_text_field($_REQUEST['swppurge'])) {
         if (is_array($toPurge) && !empty($toPurge)) {
             do_action('searchwp_log', 'Purge queue (' . count($toPurge) . '): ' . implode(', ', $toPurge));
             $toPurge = array_unique($toPurge);
             foreach ($toPurge as $object_id) {
                 do_action('searchwp_log', 'Purge post ' . $object_id);
                 $this->purge_post(intval($object_id));
             }
             searchwp_update_option('doing_delta', false);
         } else {
             do_action('searchwp_log', '$toPurge is inapplicable');
         }
         searchwp_update_option('purge_queue', array());
         $this->purgeQueue = array();
         do_action('searchwp_log', 'Purge queue processed, trigger_index()');
         // allow developers the ability to disable automatic reindexing after edits in favor of their own method
         $automaticallyReindex = apply_filters('searchwp_auto_reindex', true);
         do_action('searchwp_log', '$automaticallyReindex = ' . print_r($automaticallyReindex, true));
         if (!$busy && !$this->paused && $automaticallyReindex) {
             // if the initial index hasn't been built yet, we don't want this request to double up
             // in the use case where the user is editing posts while the initial index is still being built
             if (searchwp_get_setting('initial_index_built')) {
                 $this->trigger_index();
             }
         }
         do_action('searchwp_log', 'Shutting down after purge request');
         $this->shutdown();
         die;
     } elseif (!$this->paused && !$this->indexing && get_option('searchwp_transient') === sanitize_text_field($indexnonce = searchwp_get_option('indexnonce'))) {
         if (!$this->alternate_indexer) {
             $this->indexing = true;
             $hash = sanitize_text_field($indexnonce);
             searchwp_delete_option('indexnonce');
             // prior to 2.0.1 this searchwp_add_option() was never fired so this indexer request would never happen (likely because this never needed to work because we no longer request an index from the options page via AJAX anymore)
             // searchwp_add_option( 'indexnonce', $hash );
             do_action('searchwp_log', 'Performing background index ' . $hash);
             if (!$busy) {
                 searchwp_update_option('busy', true);
                 do_action('searchwp_log', 'OK to index, proceed');
                 new SearchWPIndexer($hash);
             } else {
                 do_action('searchwp_log', '!!! Indexer BUSY !!!');
             }
             exit;
         }
     } elseif ($this->indexing) {
         do_action('searchwp_log', 'SHORT CIRCUIT: index process already running');
     }
     // check to see if we need to process a purgeQueue
     if (is_array($toPurge) && !empty($toPurge) && false == searchwp_get_setting('processing_purge_queue') && searchwp_get_setting('initial_index_built')) {
         if (apply_filters('searchwp_background_deltas', true)) {
             // proceed with delta update
             do_action('searchwp_log', 'Automatic delta index update');
             $doing_delta = searchwp_get_option('doing_delta');
             if (!$doing_delta) {
                 // prevent delta update cycle by maxing out the number of attempts
                 $delta_attempts = absint(searchwp_get_option('delta_attempts'));
                 if ($delta_attempts > apply_filters('searchwp_max_delta_attempts', 5)) {
                     do_action('searchwp_log', 'TOO MANY DELTA ATTEMPTS, ABORT');
                     return;
                 }
                 searchwp_update_option('delta_attempts', $delta_attempts + 1);
                 // at this point we're viewing the screen that loads after making an edit, so we can't die();
                 searchwp_update_option('doing_delta', true);
                 $this->process_updates();
             }
         } else {
             do_action('searchwp_log', 'Background delta index update prevented');
         }
     } else {
         if (searchwp_get_setting('processing_purge_queue')) {
             do_action('searchwp_log', 'Cleaning up after processing purge queue');
             searchwp_set_setting('processing_purge_queue', false);
             searchwp_update_option('delta_attempts', 0);
         }
     }
 }
	/**
	 * Constructor
	 *
	 * @param string $hash The key used to validate instantiation
	 * @since 1.0
	 */
	public function __construct( $hash = '' ) {

		$searchwp = SWP();

		// by default let's only grab 'enabled' post types across the board (so as to keep the index size at a minimum)
		$this->postTypesToIndex = $searchwp->get_enabled_post_types_across_all_engines();

		// make sure we've got a valid request to index
		if ( get_option( 'searchwp_transient' ) !== $hash ) {
			if ( ! empty( $hash ) ) {
				do_action( 'searchwp_log', 'Invalid index request ' . $hash );
			} else {
				do_action( 'searchwp_log', 'External SearchWPIndexer instantiation' );
			}
		} else {
			do_action( 'searchwp_indexer_pre' );

			// init
			$this->common = $searchwp->common;

			$this->lenient_accents = apply_filters( 'searchwp_leinant_accents', $this->lenient_accents ); // deprecated
			$this->lenient_accents = apply_filters( 'searchwp_lenient_accents', $this->lenient_accents );

			// dynamically decide whether we're going to index Attachments based on whether Media is enabled for any search engine
			$index_attachments_from_settings = false;
			if ( in_array( 'attachment', $this->postTypesToIndex ) ) {
				$index_attachments_from_settings = true;
			}

			// allow dev to completely disable indexing of Attachments to save indexing time
			$this->indexAttachments = apply_filters( 'searchwp_index_attachments', $index_attachments_from_settings );
			if ( ! is_bool( $this->indexAttachments ) ) {
				$this->indexAttachments = false;
			}

			// allow dev to customize post statuses are included
			$this->post_statuses = (array) apply_filters( 'searchwp_post_statuses', $this->post_statuses, null );
			foreach ( $this->post_statuses as $post_status_key => $post_status_value ) {
				$this->post_statuses[ $post_status_key ] = sanitize_key( $post_status_value );
			}

			// allow dev to forcefully omit posts from being indexed
			$this->excludeFromIndex = apply_filters( 'searchwp_prevent_indexing', array() );
			if ( ! is_array( $this->excludeFromIndex ) ) {
				$this->excludeFromIndex = array();
			}
			$this->excludeFromIndex = array_map( 'absint', $this->excludeFromIndex );

			// allow dev to forcefully omit post types that would normally be indexed
			$this->postTypesToIndex = apply_filters( 'searchwp_indexed_post_types', $this->postTypesToIndex );

			// attachments cannot be included here, to omit attachments use the searchwp_index_attachments filter
			// so we have to check to make sure attachments were not included
			if ( is_array( $this->postTypesToIndex ) ) {
				foreach ( $this->postTypesToIndex as $key => $postType ) {
					$post_type_lower = function_exists( 'mb_strtolower' ) ? mb_strtolower( $postType ) : strtolower( $postType );
					if ( 'attachment' == $post_type_lower ) {
						unset( $this->postTypesToIndex[ $key ] );
					}
				}
			} elseif ( 'attachment' == strtolower( $this->postTypesToIndex ) ) {
				$this->postTypesToIndex = 'any';
			}

			/**
			 * Allow for some catch-up from the last request
			 */

			// auto-throttle based on load
			$waitTime = 1;
			$waiting = false;

			if ( apply_filters( 'searchwp_indexer_load_monitoring', true ) && function_exists( 'sys_getloadavg' ) ) {
				$load = sys_getloadavg();
				$loadThreshold = abs( apply_filters( 'searchwp_load_maximum', 2 ) );

				// if the load has breached the threshold, scale the wait time
				if ( $load[0] > $loadThreshold ) {
					$waiting = true;
					$waitTime = 4 * floor( $load[0] );
					do_action( 'searchwp_log', 'Load threshold (' . $loadThreshold . ') has been breached! Current load: ' . $load[0] . '. Automatically injecting a wait time of ' . $waitTime );

					// this flag is going to prevent the indexer from jumpstarting which could very well trigger parallel indexers
					searchwp_update_option( 'waiting', true );
				}
			}

			// allow developers to throttle the indexer
			$waitTime = absint( apply_filters( 'searchwp_indexer_throttle', $waitTime ) );
			$iniMaxExecutionTime = absint( ini_get( 'max_execution_time' ) ) - 5;
			if ( $iniMaxExecutionTime < 10 ) {
				$iniMaxExecutionTime = 10;
			}
			if ( $waitTime > $iniMaxExecutionTime ) {
				do_action( 'searchwp_log', 'Requested throttle of ' . $waitTime . 's exceeds max execution time, forcing ' . $iniMaxExecutionTime . 's' );
				$waitTime = $iniMaxExecutionTime;
			}

			$memoryUse = size_format( memory_get_usage() );
			do_action( 'searchwp_log', 'Memory usage: ' . $memoryUse . ' - sleeping for ' . $waitTime . 's' );

			if ( 1 == $waitTime ) {
				// wait time was not adjusted, so we're just going to usleep because 1 second is an eternity
				usleep( 750000 );
			} else {
				sleep( $waitTime );
			}

			if ( $waiting ) {
				searchwp_update_option( 'waiting', false );
			}

			// see if the indexer has stalled
			searchwp_check_for_stalled_indexer();

			// check to see if indexer is already running
			$running = searchwp_get_setting( 'running' );
			if ( empty( $running ) ) {
				do_action( 'searchwp_log', 'Indexer NOW RUNNING' );

				searchwp_set_setting( 'last_activity', current_time( 'timestamp' ), 'stats' );
				searchwp_set_setting( 'running', true );

				do_action( 'searchwp_indexer_running' );

				if ( apply_filters( 'searchwp_remove_pre_get_posts', true ) ) {
					remove_all_actions( 'pre_get_posts' );
					remove_all_filters( 'pre_get_posts' );
				}

				$this->update_running_counts();

				if ( false !== $this->find_unindexed_posts() ) {
					do_action( 'searchwp_indexer_posts' );

					$start_time = time();

					// index this chunk of posts
					$this->index();

					$index_time = time() - $start_time;

					// clean up
					do_action( 'searchwp_log', 'Indexing chunk complete: ' . $index_time . 's' );

					searchwp_set_setting( 'running', false );
					searchwp_set_setting( 'in_process', false, 'stats' );
					searchwp_update_option( 'busy', false );

					// reset the transient
					$this->hash = sprintf( '%.22F', microtime( true ) ); // inspired by $doing_wp_cron
					update_option( 'searchwp_transient', $this->hash );

					$destination = esc_url_raw( $searchwp->endpoint . '?swpnonce=' . $this->hash );

					do_action( 'searchwp_log', 'Request index (internal loopback) ' . $destination );

					$timeout = abs( apply_filters( 'searchwp_timeout', 0.02 ) );

					// recursive trigger
					$args = array(
						'body'        => array( 'swpnonce' => $this->hash ),
						'blocking'    => false,
						'user-agent'  => 'SearchWP',
						'timeout'     => $timeout,
						'sslverify'   => false,
					);
					$args = apply_filters( 'searchwp_indexer_loopback_args', $args );
					do_action( 'searchwp_indexer_loopback', $args );

					if ( ! apply_filters( 'searchwp_alternate_indexer', false ) ) {
						wp_remote_post( $destination, $args );
					}
				} else {
					do_action( 'searchwp_log', 'Nothing left to index' );
					do_action( 'searchwp_index_up_to_date' );
					$initial = searchwp_get_setting( 'initial_index_built' );
					if ( empty( $initial ) ) {
						wp_clear_scheduled_hook( 'swp_indexer' ); // clear out the pre-initial-index cron event
						do_action( 'searchwp_log', 'Initial index complete' );
						searchwp_set_setting( 'initial_index_built', true );
						do_action( 'searchwp_index_initial_complete' );
					}
					searchwp_set_setting( 'running', false );
					searchwp_set_setting( 'in_process', false, 'stats' );
					searchwp_update_option( 'busy', false );

					// delta updates may have been triggered, so now that the initial index has been built we can process them
					$purge_queue = searchwp_get_option( 'purge_queue' );
					if ( ! empty( $purge_queue ) ) {
						$timeout = abs( apply_filters( 'searchwp_timeout', 0.02 ) );

						// we don't need a hash because the purge queue is checked per request
						$destination = esc_url_raw( $searchwp->endpoint . '?swpdeltas=swpdeltas&' . sprintf( '%.22F', microtime( true ) ) );

						// recursive trigger
						$args = array(
							'body'        => array( 'swpdeltas' => 'swpdeltas' ),
							'blocking'    => false,
							'user-agent'  => 'SearchWP',
							'timeout'     => $timeout,
							'sslverify'   => false,
						);
						$args = apply_filters( 'searchwp_indexer_loopback_args', $args );

						do_action( 'searchwp_indexer_loopback', $args );

						wp_remote_post( $destination, $args );
					}
				}
			} else {
				do_action( 'searchwp_log', 'SHORT CIRCUIT: Indexer already running' );
			}
		}
	}