?
Current File : /home/c/i/d/cideo/www/wp-includesVIp/js/crop/images/sync.tar
admin-sync.php000066600000016373151264450030007335 0ustar00<?php

/**
 * Manages copy and synchronization of terms and post metas
 *
 * @since 1.2
 */
class PLL_Admin_Sync extends PLL_Sync {

	/**
	 * Constructor
	 *
	 * @since 1.2
	 *
	 * @param object $polylang
	 */
	public function __construct( &$polylang ) {
		parent::__construct( $polylang );

		add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 3 );
		add_filter( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ) );
		add_action( 'rest_api_init', array( $this, 'new_post_translation' ) ); // Block editor
		add_action( 'add_meta_boxes', array( $this, 'new_post_translation' ), 5 ); // Classic editor, before Types which populates custom fields in same hook with priority 10
	}

	/**
	 * Translate post parent if exists when using "Add new" ( translation )
	 *
	 * @since 0.6
	 *
	 * @param int   $post_parent Post parent ID
	 * @param int   $post_id     Post ID, unused
	 * @param array $postarr     Array of parsed post data
	 * @return int
	 */
	public function wp_insert_post_parent( $post_parent, $post_id, $postarr ) {
		if ( isset( $_GET['from_post'], $_GET['new_lang'], $_GET['post_type'] ) ) {
			check_admin_referer( 'new-post-translation' );
			// Make sure not to impact media translations created at the same time
			if ( $_GET['post_type'] === $postarr['post_type'] && ( $id = wp_get_post_parent_id( (int) $_GET['from_post'] ) ) && $parent = $this->model->post->get_translation( $id, sanitize_key( $_GET['new_lang'] ) ) ) {
				$post_parent = $parent;
			}
		}
		return $post_parent;
	}

	/**
	 * Copy menu order, comment, ping status and optionally the date when creating a new tanslation
	 *
	 * @since 2.5
	 *
	 * @param array $data An array of slashed post data.
	 * @return array
	 */
	public function wp_insert_post_data( $data ) {
		if ( isset( $GLOBALS['pagenow'], $_GET['from_post'], $_GET['new_lang'] ) && 'post-new.php' === $GLOBALS['pagenow'] && $this->model->is_translated_post_type( $data['post_type'] ) ) {
			check_admin_referer( 'new-post-translation' );

			$from_post_id = (int) $_GET['from_post'];
			$from_post    = get_post( $from_post_id );

			foreach ( array( 'menu_order', 'comment_status', 'ping_status' ) as $property ) {
				$data[ $property ] = $from_post->$property;
			}

			// Copy the date only if the synchronization is activated
			if ( in_array( 'post_date', $this->options['sync'] ) ) {
				$data['post_date']     = $from_post->post_date;
				$data['post_date_gmt'] = $from_post->post_date_gmt;
			}
		}

		return $data;
	}

	/**
	 * Copy post metas, and taxonomies when using "Add new" ( translation )
	 *
	 * @since 2.5
	 */
	public function new_post_translation() {
		global $post;
		static $done = array();

		if ( isset( $GLOBALS['pagenow'], $_GET['from_post'], $_GET['new_lang'] ) && 'post-new.php' === $GLOBALS['pagenow'] && $this->model->is_translated_post_type( $post->post_type ) ) {
			check_admin_referer( 'new-post-translation' );

			// Capability check already done in post-new.php
			$from_post_id = (int) $_GET['from_post'];
			$lang         = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) );

			if ( ! $from_post_id || ! $lang || ! empty( $done[ $from_post_id ] ) ) {
				return;
			}

			$done[ $from_post_id ] = true; // Avoid a second duplication in the block editor. Using an array only to allow multiple phpunit tests.

			$this->taxonomies->copy( $from_post_id, $post->ID, $lang->slug );
			$this->post_metas->copy( $from_post_id, $post->ID, $lang->slug );

			if ( is_sticky( $from_post_id ) ) {
				stick_post( $post->ID );
			}
		}
	}

	/**
	 * Get post fields to synchronize
	 *
	 * @since 2.4
	 *
	 * @param object $post Post object
	 * @return array
	 */
	protected function get_fields_to_sync( $post ) {
		global $wpdb;

		$postarr = parent::get_fields_to_sync( $post );

		// For new drafts, save the date now otherwise it is overriden by WP. Thanks to JoryHogeveen. See #32.
		if ( in_array( 'post_date', $this->options['sync'] ) && isset( $GLOBALS['pagenow'], $_GET['from_post'], $_GET['new_lang'] ) && 'post-new.php' === $GLOBALS['pagenow'] ) {
			check_admin_referer( 'new-post-translation' );

			unset( $postarr['post_date'] );
			unset( $postarr['post_date_gmt'] );

			$original = get_post( (int) $_GET['from_post'] );
			$wpdb->update(
				$wpdb->posts,
				array(
					'post_date'     => $original->post_date,
					'post_date_gmt' => $original->post_date_gmt,
				),
				array( 'ID' => $post->ID )
			);
		}

		if ( isset( $GLOBALS['post_type'] ) ) {
			$post_type = $GLOBALS['post_type'];
		} elseif ( isset( $_REQUEST['post_type'] ) ) {
			$post_type = sanitize_key( $_REQUEST['post_type'] ); // 2nd case for quick edit
		}

		// Make sure not to impact media translations when creating them at the same time as post
		if ( in_array( 'post_parent', $this->options['sync'] ) && ( ! isset( $post_type ) || $post_type !== $post->post_type ) ) {
			unset( $postarr['post_parent'] );
		}

		return $postarr;
	}

	/**
	 * Synchronizes post fields in translations
	 *
	 * @since 1.2
	 *
	 * @param int    $post_id      post id
	 * @param object $post         post object
	 * @param array  $translations post translations
	 */
	public function pll_save_post( $post_id, $post, $translations ) {
		parent::pll_save_post( $post_id, $post, $translations );

		// Sticky posts
		if ( in_array( 'sticky_posts', $this->options['sync'] ) ) {
			$stickies = get_option( 'sticky_posts' );
			if ( isset( $_REQUEST['sticky'] ) && 'sticky' === $_REQUEST['sticky'] ) { // phpcs:ignore WordPress.Security.NonceVerification
				$stickies = array_merge( $stickies, array_values( $translations ) );
			} else {
				$stickies = array_diff( $stickies, array_values( $translations ) );
			}
			update_option( 'sticky_posts', array_unique( $stickies ) );
		}
	}

	/**
	 * Some backward compatibility with Polylang < 2.3
	 * allows to call PLL()->sync->copy_post_metas() and PLL()->sync->copy_taxonomies()
	 * used for example in Polylang for WooCommerce
	 * the compatibility is however only partial as the 4th argument $sync is lost
	 *
	 * @since 2.3
	 *
	 * @param string $func Function name
	 * @param array  $args Function arguments
	 */
	public function __call( $func, $args ) {
		$obj = substr( $func, 5 );

		if ( is_object( $this->$obj ) && method_exists( $this->$obj, 'copy' ) ) {
			if ( WP_DEBUG ) {
				$debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
				$i = 1 + empty( $debug[1]['line'] ); // The file and line are in $debug[2] if the function was called using call_user_func

				trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
					sprintf(
						'%1$s was called incorrectly in %3$s on line %4$s: the call to PLL()->sync->%1$s() has been deprecated in Polylang 2.3, use PLL()->sync->%2$s->copy() instead.' . "\nError handler",
						esc_html( $func ),
						esc_html( $obj ),
						esc_html( $debug[ $i ]['file'] ),
						absint( $debug[ $i ]['line'] )
					)
				);
			}
			return call_user_func_array( array( $this->$obj, 'copy' ), $args );
		}

		$debug = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
		trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions
			sprintf(
				'Call to undefined function PLL()->sync->%1$s() in %2$s on line %3$s' . "\nError handler",
				esc_html( $func ),
				esc_html( $debug[0]['file'] ),
				absint( $debug[0]['line'] )
			),
			E_USER_ERROR
		);
	}
}
sync-metas.php000066600000027730151264450030007355 0ustar00<?php

