HEX
Server: Apache
System: Linux sxb1plmcpnl510113.prod.sxb1.secureserver.net 4.18.0-553.58.1.lve.el8.x86_64 #1 SMP Fri Jul 4 12:07:06 UTC 2025 x86_64
User: acnbijigo78q (10488831)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: /home/acnbijigo78q/public_html/wp-content/plugins/wpml-elasticpress/src/Sync/Singular.php
<?php

namespace WPML\ElasticPress\Sync;

use ElasticPress\Indexables;

use WPML\ElasticPress\Constants;
use WPML\ElasticPress\Manager\Indices;

use WPML\ElasticPress\Traits\CrudPropagation;

class Singular {

	use CrudPropagation;

	/**
	 * In ElasticPress 5.0.0, the \ElasticPress\SyncManager::sync_queue attribute mutated:
	 * - Before, it was an array indexed by post IDs with a TRUE value for those modified.
	 * - After, those same entries got spread in parent arrays per site ID.
	 *
	 * For example, [ 123 => true ] became [ 1 => [ 123 => true ] ],
	 * where 1 is the a block ID and 123 is a post ID for a post in the blog with ID equal 1.
	 *
	 * Nice for them to change some public data structure, right? Well, life!
	 */
	const SYNC_QUEUE_API_CHANGE_V1 = '5.0.0';

	/** @var Indexables */
	private $indexables;

	/** @var Indices */
	private $indicesManager;

	/** @var array */
	private $activeLanguages;

	/** @var string */
	private $defaultLanguage;

	/** @var string */
	private $elasticPressVersion;

	/**
	 * @param Indexables $indexables
	 * @param Indices    $indicesManager
	 * @param array      $activeLanguages
	 * @param string     $defaultLanguage
	 * @param string     $elasticPressVersion
	 */
	public function __construct(
		Indexables $indexables,
		Indices    $indicesManager,
		$activeLanguages,
		$defaultLanguage,
		$elasticPressVersion
	) {
		$this->indexables          = $indexables;
		$this->indicesManager      = $indicesManager;
		$this->activeLanguages     = $activeLanguages;
		$this->defaultLanguage     = $defaultLanguage;
		$this->elasticPressVersion = $elasticPressVersion;
	}

	public function addHooks() {
		add_filter( 'pre_ep_index_sync_queue', [ $this, 'manageSyncQueue' ], Constants::LATE_HOOK_PRIORITY, 3 );

		$beforeUnsyncHooks = [ 'wp_trash_post', 'delete_post' ];
		array_walk(
			$beforeUnsyncHooks,
			function( $hook ) {
				add_action( $hook, [ $this, 'startUnsync' ], Constants::EARLY_HOOK_PRIORITY );
				add_action( $hook, [ $this, 'clearIndexLanguage' ], Constants::LATE_HOOK_PRIORITY );
			}
		);
		$afterUnsyncHooks = [ 'trashed_post', 'deleted_post' ];
		array_walk(
			$afterUnsyncHooks,
			function( $hook ) {
				add_action( $hook, [ $this, 'completeUnsync' ], Constants::LATE_HOOK_PRIORITY );
			}
		);

		add_action( 'transition_post_status', [ $this, 'maybeUnsyncByStatus' ], Constants::LATE_HOOK_PRIORITY, 3 );
	}

	/**
	 * @param  \ElasticPress\SyncManager $syncManager
	 *
	 * @return int[]
	 */
	private function getIdsInSyncQueue( $syncManager ) {
		$syncQueue = $syncManager->sync_queue;

		if ( version_compare( $this->elasticPressVersion, self::SYNC_QUEUE_API_CHANGE_V1, '<' ) ) {
			return array_keys( $syncQueue );
		}
		$currentBlogId           = get_current_blog_id();
		$syncQueueForCurrentBlog = $syncQueue[ $currentBlogId ] ?? [];
		return array_keys( $syncQueueForCurrentBlog ) ;
	}

