class-wpml-slug-translation.php 12.4 KB
<?php

class WPML_Slug_Translation implements IWPML_Action {

	const STRING_DOMAIN = 'WordPress';

	/** @var array $post_link_cache */
	private $post_link_cache = array();

	/** @var  SitePress $sitepress */
	private $sitepress;

	/** @var WPML_Slug_Translation_Records_Factory $slug_records_factory */
	private $slug_records_factory;

	/** @var WPML_ST_Term_Link_Filter $term_link_filter */
	private $term_link_filter;

	/** @var WPML_Get_LS_Languages_Status $ls_languages_status */
	private $ls_languages_status;

	/** @var WPML_ST_Slug_Translation_Settings $slug_translation_settings */
	private $slug_translation_settings;

	private $ignore_post_type_link = false;

	public function __construct(
		SitePress $sitepress,
		WPML_Slug_Translation_Records_Factory $slug_records_factory,
		WPML_Get_LS_Languages_Status $ls_language_status,
		WPML_ST_Term_Link_Filter $term_link_filter,
		WPML_ST_Slug_Translation_Settings $slug_translation_settings
	) {
		$this->sitepress                 = $sitepress;
		$this->slug_records_factory      = $slug_records_factory;
		$this->ls_languages_status       = $ls_language_status;
		$this->term_link_filter          = $term_link_filter;
		$this->slug_translation_settings = $slug_translation_settings;
	}

	public function add_hooks() {
		add_action( 'init', array( $this, 'init' ), WPML_Slug_Translation_Factory::INIT_PRIORITY );
	}

	public function init() {
		$this->migrate_global_enabled_setting();

		if ( $this->slug_translation_settings->is_enabled() ) {
			add_filter( 'post_type_link', array( $this, 'post_type_link_filter' ), apply_filters( 'wpml_post_type_link_priority', 1 ), 4 );
			add_filter( 'pre_term_link', array( $this->term_link_filter, 'replace_slug_in_termlink' ), 1, 2 ); // high priority
			add_filter( 'edit_post', array( $this, 'clear_post_link_cache' ), 1, 2 );
			add_filter( 'query_vars', array( $this, 'add_cpt_names' ), 1, 1 );
			add_filter( 'pre_get_posts', array( $this, 'filter_pre_get_posts' ), - 1000, 1 );
		}

		if ( is_admin() ) {
			add_action( 'icl_ajx_custom_call', array( $this, 'gui_save_options' ), 10, 1 );
			add_action( 'wp_loaded', array( $this, 'maybe_migrate_string_name' ), 10, 0 );
		}
	}

	/**
	 * @deprecated since 2.8.0, use the class `WPML_Post_Slug_Translation_Records` instead.
	 *
	 * @param string $type
	 *
	 * @return null|string
	 */
	public static function get_slug_by_type( $type ) {
		$slug_records_factory = new WPML_Slug_Translation_Records_Factory();
		$slug_records         = $slug_records_factory->create( WPML_Slug_Translation_Factory::POST );

		return $slug_records->get_original( $type );
	}

	/**
	 * This method is only for CPT
	 *
	 * @deprecated use `WPML_ST_Slug::filter_value` directly of the filter hook `wpml_get_translated_slug`
	 *
	 * @param string|false $slug_value
	 * @param string       $post_type
	 * @param string|bool  $language
	 *
	 * @return string
	 */
	public function get_translated_slug( $slug_value, $post_type, $language = false ) {
		if ( $post_type ) {
			$language = $language ? $language : $this->sitepress->get_current_language();
			$slug     = $this->slug_records_factory->create( WPML_Slug_Translation_Factory::POST )
												   ->get_slug( $post_type );

			return $slug->filter_value( $slug_value, $language );
		}

		return $slug_value;
	}

	/**
	 * @param array $value
	 *
	 * @return array
	 * @deprecated Use WPML\ST\SlugTranslation\Hooks\Hooks::filter
	 */
	public static function rewrite_rules_filter( $value ) {
		return ( new \WPML\ST\SlugTranslation\Hooks\HooksFactory() )->create()->filter( $value );
	}


