HEX
Server: nginx/1.27.1
System: Linux in-4 5.15.0-131-generic #141-Ubuntu SMP Fri Jan 10 21:18:28 UTC 2025 x86_64
User: ilikadirect (1186)
PHP: 7.4.33
Disabled: exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source
Upload Files
File: /storage/v6964/2foodfactor/public_html/wp-content/plugins/search-in-place/php/autocomplete.clss.php
<?php
/* Prevent direct access */
defined( 'ABSPATH' ) || die( "You can't access this file directly." );

if ( ! class_exists( 'CPSPAutocomplete' ) ) {
	class CPSPAutocomplete {

		private $_settings;
		private $_defaults;

		public function __construct() {
			$lang            = get_locale();
			$this->_defaults = array(
				'enabled' => true,
				'method'  => 'google',
				'count'   => 10,
				'chars'   => 25,
				'lang'    => substr( $lang, 0, 2 ),
				'url'     => 'http://suggestqueries.google.com/complete/search?output=toolbar&oe=utf-8&client=toolbar',
				'start'   => false,
			);
		} // End __construct

		/**** PRIVATE METHODS ****/

		private function _getSettings() {
			if ( empty( $this->_settings ) ) {
				$this->_settings = get_option( 'CPSPAutocomplete', array() );
			}
			return $this->_settings;
		} // End _getSettings

		private function _substrAtWord( $text, $length ) {
			if ( strlen( $text ) <= $length ) {
				return $text;
			}
			$blogCharset = get_bloginfo( 'charset' );
			$charset     = '' !== $blogCharset ? $blogCharset : 'UTF-8';
			$s           = mb_substr( $text, 0, $length, $charset );
			return mb_substr( $s, 0, strrpos( $s, ' ' ), $charset );
		} // End _substrAtWord

		/**** PUBLIC METHODS ****/

		public function clearSettings() {
			delete_option( 'CPSPAutocomplete' );
		} // End clearSettings

		public function updateSettings( $settings ) {
			$this->_getSettings();

			$this->setAttr( 'enabled', isset( $settings['autocomplete'] ) ? true : false );
			$this->setAttr( 'method', empty( $settings['autocomplete_method'] ) || $settings['autocomplete_method'] == 'google'  ? 'google' : 'website' );
			$this->setAttr( 'start', isset( $settings['autocomplete_start'] ) ? true : false );
			if ( isset( $settings['autocomplete_chars'] ) ) {
				$this->setAttr( 'chars', is_numeric( $settings['autocomplete_chars'] ) ? intval( $settings['autocomplete_chars'] ) : 0 );
			}
			if ( isset( $settings['autocomplete_count'] ) ) {
				$this->setAttr( 'count', is_numeric( $settings['autocomplete_count'] ) ? intval( $settings['autocomplete_count'] ) : 0 );
			}

			update_option( 'CPSPAutocomplete', $this->_settings );
		} // updateSettings

		public function setAttr( $attr, $val ) {
			$this->_getSettings();
			$this->_settings[ $attr ] = $val;
		} // setAttr

		public function getSettings() {
			echo '
			<style>
				.website-method{display:' . ( $this->getAttr( 'method' ) == 'website' ? 'table-row' : 'none'  ) . ';}
				.google-method{display:' . ( $this->getAttr( 'method' ) == 'google' ? 'table-row' : 'none'  ) . ';}
			</style>
			<script>
				jQuery(document).on("change", \'[name="autocomplete_method"]\', function(){
					let m = jQuery(\'[name="autocomplete_method"]:checked\').val();
					if(m == "google") {
						jQuery(".google-method").css("display", "table-row");
						jQuery(".website-method").hide();
					} else {
						jQuery(".website-method").css("display", "table-row");
						jQuery(".google-method").hide();
					}
				});
			</script>
			<div class="postbox search-in-place-postbox">
				<h3 class="hndle"><span>' . esc_html( __( 'Autocomplete', 'search-in-place' ) ) . '</span></h3>
				<div class="inside">

					<table class="form-table">
						<tbody>
							<tr valign="top">
								<th scope="row">
									<label for="autocomplete">' . esc_html( __( 'Enabling autocomplete', 'search-in-place' ) ) . '</label>
								</th>
								<td>
									<input type="checkbox" name="autocomplete" id="autocomplete" value="1" ' . ( ( $this->getAttr( 'enabled' ) ) ? 'checked' : '' ) . ' />
									<div style="font-weight:600;margin-top:30px;margin-bottom:15px;">' . esc_html__( 'Get terms suggestion from', 'search-in-place' ) . '</div>
									<div style="display:flex; align-items:center; gap:20px;">
										<div><label><input type="radio" name="autocomplete_method" value="google" ' . ( $this->getAttr( 'method' ) == 'google' ? 'checked' : '' ) . '> Google</label></div>
										<div><label><input type="radio" name="autocomplete_method" value="website" ' . ( $this->getAttr( 'method' ) == 'website' ? 'checked' : '' ) . '> Website (experimental)</label></div>
									</div>
								</td>
							</tr>
							<tr valign="top" class="website-method">
								<td colspan="2">
									<p style="border:1px solid #4caf50;margin-bottom:10px;padding:5px;background-color: #e6f5e6;">
										' . esc_html__( "The Website alternative is still experimental, as it can be influenced by the structure of the website. It extracts term suggestions directly from the website's information, including page and post titles, content, and excerpts.", 'search-in-place' ) . '
									</p>
								</td>
							</tr>
							<tr valign="top" class="google-method">
								<td colspan="2">
									<p style="border:1px solid #4caf50;margin-bottom:10px;padding:5px;background-color: #e6f5e6;">
										' . esc_html__( "The Google alternative utilizes the \"Google Suggest Query\" API to retrieve term suggestions based on Google's search experience. However, the suggested terms may not always be closely related to the specific website.", 'search-in-place' ) . '
									</p>
								</td>
							</tr>
							<tr valign="top" class="google-method">
								<th scope="row">
									<label for="autocomplete_chars">' . esc_html( __( 'Max keyword length', 'search-in-place' ) ) . '</label>
								</th>
								<td>
									<input type="number" name="autocomplete_chars" id="autocomplete_chars" value="' . esc_attr( $this->getAttr( 'chars' ) ) . '" />
								</td>
							</tr>
							<tr valign="top" class="google-method">
								<th scope="row">
									<label for="autocomplete_count">' . esc_html( __( 'Max keywords count', 'search-in-place' ) ) . '</label>
								</th>
								<td>
									<input type="number" name="autocomplete_count" id="autocomplete_count" value="' . esc_attr( $this->getAttr( 'count' ) ) . '" />
								</td>
							</tr>
							<tr valign="top" class="google-method">
								<th scope="row">
									<label for="autocomplete_start">' . esc_html( __( 'Suggest terms starting with the typed text', 'search-in-place' ) ) . '</label>
								</th>
								<td>
									<input type="checkbox" name="autocomplete_start" id="autocomplete_start" value="1" ' . ( $this->getAttr( 'start' ) ? 'CHECKED' : '' ) . ' />
								</td>
							</tr>
						</tbody>
					</table>
				</div>
			</div>
			';
		} // getSettings

		public function getAttr( $attr ) {
			$this->_getSettings();
			if ( isset( $this->_settings[ $attr ] ) ) {
				return $this->_settings[ $attr ];
			}
			if ( isset( $this->_defaults[ $attr ] ) ) {
				return $this->_defaults[ $attr ];
			}
		} // getAttr

		private function _fromGoogle( $terms ) {
			$result 	= array(); // Result

			$url      	= $this->getAttr( 'url' ) . '&hl=' . $this->getAttr( 'lang' ) . '&q=' . urlencode( $terms );
			$response 	= wp_remote_get(
				$url,
				array(
					'sslverify'  => false,
					'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8) AppleWebKit/535.6.2 (KHTML, like Gecko) Version/5.2 Safari/535.6.2',
				)
			);

			if ( is_wp_error( $response ) ) {
				error_log( $response->get_error_message(), 0 );
				return $result;
			}

			$body = wp_remote_retrieve_body( $response );
			if ( empty( $body ) ) {
				return $result;
			}

			if ( function_exists( 'mb_convert_encoding' ) ) {
				$body = mb_convert_encoding( $body, 'UTF-8' );
			}
			try {
				$xml = simplexml_load_string( $body );
				if ( empty( $xml ) ) {
					return $result;
				}

				$json  = json_encode( $xml );
				$array = json_decode( $json, true );

				$keywords = array();

				if ( isset( $array['CompleteSuggestion'] ) ) {
					foreach ( $array['CompleteSuggestion'] as $k => $v ) {
						if ( isset( $v['suggestion'] ) ) {
							$keywords[] = $v['suggestion']['@attributes']['data'];
						} elseif ( isset( $v[0] ) ) {
							$keywords[] = $v[0]['@attributes']['data'];
						}
					}
				}

				$m = $this->getAttr( 'count' );
				$c = 0;
				foreach ( $keywords as $k ) {
					$t = strtolower( $k );
					if (
						$t != $terms &&
						( '' != $str = $this->_substrAtWord( $t, $this->getAttr( 'chars' ) ) )
					) {
						$start = $this->getAttr( 'start' );
						if ( ! $start || ( $start && strpos( $t, $terms ) === 0 ) ) {
							$result[] = $str;
							$c++;
						}
					}
					if ( $m <= $c ) {
						break;
					}
				}
			} catch ( Exception $e ) {
				error_log( $e->getMessage() );
			}

			return $result;

		} // _fromGoogle

		private function _fromWebsite( $terms ) {
			global $wpdb;

			$result = [];

			$like = '%' . $wpdb->esc_like($terms) . '%';
			$sql = $wpdb->prepare(
				"SELECT ID, post_title, post_excerpt, post_content
				FROM $wpdb->posts
				WHERE post_status='publish'
				  AND (post_title LIKE %s
				   OR post_excerpt LIKE %s
				   OR post_content LIKE %s)
				LIMIT 50",
				[$like, $like, $like]
			);

			$rows = $wpdb->get_results($sql);

			$extract_suggestion = function ($text, $terms) {
				$pos = mb_stripos($text, $terms);

				if ($pos === false) {
					return null;
				}

				$after = mb_substr($text, $pos + mb_strlen($terms));

				// Normalize multiple spaces
				$after = preg_replace('/\s+/u', ' ', $after);

				if ($after === '') {
					return null;
				}

				$firstChar = mb_substr($after, 0, 1);

				// Case 1: incomplete word (next char is a letter or number)
				if (preg_match('/[\p{L}\p{N}]/u', $firstChar)) {
					// return the rest of the word until next space
					if (preg_match('/^([\p{L}\p{N}]+)/u', $after, $m)) {
						return $m[1];
					}
				}

				// Case 2: complete word
				// Remove tags from beginning
				$after = ltrim($after);
				$after = preg_replace('/<[^>]*>/', '', $after); // remove tags safely
				$after = ltrim($after);

				if (preg_match('/^([\p{L}\p{N}]+)/u', $after, $m)) {
					return ' ' . $m[1];
				}

				return null;
			};

			foreach ($rows as $row) {
				$flag = false;
				foreach (['post_title', 'post_excerpt', 'post_content'] as $field) {
					$raw = $row->$field;
					$s = $extract_suggestion($raw, $terms);
					if ($s) {
						$result[] 	= $terms . $s;
						$flag 		= true;
						break;
					}
				}
				if ( $flag ) break;
			}

			return $result;
		} // _fromWebsite

		public function autocomplete( $terms = '' ) {
			$result = []; // Result

			if ( empty( $terms ) || ! $this->getAttr( 'enabled' ) ) {
				return $result;
			}

			if ( $this->getAttr('method') == 'google' ) {
				$result = $this->_fromGoogle( $terms );
			} else {
				$result = $this->_fromWebsite( $terms );
			}

			return $result;

		} // autocomplete
	} // End CPSPAutocomplete
}