From 3ea9be15e76a8ad7e950fac1a66503a974b350ef Mon Sep 17 00:00:00 2001 From: Don Morrison Date: Fri, 19 Mar 2021 20:01:30 -0700 Subject: [PATCH 1/5] Add TermTranslation type Adds the `TermTranslation` type during type registration to provide term translation fields since these fields are slightly different than those for posts. --- wp-graphql-wpml.php | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/wp-graphql-wpml.php b/wp-graphql-wpml.php index 2a6ff7b..56f30e4 100644 --- a/wp-graphql-wpml.php +++ b/wp-graphql-wpml.php @@ -299,6 +299,51 @@ function wpgraphqlwpml_action_graphql_register_types() ], ]); + register_graphql_object_type('TermTranslation', [ + 'description' => __('Term Translation (WPML)', 'wp-graphql-wpml'), + 'fields' => [ + 'id' => [ + 'type' => [ + 'non_null' => 'ID', + ], + 'description' => __( + 'the id of the referenced translation (WPML)', + 'wp-graphql-wpml' + ), + ], + 'databaseId' => [ + 'type' => [ + 'non_null' => 'Int', + ], + 'description' => __( + 'the primary key from the database for the referenced translation (WPML)', + 'wp-graphql-wpml' + ), + ], + 'href' => [ + 'type' => 'String', + 'description' => __( + 'the relative link to the translated content (WPML)', + 'wp-graphql-wpml' + ), + ], + 'locale' => [ + 'type' => 'String', + 'description' => __( + 'Language code (WPML)', + 'wp-graphql-wpml' + ), + ], + 'name' => [ + 'type' => 'String', + 'description' => __( + 'the name of the translated taxonomy (WPML)', + 'wp-graphql-wpml' + ), + ], + ], + ]); + register_graphql_fields('RootQueryToMenuItemConnectionWhereArgs', [ 'language' => [ 'type' => 'String', From ec4c509a6a0def716410199247cf61095d160e68 Mon Sep 17 00:00:00 2001 From: Don Morrison Date: Fri, 19 Mar 2021 20:04:55 -0700 Subject: [PATCH 2/5] Add fields to Taxonomy types Adds fields to Taxonomies types, visible on the terms, in the same way these fields are added to posts. Adds the following fields: * `locale` - the locale of the current term * `translations` - list of `TermTranslation` objects * `translated` - list of the translated `Term` objects --- wp-graphql-wpml.php | 134 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/wp-graphql-wpml.php b/wp-graphql-wpml.php index 56f30e4..c30bcf2 100644 --- a/wp-graphql-wpml.php +++ b/wp-graphql-wpml.php @@ -1,5 +1,6 @@ graphql_single_name, + 'locale', + [ + 'type' => 'Locale', + 'description' => __('WPML translation link', 'wp-graphql-wpml'), + 'resolve' => function ( + \WPGraphQL\Model\Term $term, + $args, + $context, + $info + ) use ($taxonomy) { + global $sitepress; + + $fields = $info->getFieldSelection(); + + $language = [ + 'id' => null, + 'locale' => null, + ]; + + $wpml_element_type = 'tax_' . $taxonomy->name; + $language_code = $sitepress->get_language_for_element( $term->term_id, $wpml_element_type); + $locale = $sitepress->get_locale( $language_code ); + + if (!$locale) { + return null; + } + + $language['id'] = $locale; + + if (isset($fields['locale'])) { + $language['locale'] = $locale; + } + + return $language; + }, + ] + ); + + // Collection of available translations for this taxonomy + register_graphql_field( + $taxonomy->graphql_single_name, + 'translations', + [ + 'type' => ['list_of' => 'TermTranslation'], + 'description' => __('WPML translations', 'wpnext'), + 'resolve' => function ( + \WPGraphQL\Model\Term $term, + $args, + $context, + $info + ) use ($taxonomy) { + global $sitepress; + + $translations = []; + $languages = $sitepress->get_active_languages(); + $original_language_code = $sitepress->get_current_language(); + $original_term_id = $term->term_id; + + foreach ($languages as $language) { + $language_code = array_key_exists('language_code', $languages) ? $language['language_code'] : $language['code']; + $term_id = wpml_object_id_filter($original_term_id, $taxonomy->name, false, $language_code); + + if ($term_id === null || $term_id == $original_term_id) continue; + + // Relies on adjust ids feature being 'off' + $translated_term = get_term($term_id, $taxonomy->name); + $translated_url = get_term_link($translated_term); + + $translations[] = array( + 'databaseId' => $translated_term->term_id, + 'href' => $translated_url, + 'id' => Relay::toGlobalId( 'term', (string) $translated_term->term_id ), + 'locale' => $language['default_locale'], + 'name' => $translated_term->name, + ); + } + + return $translations; + }, + ] + ); + + // Collection of translated versions of the taxonomy + register_graphql_field( + $taxonomy->graphql_single_name, + 'translated', + [ + 'type' => ['list_of' => 'TermNode'], + 'description' => __('WPML translated versions of the term', 'wpnext'), + 'resolve' => function ( + \WPGraphQL\Model\Term $term, + $args, + $context, + $info + ) use ($taxonomy) { + global $sitepress; + + $fields = $info->getFieldSelection(); + + $translations = []; + $languages = $sitepress->get_active_languages(); + $original_language_code = $sitepress->get_current_language(); + $original_term_id = $term->term_id; + + foreach ($languages as $language) { + $language_code = array_key_exists('language_code', $languages) ? $language['language_code'] : $language['code']; + $term_id = wpml_object_id_filter($original_term_id, $taxonomy->name, false, $language_code); + + if ($term_id === null || $term_id == $original_term_id) continue; + + $translation = new \WPGraphQL\Model\Term( + \WP_Term::get_instance($term_id, $taxonomy->name) + ); + + array_push($translations, $translation); + } + + return $translations; + }, + ] + ); +} + /** * @param int $post_id * @param $language @@ -419,9 +548,14 @@ function wpgraphqlwpml_action_graphql_register_types() } ], ]); + foreach (\WPGraphQL::get_allowed_post_types() as $post_type) { wpgraphqlwpml_add_post_type_fields(get_post_type_object($post_type)); } + + foreach (\WPGraphQL::get_allowed_taxonomies() as $taxonomy_type) { + wpgraphqlwpml_add_taxonomy_type_fields(get_taxonomy($taxonomy_type)); + } } function wpgraphqlwpml__theme_mod_nav_menu_locations(array $args) From 91d28cb8ceb9ac2edaa9393aeaba4966229c4ea3 Mon Sep 17 00:00:00 2001 From: Don Morrison Date: Fri, 19 Mar 2021 20:11:21 -0700 Subject: [PATCH 3/5] Add hook to remove `get_term_adjust_id` filter Adds a hook to remove the WPML `get_term_adjust_id` filter that is attached to the WP `get_term` function at a very high priority. This filter causes WPML to adjust term ids for every request - shifting request terms to the "current language". The problem is that the WPGraphQL requests don't typically use the current language (and especially not for use cases like Gatsby). This results in term IDs only ever being returned for the default language and with single term queries always returning the default language's term. The function is added to the `init_graphql_request` action that runs at the beginning of the GraphQL request cycle. Using this action ensures that we're making adjustments for only "graphql" requests and that those changes are early enough in the cycle to get in front of the WPML filters. --- wp-graphql-wpml.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/wp-graphql-wpml.php b/wp-graphql-wpml.php index c30bcf2..c0ad1d1 100644 --- a/wp-graphql-wpml.php +++ b/wp-graphql-wpml.php @@ -761,6 +761,18 @@ function wpgraphqlwpml__switch_language_to_all_for_query(array $args) return $args; } +/** + * Remove the `get_term` filter added by WPML during WPGraphQL requests. + * This filter forces WPML to adjust term ids *before* other queries are + * run. There is a WPML setting named `auto_adjust_ids` that will turn + * off this feature, but it should never be on for GraphQL queries. + */ +function wpgraphqlwpml__remove_term_adjust_id_filter() { + global $sitepress; + + remove_filter('get_term', array($sitepress, 'get_term_adjust_id'), 1); +} + function wpgraphqlwpml_action_init() { if (!wpgraphqlwpml_is_graphql_request()) { @@ -818,9 +830,16 @@ function wpgraphqlwpml_action_init() 10, 2 ); -} + // Remove the adjust id filter during WPGraphQL requests + add_action( + 'init_graphql_request', + 'wpgraphqlwpml__remove_term_adjust_id_filter', + 10, + 0 + ); -add_action('graphql_init', 'wpgraphqlwpml_action_init'); +} +add_action('graphql_init', 'wpgraphqlwpml_action_init'); From 8ffc442b2ac8002dc940cb127c8c2d79b417458e Mon Sep 17 00:00:00 2001 From: Don Morrison Date: Fri, 19 Mar 2021 20:17:55 -0700 Subject: [PATCH 4/5] Add hook to set adjust id filter global Adds a hook to set the `$icl_adjust_id_url_filter_off` global to `true`. This global is used in the `WPML_Term_Adjust_Id->filter()` to indicate if term ids should be adjusted. Setting this global protects against another hook or filter turning this feature back on. The function is added to the `init_graphql_request` action that runs at the beginning of the GraphQL request cycle. Using this action ensures that we're making adjustments for only "graphql" requests and that those changes are early enough in the cycle to get in front of the WPML filters. Setting this to `true` is another guard against WPML id adjustment that results in only ever seeing the terms for the default language. --- wp-graphql-wpml.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/wp-graphql-wpml.php b/wp-graphql-wpml.php index c0ad1d1..841a97c 100644 --- a/wp-graphql-wpml.php +++ b/wp-graphql-wpml.php @@ -773,6 +773,20 @@ function wpgraphqlwpml__remove_term_adjust_id_filter() { remove_filter('get_term', array($sitepress, 'get_term_adjust_id'), 1); } +/** + * Set the `icl_adjust_id_url_filter_off` global used in the + * `WPML_Term_Adjust_Id->filter()` function to indicate if term ids + * should be adjusted. Setting this global to `true` ensures that + * term ids are not adjusted during GraphQL queries. + */ +function wpgraphqlwpml__set_global_adjust_id_filter_off() { + // Set global for adjust ids in case the filter is added + // again at some point in the request + global $icl_adjust_id_url_filter_off; + + $icl_adjust_id_url_filter_off = true; +} + function wpgraphqlwpml_action_init() { if (!wpgraphqlwpml_is_graphql_request()) { @@ -839,6 +853,13 @@ function wpgraphqlwpml_action_init() 0 ); + // Set the global adjust id filter to allow for multi-language term queries + add_action( + 'init_graphql_request', + 'wpgraphqlwpml__set_global_adjust_id_filter_off', + 10, + 0 + ); } From 0c678909f149128c6805a29542dc7ab941eb1482 Mon Sep 17 00:00:00 2001 From: Don Morrison Date: Tue, 27 Apr 2021 18:57:03 -0700 Subject: [PATCH 5/5] Add slug field to term translations Adds the `slug` field to the term translations response (type and resolver) to help when a term may have the same slug between different locales. When the same slug is used, WordPress will add a numerical suffix to the href/link even though the same slug may be desired. Adding the slug to the response allows the client to use the slug when generating routes. For example, imagine the situation with two locales: English and Spanish. Creating a `tech` tag in both locales would look like this: * English - slug: `tech` - href: `https://.../tag/tech` * Spanish - slug: `tech` - href: `https://.../es/tag/tech-2` With the slug and the href, the client can use whatever works best in that context. --- wp-graphql-wpml.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wp-graphql-wpml.php b/wp-graphql-wpml.php index 841a97c..51e4a91 100644 --- a/wp-graphql-wpml.php +++ b/wp-graphql-wpml.php @@ -258,6 +258,7 @@ function wpgraphqlwpml_add_taxonomy_type_fields(\WP_Taxonomy $taxonomy) 'id' => Relay::toGlobalId( 'term', (string) $translated_term->term_id ), 'locale' => $language['default_locale'], 'name' => $translated_term->name, + 'slug' => $translated_term->slug, ); } @@ -470,6 +471,13 @@ function wpgraphqlwpml_action_graphql_register_types() 'wp-graphql-wpml' ), ], + 'slug' => [ + 'type' => 'String', + 'description' => __( + 'the slug of the translated taxonomy (WPML)', + 'wp-graphql-wpml' + ), + ], ], ]);