/**
 * Abstract class to manage the copy and synchronization of metas
 *
 * @since 2.3
 */
abstract class PLL_Sync_Metas {
	public $model;
	protected $meta_type, $prev_value, $to_copy;

	/**
	 * Constructor
	 *
	 * @since 2.3
	 *
	 * @param object $polylang
	 */
	public function __construct( &$polylang ) {
		$this->model = &$polylang->model;

		add_filter( "add_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
		add_filter( "update_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );
		add_filter( "delete_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 );

		$this->add_all_meta_actions();

		add_action( "pll_save_{$this->meta_type}", array( $this, 'save_object' ), 10, 3 );
	}

	/**
	 * Removes "added_{$this->meta_type}_meta" action
	 *
	 * @since 2.3
	 */
	protected function remove_add_meta_action() {
		remove_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ), 10, 4 );
	}

	/**
	 * Removes all meta synchronization actions and filters
	 *
	 * @since 2.3
	 */
	protected function remove_all_meta_actions() {
		$this->remove_add_meta_action();

		remove_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999, 5 );
		remove_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ), 10, 4 );

		remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
		remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );
	}

	/**
	 * Adds "added_{$this->meta_type}_meta" action
	 *
	 * @since 2.3
	 */
	protected function restore_add_meta_action() {
		add_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ), 10, 4 );
	}

	/**
	 * Adds meta synchronization actions and filters
	 *
	 * @since 2.3
	 */
	protected function add_all_meta_actions() {
		$this->restore_add_meta_action();

		add_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999, 5 ); // Very late in case a filter prevents the meta to be updated
		add_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ), 10, 4 );

		add_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
		add_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );
	}

	/**
	 * Maybe modify ("translate") a meta value when it is copied or synchronized
	 *
	 * @since 2.3
	 *
	 * @param mixed  $value Meta value
	 * @param string $key   Meta key
	 * @param int    $from  Id of the source
	 * @param int    $to    Id of the target
	 * @param string $lang  Language of target
	 * @return mixed
	 */
	protected function maybe_translate_value( $value, $key, $from, $to, $lang ) {
		/**
		 * Filter a meta value before is copied or synchronized
		 *
		 * @since 2.3
		 *
		 * @param mixed  $value Meta value
		 * @param string $key   Meta key
		 * @param string $lang  Language of target
		 * @param int    $from  Id of the source
		 * @param int    $to    Id of the target
		 */
		return apply_filters( "pll_translate_{$this->meta_type}_meta", maybe_unserialize( $value ), $key, $lang, $from, $to );
	}

	/**
	 * Get the custom fields to copy or synchronize
	 *
	 * @since 2.3
	 *
	 * @param int    $from Id of the post from which we copy informations
	 * @param int    $to   Id of the post to which we paste informations
	 * @param string $lang Language slug
	 * @param bool   $sync True if it is synchronization, false if it is a copy
	 * @return array List of meta keys
	 */
	protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) {
		/**
		 * Filter the custom fields to copy or synchronize
		 *
		 * @since 0.6
		 * @since 1.9.2 The `$from`, `$to`, `$lang` parameters were added.
		 *
		 * @param array  $keys List of custom fields names
		 * @param bool   $sync True if it is synchronization, false if it is a copy
		 * @param int    $from Id of the post from which we copy informations
		 * @param int    $to   Id of the post to which we paste informations
		 * @param string $lang Language slug
		 */
		return array_unique( apply_filters( "pll_copy_{$this->meta_type}_metas", array(), $sync, $from, $to, $lang ) );
	}

	/**
	 * Disallow modifying synchronized meta if the current user can not modify translations
	 *
	 * @since 2.6
	 *
	 * @param null|bool $check    Whether to allow adding/updating/deleting metadata.
	 * @param int       $id       Object ID.
	 * @param string    $meta_key Meta key.
	 * @return null|bool
	 */
	public function can_synchronize_metadata( $check, $id, $meta_key ) {
		if ( ! $this->model->{$this->meta_type}->current_user_can_synchronize( $id ) ) {
			$tr_ids = $this->model->{$this->meta_type}->get_translations( $id );

			foreach ( $tr_ids as $lang => $tr_id ) {
				if ( $tr_id != $id ) {
					$to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
					if ( in_array( $meta_key, $to_copy ) ) {
						return false;
					}
				}
			}
		}
		return $check;
	}

	/**
	 * Synchronize added metas across translations
	 *
	 * @since 2.3
	 *
	 * @param int    $mid        Meta id.
	 * @param int    $id         Object ID.
	 * @param string $meta_key   Meta key.
	 * @param mixed  $meta_value Meta value. Must be serializable if non-scalar.
	 */
	public function add_meta( $mid, $id, $meta_key, $meta_value ) {
		static $avoid_recursion = false;

		if ( ! $avoid_recursion ) {
			$avoid_recursion = true;
			$tr_ids = $this->model->{$this->meta_type}->get_translations( $id );

			foreach ( $tr_ids as $lang => $tr_id ) {
				if ( $tr_id != $id ) {
					$to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
					if ( in_array( $meta_key, $to_copy ) ) {
						$meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
						add_metadata( $this->meta_type, $tr_id, $meta_key, is_string( $meta_value ) ? wp_slash( $meta_value ) : $meta_value );
					}
				}
			}

			$avoid_recursion = false;
		}
	}

	/**
	 * Stores the previous value when updating metas
	 *
	 * @since 2.3
	 *
	 * @param null|bool $r          Not used
	 * @param int       $id         Object ID.
	 * @param string    $meta_key   Meta key.
	 * @param mixed     $meta_value Meta value. Must be serializable if non-scalar.
	 * @param mixed     $prev_value If specified, only update existing metadata entries with the specified value.
	 * @return null|bool Unchanged
	 */
	public function update_metadata( $r, $id, $meta_key, $meta_value, $prev_value ) {
		if ( null === $r ) {
			$hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) );
			$this->prev_value[ $hash ] = $prev_value;
		}
		return $r;
	}

	/**
	 * Synchronize updated metas across translations
	 *
	 * @since 2.3
	 *
	 * @param int    $mid        Meta id.
	 * @param int    $id         Object ID.
	 * @param string $meta_key   Meta key.
	 * @param mixed  $meta_value Meta value. Must be serializable if non-scalar.
	 */
	public function update_meta( $mid, $id, $meta_key, $meta_value ) {
		static $avoid_recursion = false;

		if ( ! $avoid_recursion ) {
			$avoid_recursion = true;
			$hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) );

			$prev_meta = get_metadata_by_mid( $this->meta_type, $mid );

			if ( $prev_meta ) {
				$this->remove_add_meta_action(); // We don't want to sync back the new metas
				$tr_ids = $this->model->{$this->meta_type}->get_translations( $id );

				foreach ( $tr_ids as $lang => $tr_id ) {
					if ( $tr_id != $id && in_array( $meta_key, $this->get_metas_to_copy( $id, $tr_id, $lang, true ) ) ) {
						if ( empty( $this->prev_value[ $hash ] ) || $this->prev_value[ $hash ] === $prev_meta->meta_value ) {
							$prev_value = $this->maybe_translate_value( $prev_meta->meta_value, $meta_key, $id, $tr_id, $lang );
							$meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang );
							update_metadata( $this->meta_type, $tr_id, $meta_key, is_string( $meta_value ) ? wp_slash( $meta_value ) : $meta_value, $prev_value );
						}
					}
				}
				$this->restore_add_meta_action();
			}

			unset( $this->prev_value[ $hash ] );
			$avoid_recursion = false;
		}
	}

	/**
	 * Store metas to synchronize before deleting them
	 *
	 * @since 2.3
	 *
	 * @param array $mids  Not used
	 * @param int   $id    Object ID.
	 */
	public function store_metas_to_sync( $mids, $id ) {
		$tr_ids = $this->model->{$this->meta_type}->get_translations( $id );

		foreach ( $tr_ids as $lang => $tr_id ) {
			$this->to_copy[ $id ][ $tr_id ] = $this->get_metas_to_copy( $id, $tr_id, $lang, true );
		}
	}

	/**
	 * Synchronize deleted meta across translations
	 *
	 * @since 2.3
	 *
	 * @param array  $mids  Not used
	 * @param int    $id    Object ID.
	 * @param string $key   Meta key.
	 * @param mixed  $value Meta value.
	 */
	public function delete_meta( $mids, $id, $key, $value ) {
		static $avoid_recursion = false;

		if ( ! $avoid_recursion ) {
			$avoid_recursion = true;

			$tr_ids = $this->model->{$this->meta_type}->get_translations( $id );

			foreach ( $tr_ids as $lang => $tr_id ) {
				if ( $tr_id != $id ) {
					if ( in_array( $key, $this->to_copy[ $id ][ $tr_id ] ) ) {
						if ( '' !== $value && null !== $value && false !== $value ) { // Same test as WP
							$value = $this->maybe_translate_value( $value, $key, $id, $tr_id, $lang );
						}
						delete_metadata( $this->meta_type, $tr_id, $key, is_string( $value ) ? wp_slash( $value ) : $value );
					}
				}
			}
		}

		$avoid_recursion = false;
	}

	/**
	 * Copy or synchronize metas
	 *
	 * @since 2.3
	 *
	 * @param int    $from Id of the source object
	 * @param int    $to   Id of the target object
	 * @param string $lang Language code of the target object
	 * @param bool   $sync Optional, defaults to true. True if it is synchronization, false if it is a copy
	 */
	public function copy( $from, $to, $lang, $sync = false ) {
		$this->remove_all_meta_actions();

		remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 );
		remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 );

		$to_copy = $this->get_metas_to_copy( $from, $to, $lang, $sync );
		$metas = get_metadata( $this->meta_type, $from );
		$tr_metas = get_metadata( $this->meta_type, $to );

		foreach ( $to_copy as $key ) {
			if ( empty( $metas[ $key ] ) ) {
				if ( ! empty( $tr_metas[ $key ] ) ) {
					// If the meta key is not present in the source object, delete all values
					delete_metadata( $this->meta_type, $to, $key );
				}
			} else {
				if ( ! empty( $tr_metas[ $key ] ) && 1 === count( $metas[ $key ] ) && 1 === count( $tr_metas[ $key ] ) ) {
					// One custom field to update
					$value = reset( $metas[ $key ] );
					$value = maybe_unserialize( $value );
					$to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
					update_metadata( $this->meta_type, $to, $key, is_string( $to_value ) ? wp_slash( $to_value ) : $to_value );
				} else {
					// Multiple custom fields, either in the source or the target
					if ( ! empty( $tr_metas[ $key ] ) ) {
						// The synchronization of multiple values custom fields is easier if we delete all metas first
						delete_metadata( $this->meta_type, $to, $key );
					}

					foreach ( $metas[ $key ] as $value ) {
						$value = maybe_unserialize( $value );
						$to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang );
						add_metadata( $this->meta_type, $to, $key, is_string( $to_value ) ? wp_slash( $to_value ) : $to_value );
					}
				}
			}
		}

		$this->add_all_meta_actions();
	}

	/**
	 * If synchronized custom fields were previously not synchronized, it is expected
	 * that saving a post (or term) will synchronize them.
	 *
	 * @since 2.3
	 *
	 * @param int    $object_id    Id of the object being asaved
	 * @param object $obj          Not used
	 * @param array  $translations The list of translations object ids
	 */
	public function save_object( $object_id, $obj, $translations ) {
		$src_lang = array_search( $object_id, $translations );

		foreach ( $translations as $tr_lang => $tr_id ) {
			if ( $tr_id != $object_id ) {
				$this->copy( $object_id, $tr_id, $tr_lang, true );
			}
		}
	}
}
sync-tax.php000066600000022341151264450030007031 0ustar00<?php

