Skip Navigation

This thread is resolved. Here is a description of the problem and solution.

Problem:
If you're experiencing performance issues when copying Advanced Custom Fields (ACF) to translations with WPML, such as slow operations or timeouts, this might be due to inefficiencies in the code handling the copy process.

Solution:
We recommend modifying the

SitePress::copy_custom_fields

and

WPML_Copy_Once_Custom_Field::copy

methods to improve performance. Here are the steps to follow:

1. Locate the

SitePress::copy_custom_fields

method in

wp-content/plugins/sitepress-multilingual-cms/sitepress.class.php

and replace it with the provided code.

2. Locate the

WPML_Copy_Once_Custom_Field::copy

method in

wp-content/plugins/sitepress-multilingual-cms/classes/custom-field-translation/class-wpml-copy-once-custom-field.php

and replace it with the provided code.

These changes should optimize the process by reducing the number of instances where the entire post meta is fetched and looped through, thus improving the performance when copying ACF fields to translations.

If this solution doesn't look relevant to your issue, please open a new support ticket with us.

This is the technical support forum for WPML - the multilingual WordPress plugin.

Everyone can read, but only WPML clients can post here. WPML team is replying on the forum 6 days per week, 22 hours per day.

Tagged: 

This topic contains 4 replies, has 2 voices.

Last updated by Bruno Kos 8 months ago.

Assisted by: Bruno Kos.

Author Posts
February 1, 2024 at 5:27 pm #15256206

Joey Kudish

Hi there,

I work for Pantheon (hosting company) and I have been investigating some performance issues related to WPML and custom fields. Specifically, we have a customer site who uses WPML and has hundreds of ACF fields on their site. Whenever, they add a translation to an existing post, and save it, copying all of the ACF fields to the translation is very slow and often times out.

I have narrowed down the issue to inefficiencies in WPML's code when performing this operation.

The process starts in

WPML_Copy_Once_Custom_Field->copy()

method which gets all of the post meta from the original post, and loops through it to copy each field one by one by calling

SitePress->sync_custom_field()

which then instantiates a new instance of

WPML_Sync_Custom_Fields

for each field and calls that class'

sync_custom_field()

method.

Even though `sync_custom_field()` is provided the `$meta_key` to copy, this function doesn't actually copy a single field as you'd expect. Instead, it retrieves all of the post meta from both the original and copied posts, performs a diff of the meta keys, deletes any that aren't present in the original post and then loops through all of the meta keys to copy them to the new copied post.

This entire loop of instantiating `WPML_Sync_Custom_Fields` and fetching + looping through every single post meta is then repeated for every meta field in the original post. In essence, each meta field creates its own loop of every meta field.

As you can imagine, when doing so for hundreds of fields this creates a significant performance bottleneck.

It should be entirely possible to refactor this code in a way that it would only perform this loop a single time. Would it be possible for the WPML team to review and improve this?

Thank you!

February 2, 2024 at 7:57 am #15258069

Bruno Kos
Supporter

Languages: English (English ) German (Deutsch ) French (Français )

Timezone: Europe/Zagreb (GMT+02:00)

Hi,

Thank you for contacting WPML support!

We are familiar with the issue that more ACF fields can cause slowness because of the synchronization, especially if more languages coupled with more ACF fields, I brought this up to developers to see if this gives us some new ideas.

Regards,
Bruno Kos

February 2, 2024 at 8:05 am #15258092

Bruno Kos
Supporter

Languages: English (English ) German (Deutsch ) French (Français )

Timezone: Europe/Zagreb (GMT+02:00)

Can you try something on your end and do the testing again, do see some performance gains?

