335 lines
12 KiB
PHP
335 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* @package Polylang-Pro
|
|
*/
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* A class that re-assigns to templates a language that has just been re-created.
|
|
* When a language is deleted, the language suffix is not removed from the template slugs (see
|
|
* `PLL_FSE_Template_Slug_Sync->modify_template_slug_on_lang_assigning()`). When this language is re-created later
|
|
* (deleted by mistake?), it is re-assigned to the templates (as long as it uses the same slug than before its
|
|
* deletion).
|
|
*
|
|
* @since 3.2
|
|
*/
|
|
class PLL_FSE_Recreate_Language extends PLL_FSE_Abstract_Module implements PLL_Module_Interface {
|
|
|
|
/**
|
|
* Instance of `PLL_Admin_Model`.
|
|
*
|
|
* @var PLL_Admin_Model
|
|
*/
|
|
protected $model;
|
|
|
|
/**
|
|
* Returns the module's name.
|
|
*
|
|
* @since 3.2
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function get_name() {
|
|
return 'fse_recreate_language';
|
|
}
|
|
|
|
/**
|
|
* Sub-module init.
|
|
*
|
|
* @since 3.2
|
|
*
|
|
* @return self
|
|
*/
|
|
public function init() {
|
|
add_action( 'pll_add_language', array( $this, 'reassign_language' ) );
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Re-assigns a language to templates upon its re-creation.
|
|
*
|
|
* @since 3.2
|
|
* @global wpdb $wpdb
|
|
*
|
|
* @param array $args {
|
|
* Arguments used to create the language.
|
|
*
|
|
* @type string $name Language name (used only for display).
|
|
* @type string $slug Language code (ideally 2-letters ISO 639-1 language code).
|
|
* @type string $locale WordPress locale.
|
|
* @type int $rtl 1 if rtl language, 0 otherwise.
|
|
* @type int $term_group Language order when displayed.
|
|
* @type string $no_default_cat Optional, if set, no default category will be created for this language.
|
|
* @type string $flag Optional, country code, @see flags.php.
|
|
* }
|
|
* @return void
|
|
*
|
|
* @phpstan-param array{name:string,slug:string,locale:string,rtl:int<0,1>,term_group:positive-int,no_default_cat?:string,flag?:string} $args
|
|
*/
|
|
public function reassign_language( $args ) {
|
|
global $wpdb;
|
|
|
|
if ( empty( $this->options['default_lang'] ) || $args['slug'] === $this->options['default_lang'] ) {
|
|
// Default language.
|
|
return;
|
|
}
|
|
|
|
$def_lang = $this->model->get_default_language();
|
|
$new_lang = $this->model->get_language( $args['slug'] );
|
|
|
|
if ( empty( $def_lang ) || empty( $new_lang ) ) {
|
|
// Uh?
|
|
return;
|
|
}
|
|
|
|
// Post IDs of posts in new language, post names of posts in default language.
|
|
$post_names_by_post_ids_by_theme_id = $this->get_posts_with_lang_suffix( $new_lang );
|
|
|
|
if ( empty( $post_names_by_post_ids_by_theme_id ) ) {
|
|
// No templates found.
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* We must work "by theme" because template slugs can be identical from 1 theme to another.
|
|
* When updating translation groups, `PLL_Admin_Model->set_translation_in_mass()` cannot be used because it adds
|
|
* new translations instead of updating the existing ones.
|
|
*/
|
|
$set_language_post_ids = array(); // Used to assign the language to newly bound templates.
|
|
$clean_cache_post_ids = array(); // Used to clean the posts cache.
|
|
$clean_cache_term_ids = array(); // Used to clean the terms cache.
|
|
$all_relationships = array(); // Used to assign the translation groups to newly bound templates.
|
|
$create_groups_post_ids = array(); // Used to create new translation groups when they don't exist yet.
|
|
|
|
foreach ( $post_names_by_post_ids_by_theme_id as $theme_id => $post_names_by_post_ids ) {
|
|
// Get posts by their post_name and theme ID.
|
|
$results = $this->get_translation_groups( $post_names_by_post_ids, $theme_id );
|
|
|
|
if ( empty( $results ) ) {
|
|
// Should not happen.
|
|
continue;
|
|
}
|
|
|
|
// Update translation groups and/or create new ones.
|
|
$post_ids_by_post_names = array_flip( $post_names_by_post_ids ); // Prevents multiple uses of `array_search()`.
|
|
$tt_ids = array(); // Used to update the existing translation groups.
|
|
$query = array( // Used to build the query that will update the existing translation groups.
|
|
"UPDATE {$wpdb->term_taxonomy} SET description = (",
|
|
'CASE term_taxonomy_id',
|
|
);
|
|
|
|
foreach ( $results as $result ) {
|
|
$new_lang_post_id = $post_ids_by_post_names[ $result['post_name'] ];
|
|
$set_language_post_ids[] = $new_lang_post_id;
|
|
|
|
if ( empty( $result['tt_id'] ) ) {
|
|
// The translation group doesn't exist yet.
|
|
$clean_cache_post_ids[] = $new_lang_post_id;
|
|
$clean_cache_post_ids[] = $result['post_id'];
|
|
$create_groups_post_ids[] = array(
|
|
$def_lang->slug => $result['post_id'],
|
|
$new_lang->slug => $new_lang_post_id,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// The translation group exist: update it.
|
|
$result['translations'][ $new_lang->slug ] = $new_lang_post_id;
|
|
|
|
$clean_cache_post_ids = array_merge( $clean_cache_post_ids, array_values( $result['translations'] ) );
|
|
|
|
$tt_ids[] = $result['tt_id'];
|
|
$all_relationships[] = $wpdb->prepare( '(%d,%d)', $new_lang_post_id, $result['tt_id'] );
|
|
$query[] = $wpdb->prepare( 'WHEN %d THEN %s', $result['tt_id'], maybe_serialize( $result['translations'] ) );
|
|
}
|
|
|
|
if ( ! empty( $tt_ids ) ) {
|
|
$query[] = 'END';
|
|
$query[] = ')';
|
|
$query[] = 'WHERE term_taxonomy_id IN (' . PLL_Db_Tools::prepare_values_list( $tt_ids ) . ')';
|
|
|
|
$wpdb->query( implode( "\n", $query ) ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
|
|
|
|
$clean_cache_term_ids = array_merge( $clean_cache_term_ids, $tt_ids );
|
|
}
|
|
}
|
|
|
|
if ( ! empty( $set_language_post_ids ) ) {
|
|
// Assign the language.
|
|
$this->model->post->set_language_in_mass( $set_language_post_ids, $new_lang );
|
|
}
|
|
|
|
if ( ! empty( $all_relationships ) ) {
|
|
// Assign the translation groups.
|
|
$wpdb->query( "INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id) VALUES " . implode( ',', $all_relationships ) );
|
|
}
|
|
|
|
if ( ! empty( $create_groups_post_ids ) ) {
|
|
// Create new translation groups.
|
|
$this->model->post->set_translation_in_mass( $create_groups_post_ids );
|
|
}
|
|
|
|
if ( ! empty( $clean_cache_post_ids ) ) {
|
|
// Clean posts cache.
|
|
clean_object_term_cache( $clean_cache_post_ids, PLL_FSE_Tools::get_template_post_types() );
|
|
}
|
|
|
|
if ( ! empty( $clean_cache_term_ids ) ) {
|
|
// Clean terms cache.
|
|
clean_term_cache( $clean_cache_term_ids, '', false );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a list of templates that have a post_name suffixed with the given lang slug.
|
|
*
|
|
* @since 3.2
|
|
*
|
|
* @param PLL_Language $language A language object.
|
|
* @return string[][] A set of post IDs and post names, grouped by theme ID (term_taxonomy_id of the
|
|
* 'wp_theme' taxonomy). Each result uses the post ID as array key and the post name
|
|
* (without lang suffix) as array value.
|
|
* Example of returned value:
|
|
* array(
|
|
* {theme_id} => array(
|
|
* {post_id} => '{post_name}',
|
|
* {post_id} => '{post_name}',
|
|
* ),
|
|
* {theme_id} => array(
|
|
* {post_id} => '{post_name}',
|
|
* ),
|
|
* )
|
|
*/
|
|
private function get_posts_with_lang_suffix( PLL_Language $language ) {
|
|
global $wpdb;
|
|
|
|
$post_types = PLL_Db_Tools::prepare_values_list( PLL_FSE_Tools::get_template_post_types() );
|
|
|
|
// 'dummy' => 'dummy___fr' => 'dummy\_\_\_fr' => '%\_\_\_fr' - No it's not commented code damnit.
|
|
$post_slug = ( new PLL_FSE_Template_Slug( 'dummy' ) )->update_language( $language->slug );
|
|
$post_slug = str_replace( 'dummy', '%', $wpdb->esc_like( $post_slug ) );
|
|
|
|
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
|
|
$results = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"
|
|
SELECT p.ID as post_id, p.post_name, tt.term_taxonomy_id as theme_id
|
|
FROM {$wpdb->posts} AS p
|
|
INNER JOIN {$wpdb->term_relationships} AS tr
|
|
ON tr.object_id = p.ID
|
|
INNER JOIN {$wpdb->term_taxonomy} AS tt
|
|
ON tt.term_taxonomy_id = tr.term_taxonomy_id
|
|
AND tt.taxonomy = 'wp_theme'
|
|
WHERE p.post_type IN ($post_types)
|
|
AND post_name LIKE %s",
|
|
$post_slug
|
|
),
|
|
ARRAY_A
|
|
);
|
|
// phpcs:enable
|
|
|
|
if ( empty( $results ) ) {
|
|
return array();
|
|
}
|
|
|
|
$language_slugs = $this->get_languages_slugs();
|
|
$post_names_by_post_ids_by_theme_id = array();
|
|
|
|
foreach ( $results as $result ) {
|
|
$theme_id = $this->model->post->sanitize_int_id( $result['theme_id'] );
|
|
$post_id = $this->model->post->sanitize_int_id( $result['post_id'] );
|
|
$post_name = ( new PLL_FSE_Template_Slug( $result['post_name'], $language_slugs ) )->remove_language(); // We need the post_name without lang suffix to find the template in default language.
|
|
|
|
$post_names_by_post_ids_by_theme_id[ $theme_id ][ $post_id ] = $post_name;
|
|
}
|
|
|
|
return $post_names_by_post_ids_by_theme_id;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of translation groups belonging to templates that use the given post_names within the given theme
|
|
* ID. This will also return posts that don't have a translation group.
|
|
*
|
|
* @since 3.2
|
|
*
|
|
* @param string[] $post_names List of post names.
|
|
* @param int $theme_id `term_taxonomy_id` of the 'wp_theme' taxonomy.
|
|
* @return array[] {
|
|
* Array of arrays containing the translation groups and their `term_taxonomy_id`.
|
|
*
|
|
* @type int $post_id Post ID (post in default language).
|
|
* @type string $post_name Post slug.
|
|
* @type int $tt_id `term_taxonomy_id` of the 'post_translations' taxonomy.`0` means the term
|
|
* doesn't exist.
|
|
* @type string[] $translations List of translations. IDs are not sanitized to keep it simple (the array
|
|
* can contain other things).
|
|
* }
|
|
*
|
|
* @phpstan-return array<array{post_id:int<0,max>,post_name:string,tt_id:int<0,max>,translations:array<string>}>
|
|
*/
|
|
private function get_translation_groups( array $post_names, $theme_id ) {
|
|
global $wpdb;
|
|
|
|
$post_types = PLL_Db_Tools::prepare_values_list( PLL_FSE_Tools::get_template_post_types() );
|
|
$post_names = PLL_Db_Tools::prepare_values_list( $post_names );
|
|
|
|
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
|
|
$results = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"
|
|
SELECT p.ID as post_id, p.post_name, tt.term_taxonomy_id as tt_id, tt.description as translations
|
|
FROM {$wpdb->posts} AS p
|
|
|
|
LEFT JOIN (
|
|
SELECT tr1.object_id, tt1.term_taxonomy_id, tt1.description
|
|
FROM {$wpdb->term_relationships} AS tr1
|
|
INNER JOIN {$wpdb->term_taxonomy} AS tt1
|
|
ON tt1.term_taxonomy_id = tr1.term_taxonomy_id
|
|
AND tt1.taxonomy = 'post_translations'
|
|
) AS tt
|
|
ON tt.object_id = p.ID
|
|
|
|
INNER JOIN {$wpdb->term_relationships} AS tr
|
|
ON tr.object_id = p.ID
|
|
AND tr.term_taxonomy_id = %d
|
|
|
|
WHERE p.post_type IN ($post_types)
|
|
AND post_name IN ($post_names)",
|
|
$theme_id
|
|
),
|
|
ARRAY_A
|
|
);
|
|
// phpcs:enable
|
|
|
|
return array_map(
|
|
function ( $result ) {
|
|
$result['post_id'] = $this->model->post->sanitize_int_id( $result['post_id'] );
|
|
$result['tt_id'] = $this->model->term->sanitize_int_id( $result['tt_id'] );
|
|
$result['translations'] = $this->format_translations( $result['translations'] );
|
|
return $result;
|
|
},
|
|
(array) $results
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Formats a raw translation group from the database into an array.
|
|
*
|
|
* @since 3.2
|
|
*
|
|
* @param mixed $translations Raw translation group from the database.
|
|
* @return string[] Translation IDs are not sanitized to keep it simple
|
|
* (the array can contain other things).
|
|
*/
|
|
private function format_translations( $translations ) {
|
|
if ( empty( $translations ) ) {
|
|
return array();
|
|
}
|
|
|
|
$translations = maybe_unserialize( $translations );
|
|
|
|
return is_array( $translations ) ? $translations : array();
|
|
}
|
|
}
|