fields[ $field['name'] ] = $field; return $value; } /** * Store fields when saving a term or when duplicating a term. * * @since 2.3 * * @param int $term_id Id of the term being saved. * @return void */ public function store_term_fields( $term_id ) { $this->fields = get_field_objects( 'term_' . $term_id ); } /** * Copies and possibly translates custom fields when creating a new term translation. * * @since 2.2 * * @param mixed $value Custom field value of the source term. * @param string $post_id Expects term_{$term_id} for a term. * @param array $field Custom field. * @return mixed */ public function load_value( $value, $post_id, $field ) { if ( 'term_0' === $post_id && isset( $_GET['taxonomy'], $_GET['from_tag'], $_GET['new_lang'] ) && taxonomy_exists( sanitize_key( $_GET['taxonomy'] ) ) && $lang = PLL()->model->get_language( sanitize_key( $_GET['new_lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification $from_tag = (int) $_GET['from_tag']; // phpcs:ignore WordPress.Security.NonceVerification $tr_id = 'term_' . $from_tag; // Converts to ACF internal id. $fields = get_field_objects( $tr_id ); if ( ! empty( $fields ) ) { $keys = array_keys( $fields ); /** This filter is documented in /polylang/modules/sync/admin-sync.php */ $keys = array_unique( apply_filters( 'pll_copy_term_metas', $keys, false, $from_tag, 0, $lang->slug ) ); // Second test to load the values of subfields of accepted fields. if ( in_array( $field['name'], $keys ) || preg_match( '#^(' . implode( '|', $keys ) . ')_(.+)#', $field['name'] ) ) { $value = acf_get_value( $tr_id, $field ); // Since ACF 5.0.0. $empty = null; // Parameter 1 is useless in this context. $value = $this->translate_fields( $empty, $value, $field['name'], $field, $lang->slug ); if ( pll_is_translated_post_type( 'acf-field-group' ) ) { $references = $this->translate_fields_references( $tr_id, $lang->slug ); $this->translate_references_in_value( $value, $references ); } } } } return $value; } /** * Translates a custom field before it is copied or synchronized. * * @since 2.3 * @since 2.4 Added parameter $to. * * @param mixed $value Meta value. * @param string $key Meta key. * @param string $lang Language of target. * @param int $from Id of the object from which we copy informations. * @param int $to Id of the object to which we copy informations. * @return mixed */ public function translate_meta( $value, $key, $lang, $from, $to = 0 ) { if ( ! empty( $value ) && $field = isset( $this->fields[ $key ] ) ? $this->fields[ $key ] : $this->get_field_object( $key, $from ) ) { $create_if_not_exists = false; // Check if we should create translations if they don't exist. // $to is not empty only when translating posts. if ( ! empty( $to ) && ( $post_type = get_post_type( $to ) ) && pll_is_translated_post_type( $post_type ) ) { $duplicate_options = get_user_meta( get_current_user_id(), 'pll_duplicate_content', true ); $active = ! empty( $duplicate_options ) && ! empty( $duplicate_options[ $post_type ] ); $create_if_not_exists = $active || PLL()->sync_post_model->are_synchronized( $from, $to ); } $value = $this->translate_field( $value, $lang, $field, $create_if_not_exists ); } if ( pll_is_translated_post_type( 'acf-field-group' ) && is_string( $value ) && acf_is_field_key( $value ) ) { $references = $this->translate_fields_references( $from, $lang ); if ( isset( $references[ $value ] ) ) { $value = $references[ $value ]; } } return $value; } /** * Returns an array containing all the field data for a given field name. * Unlike the original ACF function, it works for clone fields. * * @since 2.6.2 * * @param string $key The field name or key. * @param string|int $post_id The post_id of which the value is saved against. * @return array|false */ protected function get_field_object( $key, $post_id ) { $field = get_field_object( $key, $post_id ); if ( $field ) { return $field; } $post_id = acf_get_valid_post_id( $post_id ); $field_key = acf_get_reference( $key, $post_id ); // Since ACF 5.6.5. if ( ! is_string( $field_key ) ) { return false; } $field_key = substr( $field_key, -19 ); // Keep the last key in field_xxx_field_yyy for clone fields. if ( ! acf_is_field_key( $field_key ) ) { return false; } $field = acf_get_field( $field_key ); if ( empty( $field ) ) { return false; } $field['value'] = acf_get_value( $post_id, $field ); $field['value'] = acf_format_value( $field['value'], $post_id, $field ); return $field; } /** * Translates a CPT archive link in a page link field. * * @since 2.3.6 * * @param string $link CPT archive link. * @param string $lang Language slug. * @return string Modified link. */ protected function translate_cpt_archive_link( $link, $lang ) { $lang = PLL()->model->get_language( $lang ); $link = PLL()->links_model->switch_language_in_link( $link, $lang ); foreach ( array_keys( PLL()->translate_slugs->slugs_model->get_translatable_slugs() ) as $type ) { // Unfortunately ACF does not pass the post type, so let's try with all post type archives. if ( 0 === strpos( $type, 'archive_' ) ) { $link = PLL()->translate_slugs->slugs_model->switch_translated_slug( $link, $lang, $type ); } } return $link; } /** * Translates a custom field value. * * @since 2.3 * @since 2.4 Added parameter $create_if_not_exists. * * @param mixed $value Custom field value. * @param string $lang Language slug. * @param array $field Custom field. * @param bool $create_if_not_exists Should we create the translation if it does not exist. * @return mixed */ protected function translate_field( $value, $lang, $field, $create_if_not_exists = false ) { $return = $value; switch ( $field['type'] ) { case 'image': case 'file': if ( PLL()->options['media_support'] ) { $return = 0; if ( $tr_id = pll_get_post( $value, $lang ) ) { $return = $tr_id; } elseif ( $create_if_not_exists ) { $return = PLL()->posts->create_media_translation( $value, $lang ); } } break; case 'gallery': if ( PLL()->options['media_support'] && is_array( $value ) ) { $return = array(); foreach ( $value as $img ) { if ( $tr_id = pll_get_post( $img, $lang ) ) { $return[] = $tr_id; } elseif ( $create_if_not_exists ) { $return[] = PLL()->posts->create_media_translation( $img, $lang ); } } $return = array_map( 'strval', $return ); // See acf_field_gallery::update_value(). } break; case 'post_object': case 'relationship': if ( is_numeric( $value ) ) { $return = 0; if ( $tr_id = pll_get_post( $value, $lang ) ) { $return = $tr_id; } } elseif ( is_array( $value ) ) { $return = array(); foreach ( $value as $p ) { if ( $tr_id = pll_get_post( $p, $lang ) ) { $return[] = $tr_id; } } $return = array_map( 'strval', $return ); // See the method update_value() for these fields. } break; case 'page_link': if ( is_numeric( $value ) ) { // Unique translated post. $return = 0; if ( $tr_id = pll_get_post( $value, $lang ) ) { $return = $tr_id; } } elseif ( is_array( $value ) ) { // Multiple choice. $return = array(); foreach ( $value as $p ) { if ( is_numeric( $p ) && $tr_id = pll_get_post( $p, $lang ) ) { $return[] = $tr_id; } else { $return[] = $this->translate_cpt_archive_link( $p, $lang ); // Archive. } } $return = array_map( 'strval', $return ); // See acf_field_page_link::update_value(). } else { $return = $this->translate_cpt_archive_link( $value, $lang ); // Archive. } break; case 'taxonomy': if ( pll_is_translated_taxonomy( $field['taxonomy'] ) ) { if ( is_numeric( $value ) ) { $return = 0; if ( $tr_id = pll_get_term( $value, $lang ) ) { $return = $tr_id; } } elseif ( is_array( $value ) ) { $return = array(); foreach ( $value as $t ) { if ( $tr_id = pll_get_term( $t, $lang ) ) { $return[] = $tr_id; } } } } break; } return $return; } /** * Translates repeater and flexible content sub fields (for recursive translation of this fields). * * @since 2.2 * * @param array $r Reference to a flat list of translated custom fields. * @param mixed $value Custom field value. * @param string $name Custom field name. * @param array $field ACF field or subfield. * @param string $lang Language slug. * @return array Hierarchical list of custom fields values. */ protected function translate_sub_fields( &$r, $value, $name, $field, $lang ) { $return = array(); foreach ( $value as $row => $sub_fields ) { $sub = array(); foreach ( $sub_fields as $id => $sub_value ) { $field = acf_get_field( substr( $id, -19 ) ); // Keep the last key in field_xxx_field_yyy for clone fields. if ( $field ) { $sub[ $id ] = $this->translate_fields( $r, $sub_value, $name . '_' . $row . '_' . $field['name'], $field, $lang ); } else { $sub[ $id ] = $sub_value; } } $return[] = $sub; } return $return; } /** * Recursively translates custom group, repeater and flexible content fields. * * @since 2.0 * * @param array $r Reference to a flat list of translated custom fields. * @param mixed $value Custom field value. * @param string $name Custom field name. * @param array $field ACF field or subfield. * @param string $lang Language slug. * @return array Hierarchical list of custom fields values. */ protected function translate_fields( &$r, $value, $name, $field, $lang ) { if ( empty( $value ) ) { return; } $r[ '_' . $name ] = $field['key']; $return = array(); switch ( $field['type'] ) { case 'group': $sub = array(); foreach ( $value as $id => $sub_value ) { if ( $field = acf_get_field( $id ) ) { $sub[ $id ] = $this->translate_fields( $r, $sub_value, $name . '_' . $field['name'], $field, $lang ); } else { $sub[ $id ] = $sub_value; } } $return[] = $sub; break; case 'repeater': case 'flexible_content': $return = $this->translate_sub_fields( $r, $value, $name, $field, $lang ); break; default: $return = $this->translate_field( $value, $lang, $field ); break; } return empty( $return ) ? $value : $return; } /** * Translated field groups: * Recursively translates the references in value for repeaters and flexible content. * * @since 2.2 * * @param array $value Reference to a custom field value. * @param array $references List of custom fields references with source as key and translation as value. * @return void */ protected function translate_references_in_value( &$value, $references ) { if ( is_array( $value ) ) { foreach ( $value as $row => $sub_fields ) { if ( is_array( $sub_fields ) ) { foreach ( $sub_fields as $id => $sub_value ) { if ( is_array( $sub_value ) ) { $this->translate_references_in_value( $sub_value, $references ); } if ( isset( $references[ $id ] ) ) { $value[ $row ][ $references[ $id ] ] = $sub_value; unset( $value[ $row ][ $id ] ); } } } } } } /** * Translated field groups: * Searches for fields having the same name in translated posts. * * @since 2.2 * * @param int|string $from Source post id. * @param string $lang Target language code. * @return array */ protected function translate_fields_references( $from, $lang ) { $keys = array(); $fields = get_field_objects( $from ); if ( is_array( $fields ) ) { foreach ( $fields as $field ) { if ( $tr_group = pll_get_post( $field['parent'], $lang ) ) { $tr_fields = acf_get_fields( $tr_group ); $this->translate_field_references( $keys, $field, $tr_fields ); } } } return $keys; } /** * Translated field groups: * Loops through sub fields in the recursive search for fields * having the same name among translated fields groups. * * @since 2.2 * * @param array $keys Reference to an array mapping the fields keys of the translated post to the field keys of the currentpost. * @param array $fields ACF Custom fields of the current post. * @param array $tr_fields ACF Custom fields of a translation. * @return void */ protected function translate_sub_fields_references( &$keys, $fields, $tr_fields ) { foreach ( $fields as $field ) { $this->translate_field_references( $keys, $field, $tr_fields ); } } /** * Translated field groups: * Recursively searches for fields having the same name among translated fields groups. * * @since 2.2 * * @param array $keys Reference to an array mapping the fields keys of the translated post to the field keys of the currentpost. * @param array $field ACF Custom fields of the current post. * @param array $tr_fields ACF Custom fields of a translation. * @return void */ protected function translate_field_references( &$keys, $field, $tr_fields ) { $k = array_search( $field['name'], wp_list_pluck( $tr_fields, 'name' ) ); if ( false !== $k ) { $keys[ $field['key'] ] = $tr_fields[ $k ]['key']; if ( ! empty( $field['sub_fields'] ) ) { $this->translate_sub_fields_references( $keys, $field['sub_fields'], $tr_fields[ $k ]['sub_fields'] ); } if ( ! empty( $field['layouts'] ) ) { foreach ( $field['layouts'] as $row => $layout ) { if ( ! empty( $layout['sub_fields'] ) ) { $this->translate_sub_fields_references( $keys, $layout['sub_fields'], $tr_fields[ $k ]['layouts'][ $row ]['sub_fields'] ); } } } } } }