Spamworldpro Mini Shell
Spamworldpro


Server : Apache
System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64
User : corals ( 1002)
PHP Version : 7.4.33
Disable Function : exec,passthru,shell_exec,system
Directory :  /home/corals/mets.corals.io/wp-content/plugins/searchwp/includes/Logic/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/corals/mets.corals.io/wp-content/plugins/searchwp/includes/Logic/Synonyms.php
<?php

/**
 * SearchWP Synonyms.
 *
 * @package SearchWP
 * @author  Jon Christopher
 */

namespace SearchWP\Logic;

use SearchWP\Query;
use SearchWP\Settings;
use SearchWP\Utils;
use SearchWP\Support\Str;

/**
 * Class Synonyms is responsible for applying synonyms to Tokens.
 *
 * @since 4.0
 */
class Synonyms {

	/**
	 * The language code.
	 *
	 * @since 4.0
	 *
	 * @var string
	 */
	private $language_code;

	/**
	 * A flag to track the detection of the synonyms in a search string.
	 *
	 * @since 4.2.3
	 *
	 * @var bool
	 */
	private $found_synonym = false;

	/**
	 * Original search string before applying synonyms modifications.
	 *
	 * @since 4.2.3
	 *
	 * @var string
	 */
	private $original_search_string = '';

	/**
	 * Synonyms.
	 *
	 * @since 4.0
	 *
	 * @var array
	 */
	private $synonyms = [];

	/**
	 * Grouped synonyms.
	 *
	 * @since 4.2.3
	 *
	 * @var array
	 */
	public static $synonym_groups = [];

	/**
	 * Synonyms constructor.
	 *
	 * @since 4.0
	 */
	public function __construct() {

		// TODO: Build in support for multilanguage setups (WPML, Polylang, soon to be core).
		$this->language_code = strtolower( substr( get_locale(), 0, 2 ) );

		// TODO: Running the hooks will be moved out of the constructor as a part of the plugin bootstrap rewiring.
		$this->hooks();
	}

	/**
	 * Class hooks.
	 *
	 * @since 4.2.3
	 *
	 * @return void
	 */
	private function hooks() {

		// Apply synonyms to query search string.
		add_filter( 'searchwp\query\search_string', [ $this, 'apply' ], 5, 2 );
	}

	/**
	 * Applies synonyms to tokens.
	 *
	 * @since 4.0
	 *
	 * @param string $search_string Original search string.
	 * @param Query  $query         Query object.
	 *
	 * @return string Synonyms applied.
	 */
	public function apply( string $search_string, Query $query ): string {

		$search_string = Str::lower( $search_string );

		$this->set_original_search_string( $search_string );
		$this->set_initial_synonym_groups( $search_string );

		$this->set_synonyms( $search_string, $query );
		$this->set_partial_matches( $search_string, $query );

		$search_string = $this->apply_synonyms( $search_string, $query );
		$search_string = Utils::remove_duplicate_words_from_string( $search_string );

		$this->remove_duplicate_tokens_from_token_groups();

		return $search_string;
	}

	/**
	 * Getter for saved Synonyms.
	 *
	 * @since 4.0
	 *
	 * @return array
	 */
	public function get(): array {

		$synonyms = Settings::get( 'synonyms' );

		return $synonyms === false ? [] : $this->normalize( $synonyms );
	}

	/**
	 * Normalizes synonym model.
	 *
	 * @since 4.0
	 *
	 * @param array $synonyms Incoming synonyms.
	 *
	 * @return array Normalized synonyms.
	 */
	public function normalize( array $synonyms = [] ): array {

		return array_values( array_filter( array_map( function( $synonym ) {
			if ( empty( $synonym['sources'] ) || empty( $synonym['synonyms'] ) ) {
				return false;
			}

			return [
				'sources' => implode( ', ', array_map( function( $source ) {
					return trim( sanitize_text_field( $source ) );
				}, explode( ',', $synonym['sources'] ) ) ),
				'synonyms' => implode( ', ', array_map( function( $source ) {
					return trim( sanitize_text_field( $source ) );
				}, explode( ',', $synonym['synonyms'] ) ) ),
				'replace' => ! empty( $synonym['replace'] )
			];
		}, $synonyms ) ) );
	}