	/**
	 * @param  bool                      $halt
	 * @param  \ElasticPress\SyncManager $syncManager
	 * @param  string                    $indexableSlug
	 *
	 * @return bool
	 */
	public function manageSyncQueue( $halt, $syncManager, $indexableSlug ) {
		if ( Constants::INDEXABLE_SLUG_POST !== $indexableSlug ) {
			return $halt;
		}

		$this->clearIds();

		// Store the affected post IDs
		//
		// Synced post is in the default language:
		// - If it is translatable AND displays as translated:
		//     - Sync it
		//     - Update it in other language indices using it
		// - If it is translatable but NOT displays as translated:
		//     - Sync it
		// - If it is not translatable:
		//     - Sync it in ALL languages
		//
		// Synced post is NOT in the default language:
		// - It is ALWAYS translatable, of course
		// - If it displays as translated:
		//     - Maybe remove the ID for the default language post from the current language index
		//         - Skip those which do not exist in the current language index (when updating an existing translation, for example)
		//     - Maybe sync the default language post in the default language index to sync language field values
		$this->propagateIds( $this->getIdsInSyncQueue( $syncManager ) );

		$this->manageIds( 'sync', 'main' );
		$this->manageIds( 'delete', 'related' );

		$this->indicesManager->clearCurrentIndexLanguage();

		$this->syncIds( $this->combineIds( 'related' ) );

		$this->clearIds();

		return true;
	}

	/**
	 * @param int $postId
	 */
	public function startUnsync( $postId ) {
		// Native hook to halt execution on autosave
		if ( apply_filters( 'ep_skip_autosave_sync', true, __FUNCTION__ ) ) {
			if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
				// Bypass saving if doing autosave
				// @codeCoverageIgnoreStart
				return;
				// @codeCoverageIgnoreEnd
			}
		}
		// This happens before delete_post:1 where WPML runs delete post actions
		$this->clearIds();
		// Store the affected post IDs for non-translatable and display-as-translated post types
		$this->propagateIds( [ $postId ] );
		// For display-as-translation mode, sync original language post in the language of the one deleted
		$this->manageIds( 'sync', 'related' );
		// Delete document in its current language (for translatable modes) and in all languages (for non translatable modes)
		// Note that part of this will try to happen again on the native delete_post:10 callback
		$this->manageIds( 'delete', 'main' );
		// Set the right index language so any pending indexable operation happens in the right index
		$this->setIndexLanguage( $postId );
	}

	/**
	 * @param int $postId
	 */
	public function completeUnsync( $postId ) {
		// Native hook to halt execution on autosave
		if ( apply_filters( 'ep_skip_autosave_sync', true, __FUNCTION__ ) ) {
			if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
				// Bypass saving if doing autosave
				// @codeCoverageIgnoreStart
				return;
				// @codeCoverageIgnoreEnd
			}
		}
		// For display-as-translated mode:
		// - make sure that the default language index includes proper values in language fields for the default language post
		// - get it propagated to any language that got its translation removed
		$this->indicesManager->clearCurrentIndexLanguage();
		$this->lateSyncIds( $this->combineIds( 'related' ) );
		$this->clearIds();

		return true;
	}

	/**
	 * @param int $postId
	 */
	public function setIndexLanguage( $postId ) {
		$object = get_post( $postId );
		if ( empty( $object ) ) {
			return;
		}

		$language = apply_filters( 'wpml_element_language_code', null, [
			'element_id'   => $postId,
			'element_type' => $object->post_type,
		]);
		if ( ! in_array( $language, $this->activeLanguages, true ) ) {
			$language = $this->defaultLanguage;
		}

		$this->indicesManager->setCurrentIndexLanguage( $language );
	}

	/**
	 * @param int $postId
	 */
	public function clearIndexLanguage( $postId ) {
		$this->indicesManager->clearCurrentIndexLanguage();
	}

	/**
	 * @param int $metaId
	 * @param int $postId
	 */
	public function setIndexLanguageByMeta( $metaId, $postId ) {
		$this->setIndexLanguage( $postId );
	}

	/**
	 * @param int $metaId
	 * @param int $postId
	 */
	public function clearIndexLanguageByMeta( $metaId, $postId ) {
		$this->clearIndexLanguage( $postId );
	}

	/**
	 * @param string   $newStatus
	 * @param string   $oldStatus
	 * @param \WP_Post $post
	 */
	public function maybeUnsyncByStatus( $newStatus, $oldStatus, $post ) {
		if ( $oldStatus === $newStatus ) {
			return;
		}

		$postIndexable = $this->indexables->get( Constants::INDEXABLE_SLUG_POST );
		if ( false === $postIndexable ) {
			return;
		}

		$indexablePostStati = $postIndexable->get_indexable_post_status();
		if ( in_array( $post->post_status, $indexablePostStati, true ) ) {
			return;
		}

		$this->startUnsync( $post->ID );
		$this->clearIndexLanguage( $post->ID );
		$this->completeUnsync( $post->ID );
	}

}