	/**
	 * @param string  $post_link
	 * @param WP_Post $post
	 * @param bool    $leavename
	 * @param bool    $sample
	 *
	 * @return mixed|string|WP_Error
	 */
	public function post_type_link_filter( $post_link, $post, $leavename, $sample ) {

		if ( $this->ignore_post_type_link ) {
			return $post_link;
		}

		if ( ! $this->sitepress->is_translated_post_type( $post->post_type )
			 || ! ( $ld = $this->sitepress->get_element_language_details( $post->ID, 'post_' . $post->post_type ) )
		) {
			return $post_link;
		}

		$ld = apply_filters( 'wpml_st_post_type_link_filter_language_details', $ld );

		$cache_key  = $leavename . '#' . $sample;
		$cache_key .= $this->ls_languages_status->is_getting_ls_languages() ? 'yes' : 'no';
		$cache_key .= $ld->language_code;
		$blog_id    = get_current_blog_id();
		if ( isset( $this->post_link_cache[ $blog_id ][ $post->ID ][ $cache_key ] ) ) {
			$post_link = $this->post_link_cache[ $blog_id ][ $post->ID ][ $cache_key ];
		} else {
			$slug_settings = $this->sitepress->get_setting( 'posts_slug_translation' );
			$slug_settings = ! empty( $slug_settings['types'][ $post->post_type ] ) ? $slug_settings['types'][ $post->post_type ] : null;
			if ( (bool) $slug_settings === true ) {

				$post_type_obj = get_post_type_object( $post->post_type );
				$slug_this     = isset( $post_type_obj->rewrite['slug'] ) ? trim( $post_type_obj->rewrite['slug'], '/' ) : false;
				$slug_real     = $this->get_translated_slug( $slug_this, $post->post_type, $ld->language_code );

				if ( empty( $slug_real ) || empty( $slug_this ) || $slug_this == $slug_real ) {
					return $post_link;
				}

				global $wp_rewrite;

				if ( isset( $wp_rewrite->extra_permastructs[ $post->post_type ] ) ) {
					$struct_original = $wp_rewrite->extra_permastructs[ $post->post_type ]['struct'];

					/**
					 * This hook allows to filter the slug we want to search and replace
					 * in the permalink structure. This is required for 3rd party
					 * plugins replacing the original slug with a placeholder.
					 *
					 * @since 3.1.0
					 *
					 * @param string  $slug_this The original slug.
					 * @param string  $post_link The initial link.
					 * @param WP_Post $post      The post.
					 * @param bool    $leavename Whether to keep the post name.
					 * @param bool    $sample    Is it a sample permalink.
					 */
					$slug_this = apply_filters( 'wpml_st_post_type_link_filter_original_slug', $slug_this, $post_link, $post, $leavename, $sample );

					$lslash = false !== strpos( $struct_original, '/' . $slug_this ) ? '/' : '';
					$wp_rewrite->extra_permastructs[ $post->post_type ]['struct'] = preg_replace(
						'@' . $lslash . $slug_this . '/@',
						$lslash . $slug_real . '/',
						$struct_original
					);
					$this->ignore_post_type_link                                  = true;
					$post_link                   = get_post_permalink( $post->ID, $leavename, $sample );
					$this->ignore_post_type_link = false;
					$wp_rewrite->extra_permastructs[ $post->post_type ]['struct'] = $struct_original;
				} else {
					$post_link = str_replace( $slug_this . '=', $slug_real . '=', $post_link );
				}
			}
			$this->post_link_cache[ $blog_id ][ $post->ID ][ $cache_key ] = $post_link;
		}

		return $post_link;
	}

	/**
	 * @param int      $post_ID
	 * @param \WP_Post $post
	 */
	public function clear_post_link_cache( $post_ID, $post ) {
		$blog_id = get_current_blog_id();
		unset( $this->post_link_cache[ $blog_id ][ $post_ID ] );
	}

	/**
	 * @return array
	 */
	private function get_all_post_slug_translations() {
		$slug_translations              = array();
		$post_slug_translation_settings = $this->sitepress->get_setting( 'posts_slug_translation' );

		if ( isset( $post_slug_translation_settings['types'] ) ) {
			$types     = $post_slug_translation_settings['types'];
			$cache_key = 'WPML_Slug_Translation::get_all_slug_translations' . md5( (string) json_encode( $types ) );

			$slug_translations = wp_cache_get( $cache_key );

			if ( ! is_array( $slug_translations ) ) {
				$slug_translations = array();
				$types_to_fetch    = array();

				foreach ( $types as $type => $state ) {
					if ( $state ) {
						$types_to_fetch[] = str_replace( '%', '%%', $type );
					}
				}

				if ( $types_to_fetch ) {
					$data = $this->slug_records_factory
						->create( WPML_Slug_Translation_Factory::POST )
						->get_all_slug_translations( $types_to_fetch );

					foreach ( $data as $row ) {
						foreach ( $types_to_fetch as $type ) {
							if ( preg_match( '#\s' . $type . '$#', $row->name ) === 1 ) {
								$slug_translations[ $row->value ] = $type;
							}
						}
					}
				}

				wp_cache_set( $cache_key, $slug_translations );
			}
		}

		return $slug_translations;
	}