	/**
	 * Updates saved synonyms.
	 *
	 * @since 4.0
	 *
	 * @param array $synonyms Synonyms to save.
	 *
	 * @return array Saved Synonyms.
	 */
	public function save( array $synonyms = [] ): array {

		$synonyms = $this->normalize( $synonyms );

		Settings::update( 'synonyms', $synonyms );

		return $synonyms;
	}

	/**
	 * Save the original search string for future use.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 *
	 * @return void
	 */
	private function set_original_search_string( string $search_string ) {

		$this->original_search_string = $search_string;
	}

	/**
	 * Set initial empty synonym groups to fill them with synonyms later.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 *
	 * @return void
	 */
	private function set_initial_synonym_groups( string $search_string ) {

		self::$synonym_groups = array_fill_keys( explode( ' ', $search_string ), [] );
	}

	/**
	 * Sets the synonyms for this application.
	 *
	 * @since 4.0
	 * @since 4.2.3 Changed public set() to private set_synonyms()
	 *
	 * @param string $search_string Query search string.
	 * @param Query  $query         Query object.
	 *
	 * @return void Synonyms.
	 */
	private function set_synonyms( string $search_string, Query $query ) {

		$synonyms = $this->get();

		// Allow developers to customize.
		$synonyms = (array) apply_filters( 'searchwp\synonyms', $synonyms, [
			'search_string' => $search_string,
			'query'         => $query,
		] );

		// Ensure valid format.
		$this->synonyms = array_filter( $synonyms, function( $synonym ) {
			return isset( $synonym['sources'] )
			       && isset( $synonym['synonyms'] )
			       && isset( $synonym['replace'] );
		} );
	}

	/**
	 * Applies partial matches to synonyms.
	 *
	 * @since 4.1
	 * @since 4.2.3 Changed to private method
	 *
	 * @param string $search_string Query search string.
	 * @param Query  $query         Query object.
	 *
	 * @return void
	 */
	private function set_partial_matches( string $search_string, Query $query ) {

		if ( empty( $this->synonyms ) ) {
			return;
		}

		$data = [
			'search' => $search_string,
			'query'  => $query,
		];

		// Apply our (wildcard-based) partial matching by default.
		if ( ! apply_filters( 'searchwp\synonyms\partial_matches', true, $data ) ) {
			return;
		}

		$synonyms       = $this->synonyms;
		$_search_string = $this->original_search_string;

		foreach ( $synonyms as $index => $synonym ) {
			$sources = array_map( 'trim', explode( ',', $synonym['sources'] ) );

			foreach ( $sources as $source ) {
				// In order for partial matching to apply, a wildcard character (*) is used
				// because there are too many common cases where more generalized partial
				// matching has too many overruns and it triggers unwanted synonym hits.
				if ( false === strpos( $source, '*' ) ) {
					continue;
				}

				// Pad the search string to prevent overrun.
				$_search_string = ' ' . $_search_string . ' ';

				// Convert the wildcard into something that won't be double encoded.
				$placeholder     = Utils::get_placeholder( false );
				$original_source = $source;
				$source          = str_replace( '*', $placeholder, $source );

				$term    = preg_quote( $_search_string, '/' );
				$needle  = $source[0] !== '*' ?
					str_replace( $placeholder, '\S*\b', preg_quote( $source, '/' ) ) :
					str_replace( $placeholder, '\b\S*', preg_quote( $source, '/' ) );

				$pattern = '/' . $needle . '/ius';

				if ( 1 === preg_match( $pattern, $term, $matches ) ) {
					$new_sources = implode( ',', array_map( 'trim', $matches ) );
					$this->synonyms[ $index ]['sources'] = str_replace( $original_source, $new_sources, $this->synonyms[ $index ]['sources'] );
				}

				$_search_string = trim( $_search_string );
			}
		}
	}

