<?php
/**
 * /lib/stopwords.php
 *
 * @package Relevanssi
 * @author  Mikko Saari
 * @license https://wordpress.org/about/gpl/ GNU General Public License
 * @see     https://www.relevanssi.com/
 */

/**
 * Reads automatically the correct stopwords for the current language set in
 * WPLANG.
 *
 * The stopwords are first read from the wp_relevanssi_stopwords database table
 * (which is where they were stored before they were moved to an option), but
 * if the table is empty (as it will be in new installations), the stopwords are
 * read from the stopword file for the current language (defaulting to en_US).
 *
 * @global object $wpdb                 The WordPress database interface.
 * @global array  $relevanssi_variables The global Relevanssi variables array.
 *
 * @param boolean $verbose        If true, output results. Default false.
 * @param string  $stopword_table Name of the stopword table to use. Default
 * empty, which means the default table.
 *
 * @return string Result: 'database' for reading from database, 'file' for
 * reading from file, 'no_file' for non-existing file, 'file_error' for file
 * with non-acceptable data.
 */
function relevanssi_populate_stopwords( $verbose = false, string $stopword_table = '' ) {
	global $relevanssi_variables, $wpdb;

	if ( empty( $stopword_table ) ) {
		$stopword_table = $relevanssi_variables['stopword_table'];
	}
	$stopwords_from_table = $wpdb->get_col( "SELECT * FROM $stopword_table" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	if ( count( $stopwords_from_table ) > 1 ) {
		array_walk( $stopwords_from_table, 'relevanssi_add_single_stopword' );
		$verbose && printf(
			"<div id='message' class='updated fade'><p>%s</p></div>",
			esc_html__( 'Added stopwords from the database.', 'relevanssi' )
		);
		return 'database';
	}

	$language      = relevanssi_get_current_language();
	$stopword_file = $relevanssi_variables['plugin_dir']
					. 'stopwords/stopwords.' . $language;

	if ( ! file_exists( $stopword_file ) ) {
		$verbose && printf(
			"<div id='message' class='updated fade'><p>%s</p></div>",
			sprintf(
				// Translators: %s is the language code.
				esc_html__(
					"The stopword file for the language '%s' doesn't exist.",
					'relevanssi'
				),
				esc_html( $language )
			)
		);
		return 'no_file';
	}

	$stopwords = array();
	include $stopword_file; // Contains the stopwords in the $stopwords array.

	if ( ! is_array( $stopwords ) ) {
		$verbose && printf(
			"<div id='message' class='updated fade'><p>%s</p></div>",
			esc_html__(
				"Couldn't read the stopwords from the file.",
				'relevanssi'
			)
		);
		return 'file_error';
	}

	array_walk( $stopwords, 'relevanssi_add_single_stopword' );
	$verbose && printf(
		"<div id='message' class='updated fade'><p>%s</p></div>",
		esc_html__( 'Added stopwords from the stopword file.', 'relevanssi' )
	);

	return 'file';
}

/**
 * Fetches the list of stopwords in the current language.
 *
 * Gets the list of stopwords from the relevanssi_stopwords option using the
 * current language.
 *
 * @return array An array of stopwords;  if nothing is found, returns an empty
 * array.
 */
function relevanssi_fetch_stopwords() {
	$current_language = relevanssi_get_current_language();
	$stopwords_array  = get_option( 'relevanssi_stopwords', array() );
	$stopwords        = isset( $stopwords_array[ $current_language ] ) ? $stopwords_array[ $current_language ] : '';
	$stopword_list    = $stopwords ? explode( ',', $stopwords ) : array();

	return $stopword_list;
}

/**
 * Adds a stopword to the list of stopwords.
 *
 * @param string  $term    The stopword that is added.
 * @param boolean $verbose If true, print out notices. Default true.
 *
 * @return boolean True, if success; false otherwise.
 */
function relevanssi_add_stopword( $term, $verbose = true ) {
	if ( empty( $term ) ) {
		return false;
	}

	$total_stopwords    = 0;
	$successfully_added = 0;

	$terms = explode( ',', $term );
	if ( count( $terms ) > 1 ) {
		$total_stopwords    = count( $terms );
		$successfully_added = array_reduce(
			$terms,
			function ( $counter, $term ) {
				$success = relevanssi_add_single_stopword( trim( $term ) );
				$success && $counter++;
				return $counter;
			},
			0
		);

		$verbose &&
			printf(
				"<div id='message' class='updated fade'><p>%s</p></div>",
				sprintf(
					// translators: %1$d is the successful entries, %2$d is the total entries.
					esc_html__(
						'Successfully added %1$d/%2$d terms to stopwords!',
						'relevanssi'
					),
					intval( $successfully_added ),
					intval( $total_stopwords )
				)
			);
		return boolval( $successfully_added );
	}

	// Add to stopwords.
	$success = relevanssi_add_single_stopword( $term );

	$term = esc_html( $term );

	$verbose && $success && printf(
		"<div id='message' class='updated fade'><p>%s</p></div>",
		sprintf(
			// Translators: %s is the stopword.
			esc_html__( "Term '%s' added to stopwords!", 'relevanssi' ),
			esc_html( stripslashes( $term ) )
		)
	);

	$verbose && ! $success && printf(
		"<div id='message' class='updated fade'><p>%s</p></div>",
		sprintf(
			// Translators: %s is the stopword.
			esc_html__( "Couldn't add term '%s' to stopwords!", 'relevanssi' ),
			esc_html( stripslashes( $term ) )
		)
	);

	return $success;
}

/**
 * Adds a single stopword to the stopword table.
 *
 * @global object $wpdb                 The WP database interface.
 * @global array  $relevanssi_variables The global Relevanssi variables.
 *
 * @param string $term The term to add.
 *
 * @return boolean True if success, false if not.
 */
function relevanssi_add_single_stopword( $term ) {
	if ( empty( $term ) ) {
		return false;
	}

	$stopwords = relevanssi_fetch_stopwords();
	$term      = stripslashes( relevanssi_strtolower( $term ) );

	if ( in_array( $term, $stopwords, true ) ) {
		return false;
	}

	$stopwords[] = $term;

	$success = relevanssi_update_stopwords( $stopwords );

	if ( ! $success ) {
		return false;
	}

	relevanssi_delete_term_from_all_posts( $term );

	return true;
}

/**
 * Updates the current language stopwords in the stopwords option.
 *
 * Fetches the stopwords option, replaces the current language stopwords with
 * the parameter array and updates the option.
 *
 * @param array $stopwords An array of stopwords.
 *
 * @return boolean The return value from update_option().
 */
function relevanssi_update_stopwords( $stopwords ) {
	$current_language = relevanssi_get_current_language();
	$stopwords_option = get_option( 'relevanssi_stopwords', array() );

	$stopwords_option[ $current_language ] = implode( ',', array_filter( $stopwords ) );
	return update_option(
		'relevanssi_stopwords',
		$stopwords_option
	);
}

/**
 * Deletes a term from all posts in the database, language considered.
 *
 * If Polylang or WPML are used, deletes the term only from the posts matching
 * the current language.
 *
 * @param string $term The term to delete.
 */
function relevanssi_delete_term_from_all_posts( $term ) {
	global $wpdb, $relevanssi_variables;

	if ( function_exists( 'pll_languages_list' )
		&& function_exists( 'relevanssi_get_language_term_taxonomy_id' ) ) {
		// The Relevanssi function does not exist when this function is run as
		// part of the Relevanssi install process.
		$term_id = relevanssi_get_language_term_taxonomy_id(
			relevanssi_get_current_language()
		);

		$wpdb->query(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				"DELETE FROM {$relevanssi_variables['relevanssi_table']}
				WHERE term=%s
				AND doc IN (
					SELECT object_id
					FROM $wpdb->term_relationships
					WHERE term_taxonomy_id = %d
				)",
				$term,
				$term_id
			)
		);

		return;
	}

	if ( function_exists( 'icl_object_id' ) && ! function_exists( 'pll_is_translated_post_type' ) ) {
		$language = relevanssi_get_current_language( false );
		$wpdb->query(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				"DELETE FROM {$relevanssi_variables['relevanssi_table']}
				WHERE term=%s
				AND doc IN (
					SELECT DISTINCT(element_id)
					FROM {$wpdb->prefix}icl_translations
					WHERE language_code = %s
				)",
				$term,
				$language
			)
		);

		return;
	}

	// No language defined, just remove from the index.
	$wpdb->query(
		$wpdb->prepare(
			'DELETE FROM ' . $relevanssi_variables['relevanssi_table'] . // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			' WHERE term=%s',
			$term
		)
	);
}