in \SitePress::copy_custom_fields in \wp-content\plugins\sitepress-multilingual-cms\sitepress.class.php
change the method to:

	function copy_custom_fields( $post_id_from, $post_id_to ) {
		$custom_fields_to_copy = $this->get_custom_fields_translation_settings( WPML_COPY_CUSTOM_FIELD );
		$custom_fields_from    = get_post_meta( $post_id_from );
		$custom_fields_to      = get_post_meta( $post_id_to );

		foreach ( $custom_fields_to_copy as $meta_key ) {
			if ( array_key_exists( $meta_key, $custom_fields_from ) ) {
				$meta_from = isset( $custom_fields_from[ $meta_key ] ) ? $custom_fields_from[ $meta_key ] : [];
				$meta_to   = isset( $custom_fields_to[ $meta_key ] ) ? $custom_fields_to[ $meta_key ] : [];

				if ( $meta_from || $meta_to ) {
					$this->sync_custom_field( $post_id_from, $post_id_to, $meta_key );
				}
			}
		}

		$sync_deleted_fields = false;
		/**
		 * This filter hook determines whether we should sync deleted custom
		 * fields to translations.
		 *
		 * @since 4.4.9
		 *
		 * @param bool $sync_deleted_fields True if we should sync deleted custom fields (the default is false).
		 *
		 * @return bool
		 */
		if ( apply_filters( 'wpml_sync_deleted_custom_fields', $sync_deleted_fields ) && $custom_fields_from && $custom_fields_to ) {
			$if_deleted_in_source_and_still_in_target = Logic::allPass(
				[
					Obj::prop( Fns::__, $custom_fields_to ),
					pipe( Obj::prop( Fns::__, $custom_fields_from ), Logic::not() ),
				]
			);

			wpml_collect( $this->get_custom_fields_translation_settings( WPML_TRANSLATE_CUSTOM_FIELD ) )
				->filter( $if_deleted_in_source_and_still_in_target )
				->map(
					function ( $meta_key ) use ( $post_id_from, $post_id_to ) {
						$this->sync_custom_field( $post_id_from, $post_id_to, $meta_key );
					}
				);
		}

	}

Also in \WPML_Copy_Once_Custom_Field::copy in \wp-content\plugins\sitepress-multilingual-cms\classes\custom-field-translation\class-wpml-copy-once-custom-field.php
change the method to:

	public function copy( $post_id ) {
		$custom_fields_to_copy = $this->sitepress->get_custom_fields_translation_settings( WPML_COPY_ONCE_CUSTOM_FIELD );
		if ( empty( $custom_fields_to_copy ) ) {
			return;
		}

		$source_element_id = $this->wpml_post_translation->get_original_element( $post_id );
		$custom_fields     = get_post_meta( $post_id );

		foreach ( $custom_fields_to_copy as $meta_key ) {
			if ( array_key_exists( $meta_key, $custom_fields ) ) {
				$values = isset( $custom_fields[ $meta_key ] )
				          && ! empty( $custom_fields[ $meta_key ] )
					? [ $custom_fields[ $meta_key ] ]
					: [];

				/**
				 * Custom fields values for given post obtained directly from database
				 *
				 * @param array<mixed> $values Custom fields values as they are in the database
				 * @param array<int|string> $args {
				 *
				 * @type int $post_id ID of post associated with custom field
				 * @type string $meta_key custom fields meta key
				 * @type int $custom_fields_translation field translation option
				 *
				 * }
				 * @since 4.1
				 *
				 */
				$values = apply_filters(
					'wpml_custom_field_values',
					$values,
					[
						'post_id'                   => $post_id,
						'meta_key'                  => $meta_key,
						'custom_fields_translation' => WPML_COPY_ONCE_CUSTOM_FIELD,
					]
				);

				if ( empty( $values ) && $source_element_id ) {
					$this->sitepress->sync_custom_field( $source_element_id, $post_id, $meta_key );
				}
			}
		}
	}
February 2, 2024 at 11:57 pm #15261472

Joey Kudish

Hey Bruno,

Thanks so much for the quick turnaround and potential code fix here. After some initial testing, this does seem to significantly improve the performance of saving multilingual posts. I will work with our customer to do some additional testing and validating of the solution.

Can we expect to see this solution patched in an upcoming update of WPML?

Thank you!

February 5, 2024 at 6:23 am #15264240

Bruno Kos
Supporter

Languages: English (English ) German (Deutsch ) French (Français )

Timezone: Europe/Zagreb (GMT+02:00)

Glad to hear it works - I cannot tell when will it end up in the final version though as it requires more through testing on our side.

Joey Kudish confirmed that the issue was resolved on 2024-02-02 23:57:29.
This ticket is now closed. If you're a WPML client and need related help, please open a new support ticket.