	/**
	 * Limit applicable synonyms if there are quoted phrases in a search string.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 * @param Query  $query         Query object.
	 * @param array  $synonyms      Synonyms data from the settings.
	 *
	 * @return array
	 */
	private function limit_applicable_synonyms_if_quoted_phrases_found( string $search_string, Query $query, array $synonyms ): array {

		// If there are quoted phrases, limit applicable synonyms.
		if ( $phrases = Utils::search_string_has_phrases( $search_string, $query ) ) {
			$synonyms_filtered = array_values( array_filter( $synonyms, function ( $synonym ) use ( $phrases ) {
				return ! empty( array_filter( $phrases, function ( $phrase ) use ( $synonym ) {
					return false !== strpos( $synonym['sources'], $phrase );
				} ) );
			} ) );

			if ( ! empty( $synonyms_filtered ) || apply_filters( 'searchwp\synonyms\strict', false ) ) {
				$synonyms = $synonyms_filtered;
			}
		}

		return $synonyms;
	}

	/**
	 * Apply synonyms to a search string.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 * @param Query  $query         Query object.
	 *
	 * @return string
	 */
	private function apply_synonyms( string $search_string, Query $query ): string {

		$synonyms = $this->synonyms;
		$synonyms = $this->limit_applicable_synonyms_if_quoted_phrases_found( $search_string, $query, $synonyms );

		if ( empty( $synonyms ) ) {
			return $search_string;
		}

		foreach ( $synonyms as $synonym ) {
			// Multiple sources can be set using comma separation.
			$sources = array_filter( array_map( 'trim', explode( ',', $synonym['sources'] ) ) );

			if ( empty( $sources ) ) {
				continue;
			}

			// If we're not replacing, prepend the search string to the synonyms regardless of whether it applies.
			// We are not prepending the source of the synonym because there may be multiple, comma
			// separated sources which could introduce unwanted tokens into the search.
			if ( ! $synonym['replace'] ) {
				$synonym['original_synonyms'] = $synonym['synonyms'];
				$synonym['synonyms']          = $this->original_search_string . ' ' . $synonym['synonyms'];
			}

			// Reset the found synonyms flag before using it.
			$this->found_synonym = false;

			$search_string = $this->process_synonym_sources( $search_string, $sources, $synonym );

			// Assume that one synonym replacement is enough and in doing so prevent
			// redundant synonym application, but also base that on a hook to allow
			// developers to 'stack' synonyms when that's what they want to do.
			if ( apply_filters( 'searchwp\synonyms\aggressive', true ) && $this->found_synonym ) {
				break;
			}

			// Reset the found synonyms flag after using it.
			$this->found_synonym = false;
		}

		return $search_string;
	}

	/**
	 * Process single synonym sources.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 * @param array  $sources       Single synonym sources.
	 * @param array  $synonym       Single synonym data.
	 *
	 * @return string
	 */
	private function process_synonym_sources( string $search_string, array $sources, array $synonym ): string {

		// Iterate over the sources to see if there's a match.
		foreach ( $sources as $source ) {

			// If source contains a wildcard we can skip it as it was already processed for partial matches.
			if ( false !== strpos( $source, '*' ) ) {
				continue;
			}

			// If this source is a quoted phrase, remove the quotes first.
			$source = str_replace( '"', '', $source );

			// If there's a space in the search string and the synonym source opt to replace only the whole source.
			if ( false !== strpos( $search_string, ' ' ) && false !== strpos( $source, ' ' ) ) {
				$search_string = $this->process_compound_source( $search_string, $source, $synonym );
			} else {
				$search_string = $this->process_regular_source( $search_string, $source, $synonym );
			}
		}

		return $search_string;
	}