/**
 * Removes all stopwords in specific language.
 *
 * Empties the relevanssi_stopwords option for particular language.
 *
 * @param boolean $verbose  If true, print out notice. Default true.
 * @param string  $language The language code of stopwords. If empty, removes
 * the stopwords for the current language.
 *
 * @return boolean True, if able to remove the options.
 */
function relevanssi_remove_all_stopwords( $verbose = true, $language = false ) {
	if ( ! $language ) {
		$language = relevanssi_get_current_language();
	}

	$stopwords = get_option( 'relevanssi_stopwords', array() );
	unset( $stopwords[ $language ] );
	$success = update_option( 'relevanssi_stopwords', $stopwords );

	$verbose && $success && printf(
		"<div id='message' class='updated fade'><p>%s</p></div>",
		esc_html__(
			'All stopwords removed! Remember to re-index.',
			'relevanssi'
		)
	);

	$verbose && ! $success && printf(
		"<div id='message' class='updated fade'><p>%s</p></div>",
		esc_html__(
			"There was a problem, and stopwords couldn't be removed.",
			'relevanssi'
		)
	);

	return $success;
}

/**
 * Removes a single stopword.
 *
 * @global object $wpdb                 The WP database interface.
 * @global array  $relevanssi_variables The global Relevanssi variables.
 *
 * @param string  $term    The stopword to remove.
 * @param boolean $verbose If true, print out a notice. Default true.
 *
 * @return boolean True if success, false if not.
 */