/**
 * A class to manage the sychronization of taxonomy terms across posts translations
 *
 * @since 2.3
 */
class PLL_Sync_Tax {

	/**
	 * Constructor
	 *
	 * @since 2.3
	 *
	 * @param object $polylang
	 */
	public function __construct( &$polylang ) {
		$this->model = &$polylang->model;
		$this->options = &$polylang->options;

		add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
		add_action( 'pll_save_term', array( $this, 'create_term' ), 10, 3 );
		add_action( 'pre_delete_term', array( $this, 'pre_delete_term' ) );
		add_action( 'delete_term', array( $this, 'delete_term' ) );
	}

	/**
	 * Get the list of taxonomies to copy or to synchronize
	 *
	 * @since 1.7
	 * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
	 *
	 * @param bool   $sync True if it is synchronization, false if it is a copy
	 * @param int    $from Id of the post from which we copy informations, optional, defaults to null
	 * @param int    $to   Id of the post to which we paste informations, optional, defaults to null
	 * @param string $lang Language slug, optional, defaults to null
	 * @return array List of taxonomy names
	 */
	protected function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) {
		$taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array();
		if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) {
			$taxonomies[] = 'post_format';
		}

		/**
		 * Filter the taxonomies to copy or synchronize
		 *
		 * @since 1.7
		 * @since 2.1 The `$from`, `$to`, `$lang` parameters were added.
		 *
		 * @param array  $taxonomies List of taxonomy names
		 * @param bool   $sync       True if it is synchronization, false if it is a copy
		 * @param int    $from       Id of the post from which we copy informations
		 * @param int    $to         Id of the post to which we paste informations
		 * @param string $lang       Language slug
		 */
		return array_unique( apply_filters( 'pll_copy_taxonomies', $taxonomies, $sync, $from, $to, $lang ) );
	}

	/**
	 * When copying or synchronizing terms, translate terms in translatable taxonomies
	 *
	 * @since 2.3
	 *
	 * @param array  $object_id Object ID
	 * @param array  $terms     List of terms ids assigned to the source post
	 * @param string $taxonomy  Taxonomy name
	 * @param string $lang      Language slug
	 * @return array List of terms ids to assign to the target post
	 */
	protected function maybe_translate_terms( $object_id, $terms, $taxonomy, $lang ) {
		if ( is_array( $terms ) && $this->model->is_translated_taxonomy( $taxonomy ) ) {
			$newterms = array();

			// Convert to term ids if we got tag names
			$strings = array_map( 'is_string', $terms );
			if ( in_array( true, $strings, true ) ) {
				$terms = get_the_terms( $object_id, $taxonomy );
				$terms = wp_list_pluck( $terms, 'term_id' );
			}

			foreach ( $terms as $term ) {
				/**
				 * Filter the translated term when a post translation is created or synchronized
				 *
				 * @since 2.3
				 *
				 * @param int    $tr_term Translated term id
				 * @param int    $term    Source term id
				 * @param string $lang    Language slug
				 */
				if ( $term_id = apply_filters( 'pll_maybe_translate_term', $this->model->term->get_translation( $term, $lang ), $term, $lang ) ) {
					$newterms[] = (int) $term_id; // Cast is important otherwise we get 'numeric' tags
				}
			}

			return $newterms;
		}

		return $terms; // Empty $terms or untranslated taxonomy
	}

	/**
	 * Maybe copy taxonomy terms from one post to the other
	 *
	 * @since 2.6
	 *
	 * @param int    $object_id Source object ID.
	 * @param int    $tr_id     Target object ID.
	 * @param string $lang      Target language.
	 * @param array  $terms     An array of object terms.
	 * @param string $taxonomy  Taxonomy slug.
	 * @param bool   $append    Whether to append new terms to the old terms.
	 */
	protected function copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ) {
		$to_copy = $this->get_taxonomies_to_copy( true, $object_id, $tr_id, $lang );

		if ( in_array( $taxonomy, $to_copy ) ) {
			$newterms = $this->maybe_translate_terms( $object_id, $terms, $taxonomy, $lang );

			// For some reasons, the user may have untranslated terms in the translation. Don't forget them.
			if ( $this->model->is_translated_taxonomy( $taxonomy ) ) {
				$tr_terms = get_the_terms( $tr_id, $taxonomy );
				if ( is_array( $tr_terms ) ) {
					foreach ( $tr_terms as $term ) {
						if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $object_id ) ) ) {
							$newterms[] = (int) $term->term_id;
						}
					}
				}
			}

			wp_set_object_terms( $tr_id, $newterms, $taxonomy, $append );
		}

	}

	/**
	 * When assigning terms to a post, assign translated terms to the translated posts (synchronisation)
	 *
	 * @since 2.3
	 *
	 * @param int    $object_id Object ID.
	 * @param array  $terms     An array of object terms.
	 * @param array  $tt_ids    An array of term taxonomy IDs.
	 * @param string $taxonomy  Taxonomy slug.
	 * @param bool   $append    Whether to append new terms to the old terms.
	 */
	public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy, $append ) {
		static $avoid_recursion = false;
		$taxonomy_object = get_taxonomy( $taxonomy );

		// Make sure that the taxonomy is registered for a post type
		if ( ! $avoid_recursion && array_filter( $taxonomy_object->object_type, 'post_type_exists' ) ) {
			$avoid_recursion = true;

			$tr_ids = $this->model->post->get_translations( $object_id );

			foreach ( $tr_ids as $lang => $tr_id ) {
				if ( $tr_id !== $object_id ) {
					if ( $this->model->post->current_user_can_synchronize( $object_id ) ) {
						$this->copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append );
					} else {
						// No permission to synchronize, so let's synchronize in reverse order
						$orig_lang = array_search( $object_id, $tr_ids );
						$tr_terms = (array) get_the_terms( $tr_id, $taxonomy );
						if ( is_array( $tr_terms ) ) {
							$tr_terms = wp_list_pluck( $tr_terms, 'term_id' );
							$this->copy_object_terms( $tr_id, $object_id, $orig_lang, $tr_terms, $taxonomy, $append );
						}
						break;
					}
				}
			}

			$avoid_recursion = false;
		}
	}

	/**
	 * Copy terms fron one post to a translation, does not sync
	 *
	 * @since 2.3
	 *
	 * @param int    $from  Id of the source post
	 * @param int    $to    Id of the target post
	 * @param string $lang  Language slug
	 */
	public function copy( $from, $to, $lang ) {
		remove_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 6 );

		// Get taxonomies to sync for this post type
		$taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( false, $from, $to, $lang ) );

		// Update the term cache to reduce the number of queries in the loop
		update_object_term_cache( $from, get_post_type( $from ) );

		// Copy
		foreach ( $taxonomies as $tax ) {
			if ( $terms = get_the_terms( $from, $tax ) ) {
				$terms = array_map( 'intval', wp_list_pluck( $terms, 'term_id' ) );
				$newterms = $this->maybe_translate_terms( $from, $terms, $tax, $lang );

				if ( ! empty( $newterms ) ) {
					wp_set_object_terms( $to, $newterms, $tax );
				}
			}
		}

		add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 6 );
	}

	/**
	 * When creating a new term, associate it to posts having translations associated to the translated terms
	 *
	 * @since 2.3
	 *
	 * @param int    $term_id      Id of the created term
	 * @param string $taxonomy     Taxonomy
	 * @param array  $translations Ids of the translations of the created term
	 */
	public function create_term( $term_id, $taxonomy, $translations ) {
		if ( doing_action( 'create_term' ) && in_array( $taxonomy, $this->get_taxonomies_to_copy( true ) ) ) {
			// Get all posts associated to the translated terms
			$tr_posts = get_posts(
				array(
					'numberposts' => -1,
					'nopaging'    => true,
					'post_type'   => 'any',
					'post_status' => 'any',
					'fields'      => 'ids',
					'tax_query'   => array(
						array(
							'taxonomy'         => $taxonomy,
							'field'            => 'id',
							'terms'            => array_merge( array( $term_id ), array_values( $translations ) ),
							'include_children' => false,
						),
					),
				)
			);

			$lang = $this->model->term->get_language( $term_id ); // Language of the created term
			$posts = array();

			foreach ( $tr_posts as $post_id ) {
				$post = $this->model->post->get_translation( $post_id, $lang );

				if ( $post ) {
					$posts[] = $post;
				}
			}

			$posts = array_unique( $posts );

			foreach ( $posts as $post_id ) {
				if ( current_user_can( 'assign_term', $term_id ) ) {
					wp_set_object_terms( $post_id, $term_id, $taxonomy, true );
				}
			}
		}
	}

	/**
	 * Deactivate the synchronization of terms before deleting a term
	 * to avoid translated terms to be removed from translated posts
	 *
	 * @since 2.3.2
	 */
	public function pre_delete_term() {
		remove_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
	}

	/**
	 * Re-activate the synchronization of terms after a term is deleted
	 *
	 * @since 2.3.2
	 */
	public function delete_term() {
		add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 );
	}
}
settings-sync.php000066600000005117151264450030010077 0ustar00<?php