	/**
	 * Process single compound (consisting of several words) source.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 * @param string $source        Single source.
	 * @param array  $synonym       Single synonym data.
	 *
	 * @return string
	 */
	private function process_compound_source( string $search_string, string $source, array $synonym ): string {

		$search_string_before = $search_string;
		$search_string        = preg_replace( '/\b' . preg_quote( $source, '/' ) . '\b/i', $synonym['synonyms'], str_replace( '"', '', $search_string ) );

		// If the synonyms are present in the group as a source they should be removed.
		foreach ( explode( ' ', $source ) as $source_tokens ) {
			if ( isset( self::$synonym_groups[ $source_tokens ] ) ) {
				unset( self::$synonym_groups[ $source_tokens ] );
			}
		}

		// We can now add the synonyms tokens as new sources.
		$replace_synonyms = array_map( 'trim', explode( ' ', $synonym['synonyms'] ) );
		$new_tokens       = array_fill_keys( $replace_synonyms, [] );

		self::$synonym_groups = array_merge( self::$synonym_groups, $new_tokens );

		if ( $search_string_before !== $search_string ) {
			$this->found_synonym = true;
		}

		return $search_string;

		// TODO: use searchwp\query\tokens\limit hook to adjust limit based on count( $synonym['synonyms'] ) if necessary?
	}

	/**
	 * Process regular non-compound (one word) source.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 * @param string $source        Single source.
	 * @param array  $synonym       Single synonym data.
	 *
	 * @return string
	 */
	private function process_regular_source( string $search_string, string $source, array $synonym ): string {

		$search_string_before = $search_string;

		if ( false === strpos( $search_string, $source ) ) {
			return $search_string;
		}

		if ( $synonym['replace'] ) {
			$search_string = $this->process_regular_source_replace( $search_string, $source, $synonym );
		} else {
			$search_string = $this->process_regular_source_no_replace( $search_string, $source, $synonym );
		}

		if ( $search_string_before !== $search_string ) {
			$this->found_synonym = true;
		}

		return $search_string;

		// TODO: use searchwp\query\tokens\limit hook to adjust limit based on count( $synonym['synonyms'] ) if necessary?
	}

	/**
	 * Process regular non-compound (one word) source replacing it with a synonym.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 * @param string $source        Single source.
	 * @param array  $synonym       Single synonym data.
	 *
	 * @return string
	 */
	private function process_regular_source_replace( string $search_string, string $source, array $synonym ): string {

		$new_tokens    = [];
		$search_string = preg_replace( '/\b' . preg_quote( $source, '/' ) . '\b/i', $synonym['synonyms'], $search_string );

		if ( isset( self::$synonym_groups[ $source ] ) ) {
			unset( self::$synonym_groups[ $source ] );
		}
		$replace_synonyms                   = array_map( 'trim', explode( ',', $synonym['synonyms'] ) );
		$new_tokens[ $replace_synonyms[0] ] = array_slice( $replace_synonyms, 1 );
		self::$synonym_groups               = array_merge( self::$synonym_groups, $new_tokens );

		return $search_string;
	}

	/**
	 * Process regular non-compound (one word) source without replacing it with a synonym.
	 *
	 * @since 4.2.3
	 *
	 * @param string $search_string Search string.
	 * @param string $source        Single source.
	 * @param array  $synonym       Single synonym data.
	 *
	 * @return string
	 */
	private function process_regular_source_no_replace( string $search_string, string $source, array $synonym ): string {

		$search_string = preg_replace( '/\b' . preg_quote( $source, '/' ) . '\b/i', $source . ' ' . $synonym['original_synonyms'], $search_string );

		if ( in_array( $source, array_keys( self::$synonym_groups ), true ) ) {
			self::$synonym_groups[ $source ] = array_merge( self::$synonym_groups[ $source ], array_map( 'trim', explode( ',', $synonym['original_synonyms'] ) ) );
		} else {
			self::$synonym_groups[ $source ] = array_map( 'trim', explode( ',', $synonym['original_synonyms'] ) );
		}

		return $search_string;
	}

	/**
	 * Remove duplicate token between sources and synonyms from token groups.
	 *
	 * @since 4.2.3
	 *
	 * @return void
	 */
	private function remove_duplicate_tokens_from_token_groups(): void {

		$synonym_groups_keys = array_keys( self::$synonym_groups );

		foreach ( self::$synonym_groups as $tokens ) {
			foreach ( $tokens as $token ) {
				if ( in_array( $token, $synonym_groups_keys, true ) && empty( self::$synonym_groups[ $token ] ) ) {
					unset( self::$synonym_groups[ $token ] );
				}
			}
		}
	}
}

Spamworldpro Mini