function relevanssi_remove_stopword( $term, $verbose = true ) {
	$stopwords = relevanssi_fetch_stopwords();
	$term      = stripslashes( $term );
	$stopwords = array_filter(
		$stopwords,
		function ( $stopword ) use ( $term ) {
			return $stopword !== $term;
		}
	);

	$success = relevanssi_update_stopwords( $stopwords );

	$verbose && $success &&
		printf(
			"<div id='message' class='updated fade'><p>%s</p></div>",
			sprintf(
				// Translators: %s is the stopword.
				esc_html__(
					"Term '%s' removed from stopwords! Re-index to get it back to index.",
					'relevanssi'
				),
				esc_html( stripslashes( $term ) )
			)
		);

	$verbose && ! $success &&
		printf(
			"<div id='message' class='updated fade'><p>%s</p></div>",
			sprintf(
				// Translators: %s is the stopword.
				esc_html__(
					"Couldn't remove term '%s' from stopwords!",
					'relevanssi'
				),
				esc_html( stripslashes( $term ) )
			)
		);

	return $success;
}

/**
 * Helper function to remove stopwords from an array.
 *
 * Removes all stopwords from an array of terms. If body stopwords are
 * available, those will also be removed. The terms must be in the array values.
 *
 * @param array $terms An array of terms to clean out.
 *
 * @return array An array of terms with stopwords removed.
 */
function relevanssi_remove_stopwords_from_array( $terms ) {
	$stopword_list = relevanssi_fetch_stopwords();
	if ( function_exists( 'relevanssi_fetch_body_stopwords' ) ) {
		$stopword_list = array_merge( $stopword_list, relevanssi_fetch_body_stopwords() );
	}
	$terms_without_stops = array_diff( $terms, $stopword_list );
	return $terms_without_stops;
}

/**
 * Updates the relevanssi_stopwords setting from a simple string to an array
 * that is required for multilingual stopwords.
 */
function relevanssi_update_stopwords_setting() {
	$stopwords = get_option( 'relevanssi_stopwords' );
	if ( is_object( $stopwords ) ) {
		$array_stopwords = (array) $stopwords;
		update_option( 'relevanssi_stopwords', $array_stopwords );
		return;
	}

	$current_language = relevanssi_get_current_language();

	$array_stopwords[ $current_language ] = $stopwords;
	update_option( 'relevanssi_stopwords', $array_stopwords );
}