/**
 * Settings class for synchronization settings management
 *
 * @since 1.8
 */
class PLL_Settings_Sync extends PLL_Settings_Module {

	/**
	 * Constructor
	 *
	 * @since 1.8
	 *
	 * @param object $polylang polylang object
	 */
	public function __construct( &$polylang ) {
		parent::__construct(
			$polylang,
			array(
				'module'      => 'sync',
				'title'       => __( 'Synchronization', 'polylang' ),
				'description' => __( 'The synchronization options allow to maintain exact same values (or translations in the case of taxonomies and page parent) of meta content between the translations of a post or page.', 'polylang' ),
			)
		);
	}

	/**
	 * Deactivates the module
	 *
	 * @since 1.8
	 */
	public function deactivate() {
		$this->options['sync'] = array();
		update_option( 'polylang', $this->options );
	}

	/**
	 * Displays the settings form
	 *
	 * @since 1.8
	 */
	protected function form() {
		?>
		<ul class="pll-inline-block-list">
			<?php
			foreach ( self::list_metas_to_sync() as $key => $str ) {
				printf(
					'<li><label><input name="sync[%s]" type="checkbox" value="1" %s /> %s</label></li>',
					esc_attr( $key ),
					checked( in_array( $key, $this->options['sync'] ), true, false ),
					esc_html( $str )
				);
			}
			?>
		</ul>
		<?php
	}