	/**
	 * Adds all translated custom post type slugs as valid query variables in addition to their original values
	 *
	 * @param array $qvars
	 *
	 * @return array
	 */
	public function add_cpt_names( $qvars ) {
		$all_slugs_translations = array_keys( $this->get_all_post_slug_translations() );
		$qvars                  = array_merge( $qvars, $all_slugs_translations );

		return $qvars;
	}

	/**
	 * @param WP_Query $query
	 *
	 * @return WP_Query
	 */
	public function filter_pre_get_posts( $query ) {
		/** Do not alter the query if it has already resolved the post ID */
		if ( ! empty( $query->query_vars['p'] ) ) {
			return $query;
		}

		$all_slugs_translations = $this->get_all_post_slug_translations();

		foreach ( $query->query as $slug => $post_name ) {
			if ( isset( $all_slugs_translations[ $slug ] ) ) {
				$new_slug = isset( $all_slugs_translations[ $slug ] ) ? $all_slugs_translations[ $slug ] : $slug;
				unset( $query->query[ $slug ] );
				$query->query[ $new_slug ] = $post_name;
				$query->query['name']      = $post_name;
				$query->query['post_type'] = $new_slug;
				unset( $query->query_vars[ $slug ] );
				$query->query_vars[ $new_slug ] = $post_name;
				$query->query_vars['name']      = $post_name;
				$query->query_vars['post_type'] = $new_slug;

			}
		}

		return $query;
	}

	/**
	 * @param string $action
	 */
	public static function gui_save_options( $action ) {
		switch ( $action ) {
			case 'icl_slug_translation':
				global $sitepress;
				$is_enabled = intval( ! empty( $_POST['icl_slug_translation_on'] ) );
				$settings   = new WPML_ST_Post_Slug_Translation_Settings( $sitepress );
				$settings->set_enabled( (bool) $is_enabled );
				echo '1|' . $is_enabled;
				break;
		}
	}

	/**
	 * @param string $slug
	 *
	 * @return string
	 */
	public static function sanitize( $slug ) {
		// we need to preserve the %
		$slug = str_replace( '%', '%45', $slug );
		$slug = sanitize_title_with_dashes( $slug );
		$slug = str_replace( '%45', '%', $slug );

		/**
		 * Filters the sanitized post type or taxonomy slug translation
		 *
		 * @since 2.10.0
		 *
		 * @param string $slug
		 */
		return apply_filters( 'wpml_st_slug_translation_sanitize', $slug );
	}

	/**
	 * @deprecated since 2.8.0, use the class `WPML_Post_Slug_Translation_Records` instead.
	 */
	public static function register_string_for_slug( $post_type, $slug ) {
		return icl_register_string( self::STRING_DOMAIN, 'URL slug: ' . $post_type, $slug );
	}

	public function maybe_migrate_string_name() {
		global $wpdb;

		$slug_settings = $this->sitepress->get_setting( 'posts_slug_translation' );

		if ( ! isset( $slug_settings['string_name_migrated'] ) ) {

			/** @var string[] $queryable_post_types */
			$queryable_post_types = get_post_types( array( 'publicly_queryable' => true ) );

			foreach ( $queryable_post_types as $type ) {
				$post_type_obj = get_post_type_object( $type );
				if ( null === $post_type_obj || ! isset( $post_type_obj->rewrite['slug'] ) ) {
					continue;
				}

				$slug = trim( $post_type_obj->rewrite['slug'], '/' );
				if ( $slug ) {
					// First check if we should migrate from the old format URL slug: slug
					$string_id = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT id
											FROM {$wpdb->prefix}icl_strings
											WHERE name = %s AND value = %s",
							'URL slug: ' . $slug,
							$slug
						)
					);
					if ( $string_id ) {
						// migrate it to URL slug: post_type

						$st_update['name'] = 'URL slug: ' . $type;
						$wpdb->update( $wpdb->prefix . 'icl_strings', $st_update, array( 'id' => $string_id ) );
					}
				}
			}

			$slug_settings['string_name_migrated'] = true;
			$this->sitepress->set_setting( 'posts_slug_translation', $slug_settings, true );
		}
	}

	/**
	 * Move global on/off setting to its own option WPML_ST_Slug_Translation_Settings::KEY_ENABLED_GLOBALLY
	 */
	private function migrate_global_enabled_setting() {
		$enabled = get_option( WPML_ST_Slug_Translation_Settings::KEY_ENABLED_GLOBALLY );

		if ( false === $enabled ) {
			$old_setting = $this->sitepress->get_setting( WPML_ST_Post_Slug_Translation_Settings::KEY_IN_SITEPRESS_SETTINGS );

			if ( array_key_exists( 'on', $old_setting ) ) {
				$enabled = (int) $old_setting['on'];
			} else {
				$enabled = 0;
			}

			update_option( WPML_ST_Slug_Translation_Settings::KEY_ENABLED_GLOBALLY, $enabled );
		}
	}
}