	/**
	 * Sanitizes the settings before saving
	 *
	 * @since 1.8
	 *
	 * @param array $options
	 */
	protected function update( $options ) {
		$newoptions['sync'] = empty( $options['sync'] ) ? array() : array_keys( $options['sync'], 1 );
		return $newoptions; // take care to return only validated options
	}

	/**
	 * Get the row actions
	 *
	 * @since 1.8
	 *
	 * @return array
	 */
	protected function get_actions() {
		return empty( $this->options['sync'] ) ? array( 'configure' ) : array( 'configure', 'deactivate' );
	}

	/**
	 * List the post metas to synchronize
	 *
	 * @since 1.0
	 *
	 * @return array
	 */
	public static function list_metas_to_sync() {
		return array(
			'taxonomies'        => __( 'Taxonomies', 'polylang' ),
			'post_meta'         => __( 'Custom fields', 'polylang' ),
			'comment_status'    => __( 'Comment status', 'polylang' ),
			'ping_status'       => __( 'Ping status', 'polylang' ),
			'sticky_posts'      => __( 'Sticky posts', 'polylang' ),
			'post_date'         => __( 'Published date', 'polylang' ),
			'post_format'       => __( 'Post format', 'polylang' ),
			'post_parent'       => __( 'Page parent', 'polylang' ),
			'_wp_page_template' => __( 'Page template', 'polylang' ),
			'menu_order'        => __( 'Page order', 'polylang' ),
			'_thumbnail_id'     => __( 'Featured image', 'polylang' ),
		);
	}
}
sync.php000066600000015661151264450030006246 0ustar00<?php

/**
 * Manages copy and synchronization of terms and post metas on front
 *
 * @since 2.4
 */
class PLL_Sync {
	public $taxonomies, $post_metas, $term_meta;

	/**
	 * Constructor
	 *
	 * @since 1.2
	 *
	 * @param object $polylang
	 */
	public function __construct( &$polylang ) {
		$this->model   = &$polylang->model;
		$this->options = &$polylang->options;

		$this->taxonomies = new PLL_Sync_Tax( $polylang );
		$this->post_metas = new PLL_Sync_Post_Metas( $polylang );
		$this->term_metas = new PLL_Sync_Term_Metas( $polylang );

		add_filter( 'wp_insert_post_parent', array( $this, 'can_sync_post_parent' ), 10, 3 );
		add_filter( 'wp_insert_post_data', array( $this, 'can_sync_post_data' ), 10, 2 );

		add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 );
		add_action( 'created_term', array( $this, 'sync_term_parent' ), 10, 3 );
		add_action( 'edited_term', array( $this, 'sync_term_parent' ), 10, 3 );

		add_action( 'pll_duplicate_term', array( $this->term_metas, 'copy' ), 10, 3 );

		if ( $this->options['media_support'] ) {
			add_action( 'pll_translate_media', array( $this->taxonomies, 'copy' ), 10, 3 );
			add_action( 'pll_translate_media', array( $this->post_metas, 'copy' ), 10, 3 );
			add_action( 'edit_attachment', array( $this, 'edit_attachment' ) );
		}

		add_filter( 'pre_update_option_sticky_posts', array( $this, 'sync_sticky_posts' ), 10, 2 );
	}

	/**
	 * Get post fields to synchornize
	 *
	 * @since 2.4
	 *
	 * @param object $post Post object
	 * @return array
	 */
	protected function get_fields_to_sync( $post ) {
		$postarr = array();

		foreach ( array( 'comment_status', 'ping_status', 'menu_order' ) as $property ) {
			if ( in_array( $property, $this->options['sync'] ) ) {
				$postarr[ $property ] = $post->$property;
			}
		}

		if ( in_array( 'post_date', $this->options['sync'] ) ) {
			$postarr['post_date']     = $post->post_date;
			$postarr['post_date_gmt'] = $post->post_date_gmt;
		}

		if ( in_array( 'post_parent', $this->options['sync'] ) ) {
			$postarr['post_parent'] = wp_get_post_parent_id( $post->ID );
		}

		return $postarr;
	}

	/**
	 * Prevents synchronized post parent modification if the current user hasn't enough rights
	 *
	 * @since 2.6
	 *
	 * @param int   $post_parent Post parent ID
	 * @param int   $post_id     Post ID, unused
	 * @param array $postarr     Array of parsed post data
	 * @return int
	 */
	public function can_sync_post_parent( $post_parent, $post_id, $postarr ) {
		if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) {
			$tr_ids = $this->model->post->get_translations( $postarr['ID'] );
			$orig_lang = array_search( $postarr['ID'], $tr_ids );
			foreach ( $tr_ids as $tr_id ) {
				if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) {
					$post_parent = $post->post_parent;
					break;
				}
			}
		}
		return $post_parent;
	}

	/**
	 * Prevents synchronized post data modification if the current user hasn't enough rights
	 *
	 * @since 2.6
	 *
	 * @param array $data    An array of slashed post data.
	 * @param array $postarr An array of sanitized, but otherwise unmodified post data.
	 * @return array
	 */
	public function can_sync_post_data( $data, $postarr ) {
		if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) {
			foreach ( $this->model->post->get_translations( $postarr['ID'] ) as $tr_id ) {
				if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) {
					$to_sync = $this->get_fields_to_sync( $post );
					$data = array_merge( $data, $to_sync );
					break;
				}
			}
		}
		return $data;
	}

	/**
	 * Synchronizes post fields in translations
	 *
	 * @since 2.4
	 *
	 * @param int    $post_id      post id
	 * @param object $post         post object
	 * @param array  $translations post translations
	 */
	public function pll_save_post( $post_id, $post, $translations ) {
		global $wpdb;

		if ( $this->model->post->current_user_can_synchronize( $post_id ) ) {
			$postarr = $this->get_fields_to_sync( $post );

			if ( ! empty( $postarr ) ) {
				foreach ( $translations as $lang => $tr_id ) {
					if ( ! $tr_id || $tr_id === $post_id ) {
						continue;
					}

					$tr_arr = $postarr;
					unset( $tr_arr['post_parent'] );

					// Do not udpate the translation parent if the user set a parent with no translation
					if ( isset( $postarr['post_parent'] ) ) {
						$post_parent = $postarr['post_parent'] ? $this->model->post->get_translation( $postarr['post_parent'], $lang ) : 0;
						if ( ! ( $postarr['post_parent'] && ! $post_parent ) ) {
							$tr_arr['post_parent'] = $post_parent;
						}
					}

					// Update all the row at once
					// Don't use wp_update_post to avoid infinite loop
					$wpdb->update( $wpdb->posts, $tr_arr, array( 'ID' => $tr_id ) );
					clean_post_cache( $tr_id );
				}
			}
		}
	}

	/**
	 * Synchronize term parent in translations
	 * Calling clean_term_cache *after* this is mandatory otherwise the $taxonomy_children option is not correctly updated
	 *
	 * @since 2.3
	 *
	 * @param int    $term_id  Term id.
	 * @param int    $tt_id    Term taxonomy id, not used.
	 * @param string $taxonomy Taxonomy name.
	 */
	public function sync_term_parent( $term_id, $tt_id, $taxonomy ) {
		global $wpdb;

		if ( is_taxonomy_hierarchical( $taxonomy ) && $this->model->is_translated_taxonomy( $taxonomy ) ) {
			$term = get_term( $term_id );
			$translations = $this->model->term->get_translations( $term_id );

			foreach ( $translations as $lang => $tr_id ) {
				if ( ! empty( $tr_id ) && $tr_id !== $term_id ) {
					$tr_parent = $this->model->term->get_translation( $term->parent, $lang );
					$wpdb->update(
						$wpdb->term_taxonomy,
						array( 'parent' => isset( $tr_parent ) ? $tr_parent : 0 ),
						array( 'term_taxonomy_id' => get_term( (int) $tr_id, $taxonomy )->term_taxonomy_id )
					);

					clean_term_cache( $tr_id, $taxonomy ); // OK since WP 3.9
				}
			}
		}
	}

	/**
	 * Synchronizes terms and metas in translations for media
	 *
	 * @since 1.8
	 *
	 * @param int $post_id post id
	 */
	public function edit_attachment( $post_id ) {
		$this->pll_save_post( $post_id, get_post( $post_id ), $this->model->post->get_translations( $post_id ) );
	}

	/**
	 * Synchronize sticky posts
	 *
	 * @since 2.3
	 *
	 * @param array $value     New option value
	 * @param array $old_value Old option value
	 * @return array
	 */
	public function sync_sticky_posts( $value, $old_value ) {
		if ( in_array( 'sticky_posts', $this->options['sync'] ) ) {
			// Stick post
			if ( $sticked = array_diff( $value, $old_value ) ) {
				$translations = $this->model->post->get_translations( reset( $sticked ) );
				$value        = array_unique( array_merge( $value, array_values( $translations ) ) );
			}

			// Unstick post
			if ( $unsticked = array_diff( $old_value, $value ) ) {
				$translations = $this->model->post->get_translations( reset( $unsticked ) );
				$value        = array_unique( array_diff( $value, array_values( $translations ) ) );
			}
		}

		return $value;
	}
}
sync-term-metas.php000066600000000521151264450030010307 0ustar00<?php

/**
 * A class to manage copy and synchronization of term metas
 *
 * @since 2.3
 */
class PLL_Sync_Term_Metas extends PLL_Sync_Metas {

	/**
	 * Constructor
	 *
	 * @since 2.3
	 *
	 * @param object $polylang
	 */
	public function __construct( &$polylang ) {
		$this->meta_type = 'term';

		parent::__construct( $polylang );
	}
}