ate_api = $ate_api; $this->ate_jobs = $ate_jobs; $this->sitepress = $sitepress; $this->current_screen = $current_screen; $this->translator_activation_records = $translator_activation_records; $this->job_sync_script_loader = $job_sync_script_loader; } public function add_hooks() { add_action( 'wpml_added_translation_job', array( $this, 'added_translation_job' ), 10, 2 ); add_action( 'wpml_added_translation_jobs', array( $this, 'added_translation_jobs' ) ); add_action( 'admin_notices', array( $this, 'handle_messages' ) ); add_action( 'current_screen', array( $this, 'update_jobs_on_current_screen' ) ); add_action( 'wp', array( $this, 'update_jobs_on_current_screen' ) ); add_filter( 'wpml_tm_ate_jobs_data', array( $this, 'get_ate_jobs_data_filter' ), 10, 2 ); add_filter( 'wpml_tm_ate_jobs_editor_url', array( $this, 'get_editor_url' ), 10, 3 ); } public function handle_messages() { if ( $this->current_screen->id_ends_with( WPML_TM_FOLDER . '/menu/translations-queue' ) ) { if ( array_key_exists( 'message', $_GET ) ) { if ( array_key_exists( 'ate_job_id', $_GET ) ) { $ate_job_id = filter_var( $_GET['ate_job_id'], FILTER_SANITIZE_NUMBER_INT ); $this->resign_job_on_error( $ate_job_id ); } $message = filter_var( $_GET['message'], FILTER_SANITIZE_STRING ); ?>

added_translation_jobs( array( $translation_service => array( $job_id ) ) ); } /** * @param array $jobs * * @return bool|void * @throws \InvalidArgumentException * @throws \RuntimeException */ public function added_translation_jobs( array $jobs ) { $oldEditor = wpml_tm_load_old_jobs_editor(); $job_ids = Fns::reject( [ $oldEditor, 'shouldStickToWPMLEditor' ], Obj::propOr( [], 'local', $jobs ) ); if ( ! $job_ids ) { return; } $jobs = []; $rid_to_job_map = []; foreach ( $job_ids as $job_id ) { $rid = wpml_tm_get_records()->icl_translate_job_by_job_id( $job_id )->rid(); $rid_to_job_map[ $rid ] = $job_id; $jobs[] = wpml_tm_create_ATE_job_creation_model( $job_id, $rid ); } $response = $this->create_jobs( $jobs ); try { $this->check_response_error( $response ); } catch ( RuntimeException $ex ) { do_action( 'wpml_tm_basket_add_message', 'error', $ex->getMessage() ); return; } $has_valid_response = $response && isset( $response->jobs ); $response_jobs = null; if ( $has_valid_response ) { $response_jobs = $response->jobs; } if ( $response_jobs ) { if ( is_object( $response_jobs ) ) { $response_jobs = json_decode( wp_json_encode( $response_jobs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ), true ); } $response_jobs = $this->map_response_jobs( $response_jobs, $rid_to_job_map ); $this->ate_jobs->warm_cache( array_keys( $response_jobs ) ); foreach ( $response_jobs as $wpml_job_id => $ate_job_id ) { $this->ate_jobs->store( $wpml_job_id, array( JobRecords::FIELD_ATE_JOB_ID => $ate_job_id ) ); $oldEditor->set( $wpml_job_id, WPML_TM_Editors::ATE ); } $message = __( '%1$s jobs added to the Advanced Translation Editor.', 'wpml-translation-management' ); $this->add_message( 'updated', sprintf( $message, count( $response_jobs ) ), 'wpml_tm_ate_create_job' ); } else { $this->add_message( 'error', __( 'Jobs could not be created in Advanced Translation Editor. Please try again or contact the WPML support for help.', 'wpml-translation-management' ), 'wpml_tm_ate_create_job' ); } } private function map_response_jobs( $responseJobs, $rid_to_job_id_map ) { $result = []; foreach ( $responseJobs as $rid => $ate_job_id ) { if ( isset( $rid_to_job_id_map[ $rid ] ) ) { $result[ $rid_to_job_id_map[ $rid ] ] = $ate_job_id; } } return $result; } /** * @param string $type * @param string $message * @param string|null $id */ private function add_message( $type, $message, $id = null ) { do_action( 'wpml_tm_basket_add_message', $type, $message, $id ); } /** * @param WPML_TM_ATE_Models_Job_Create[] $jobs * * @return mixed * @throws \InvalidArgumentException */ private function create_jobs( array $jobs ) { $params = json_decode( wp_json_encode( array( 'jobs' => $jobs ) ), true ); return $this->ate_api->create_jobs( $params ); } /** * After implementation of wpmltm-3211 and wpmltm-3391, we should not find missing ATE IDs anymore. * Some code below seems dead but we'll keep it for now in case we are missing a specific context. * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-3211 * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-3391 */ private function get_ate_jobs_data( array $translation_jobs ) { $ate_jobs_data = array(); $skip_getting_data = false; $ate_jobs_to_create = array(); $this->ate_jobs->warm_cache( wpml_collect( $translation_jobs )->pluck( 'job_id' )->toArray() ); foreach ( $translation_jobs as $translation_job ) { if ( $this->is_ate_translation_job( $translation_job ) ) { $ate_job_id = $this->get_ate_job_id( $translation_job->job_id ); // Start of possibly dead code. if ( ! $ate_job_id ) { $ate_jobs_to_create[] = $translation_job->job_id; $skip_getting_data = true; } // End of possibly dead code. if ( ! $skip_getting_data ) { $ate_jobs_data[ $translation_job->job_id ] = [ 'ate_job_id' => $ate_job_id ]; } } } // Start of possibly dead code. if ( ! $this->is_second_attempt_to_get_jobs_data && $ate_jobs_to_create && $this->added_translation_jobs( array( 'local' => $ate_jobs_to_create ) ) ) { $ate_jobs_data = $this->get_ate_jobs_data( $translation_jobs ); $this->is_second_attempt_to_get_jobs_data = true; } // End of possibly dead code. return $ate_jobs_data; } /** * @param string $default_url * @param int $job_id * @param null|string $return_url * * @return string * @throws \InvalidArgumentException */ public function get_editor_url( $default_url, $job_id, $return_url = null ) { if ( $this->translator_activation_records->is_current_user_activated() ) { $ate_job_id = $this->ate_jobs->get_ate_job_id( $job_id ); if ( $ate_job_id ) { if ( ! $return_url ) { $return_url = add_query_arg( array( 'page' => WPML_TM_FOLDER . '/menu/translations-queue.php', 'ate-return-job' => $job_id, ), admin_url( '/admin.php' ) ); } $ate_job_url = $this->ate_api->get_editor_url( $ate_job_id, $return_url ); if ( $ate_job_url && ! is_wp_error( $ate_job_url ) ) { return $ate_job_url; } } } return $default_url; } /** * @param $ignore * @param array $translation_jobs * * @return array */ public function get_ate_jobs_data_filter( $ignore, array $translation_jobs ) { return $this->get_ate_jobs_data( $translation_jobs ); } private function get_ate_job_id( $job_id ) { return $this->ate_jobs->get_ate_job_id( $job_id ); } public function update_jobs_on_current_screen() { $load_ate_jobs_synchronization = $this->is_edit_list_page_of_a_translatable_type() || $this->is_edit_page_of_a_translatable_type() || WPML_TM_Page::is_dashboard() || WPML_TM_Page::is_translation_queue(); if ( apply_filters( 'wpml_tm_load_ate_jobs_synchronization', $load_ate_jobs_synchronization ) ) { $this->job_sync_script_loader->load(); } } /** * @param string $message */ private function add_update_error_notice( $message ) { $error_log = new WPML_TM_ATE_API_Error(); $error_log->log( $message ); } /** * @todo: Remove this method in favor of the new SYNC/DOWNLOAD process. * * @param bool $updated * @param array|stdClass $translation_jobs * @param bool $ignore_errors * * @throws \RuntimeException * * @return int[] Returns an array of WPML job IDs that translation was applied (XLIFF updated) */ public function update_jobs( $updated, $translation_jobs, $ignore_errors = false ) { /** * We should only expect an array of objects. * However, this method can be called by an action and a known issue may cause to pass a single object instead * * @see https://developer.wordpress.org/reference/functions/do_action/#comment-2371 */ if ( is_object( $translation_jobs ) ) { if ( isset( $translation_jobs->job_id ) ) { $translation_jobs = array( $translation_jobs ); } else { $translation_jobs = null; } } $jobs_with_translation_applied = array(); if ( $translation_jobs ) { $ate_jobs_data = $this->get_ate_jobs_data( $translation_jobs ); if ( ! $ate_jobs_data ) { return array(); } $job_ids_map = array(); foreach ( $translation_jobs as $translation_job ) { if ( $this->is_ate_translation_job( $translation_job ) ) { $ate_job_id = null; if ( isset( $ate_jobs_data[ $translation_job->job_id ]['ate_job_id'] ) ) { $ate_job_id = $ate_jobs_data[ $translation_job->job_id ]['ate_job_id']; $job_ids_map[ $ate_job_id ] = $translation_job->job_id; } } } if ( $job_ids_map ) { $ate_job_ids = array_keys( $job_ids_map ); $response = $this->ate_api->get_jobs( $ate_job_ids ); try { $this->check_response_error( $response ); } catch ( RuntimeException $e ) { $this->add_update_error_notice( $e->getMessage() ); } $processed = json_decode( wp_json_encode( $response ), true ); if ( $processed ) { foreach ( $processed as $ate_job_id => $ate_job_data ) { if ( array_key_exists( $ate_job_id, $job_ids_map ) ) { $wpml_job_id = (int) $job_ids_map[ $ate_job_id ]; if ( $this->is_delivered_job_being_edited( $wpml_job_id, $ate_job_data ) ) { continue; } try { $is_translations_applied = $this->maybe_apply_translation( $ate_job_data ); } catch ( Exception $e ) { if ( ! $ignore_errors ) { throw new RuntimeException( $e->getMessage(), $e->getCode() ); } continue; } if ( $is_translations_applied ) { $this->ate_jobs->store( $wpml_job_id, $ate_job_data ); if ( $this->must_acknowledge_ATE( $ate_job_data ) ) { $this->confirm_received_job( $ate_job_id, $ignore_errors ); } $jobs_with_translation_applied[] = $wpml_job_id; } else { $this->ate_jobs->store( $wpml_job_id, $ate_job_data ); if ( isset( $ate_job_data['status_id'] ) ) { $this->ate_jobs->set_wpml_status_from_ate( $wpml_job_id, (int) $ate_job_data['status_id'] ); } } } } } } } return $jobs_with_translation_applied; } /** * This situation happens when a job was delivered * and the translator is editing the job but he did not * click on the "Redeliver" button yet. * * @param int $wpml_job_id * @param array $ate_job_data * * @return bool */ private function is_delivered_job_being_edited( $wpml_job_id, array $ate_job_data ) { return isset( $ate_job_data['status_id'] ) && WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_DELIVERED === $ate_job_data['status_id'] && $this->ate_jobs->is_editing_job( $wpml_job_id ); } /** * If we have an XLIFF URL, we will fetch the remote file * and try to apply it. * * @param array $ate_job_data * * @return bool * @throws Requests_Exception */ private function maybe_apply_translation( array $ate_job_data ) { if ( isset( $ate_job_data['translated_xliff'] ) ) { $xliff_content = $this->ate_api->get_remote_xliff_content( $ate_job_data['translated_xliff'] ); if ( $xliff_content ) { return $this->ate_jobs->apply( $xliff_content ); } } return false; } /** * @param $ate_job_id * @param $ignore_errors * * @return bool */ private function confirm_received_job( $ate_job_id, $ignore_errors ) { $confirmation_response = $this->ate_api->confirm_received_job( $ate_job_id ); try { $this->check_response_error( $confirmation_response ); return true; } catch ( Exception $ex ) { if ( ! $ignore_errors ) { throw new $ex(); } return false; } } /** * @param mixed $response * * @throws \RuntimeException */ protected function check_response_error( $response ) { if ( is_wp_error( $response ) ) { $code = 0; $message = $response->get_error_message(); if ( $response->error_data && is_array( $response->error_data ) ) { foreach ( $response->error_data as $http_code => $error_data ) { $code = $error_data[0]['status']; $message = ''; switch ( (int) $code ) { case self::RESPONSE_ATE_NOT_ACTIVE_ERROR: $wp_admin_url = admin_url( 'admin.php' ); $mcsetup_page = add_query_arg( array( 'page' => WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS, 'sm' => 'mcsetup', ), $wp_admin_url ); $mcsetup_page .= '#ml-content-setup-sec-1'; $resend_link = '' . esc_html__( 'Resend that email', 'wpml-translation-management' ) . ''; $message .= '

' . esc_html__( 'WPML cannot send these documents to translation because the Advanced Translation Editor is not fully set-up yet.', 'wpml-translation-management' ) . '

' . esc_html__( 'Please open the confirmation email that you received and click on the link inside it to confirm your email.', 'wpml-translation-management' ) . '

' . $resend_link . '

'; break; case self::RESPONSE_ATE_DUPLICATED_SOURCE_ID: case self::RESPONSE_ATE_UNEXPECTED_ERROR: default: $message = '

' . __( 'Advanced Translation Editor error:', 'wpml-translation-management' ) . '

' . $error_data[0]['message'] . '

'; } $message = '

' . $message . '

'; } } /** @var WP_Error $response */ throw new RuntimeException( $message, $code ); } } /** * @param $ate_job_id */ private function resign_job_on_error( $ate_job_id ) { $job_id = $this->ate_jobs->get_wpml_job_id( $ate_job_id ); if ( $job_id ) { wpml_load_core_tm()->resign_translator( $job_id ); } } /** * @param $translation_job * * @return bool */ private function is_ate_translation_job( $translation_job ) { return 'local' === $translation_job->translation_service && WPML_TM_Editors::ATE === $translation_job->editor; } /** * @param $post * * @return array|null|WP_Post */ private function get_wp_post( $post ) { if ( ! $post instanceof WP_Post ) { if ( isset( $post->ID ) ) { $post = get_post( $post->ID ); } else { $post = null; } } return $post; } /** * @return bool */ private function is_edit_list_page_of_a_translatable_type() { return $this->current_screen->is_edit_posts_list() && $this->sitepress->is_translated_post_type( $this->current_screen->get_post_type() ); } /** * @return bool */ private function is_edit_page_of_a_translatable_type() { return $this->current_screen->is_edit_post() && $this->sitepress->is_translated_post_type( $this->current_screen->get_post_type() ); } /** * @param int $trid * @param string $element_type * * @return mixed */ private function get_original_element( $trid, $element_type ) { if ( ! array_key_exists( $trid, $this->trid_original_element_map ) ) { $element_translation = $this->sitepress->get_original_element_translation( $trid, $element_type ); if ( $element_translation ) { $this->trid_original_element_map[ $trid ] = $element_translation; return $element_translation; } } return $this->trid_original_element_map[ $trid ]; } /** * @param $job_status * * @return bool */ private function must_acknowledge_ATE( $job_status ) { return $job_status['status_id'] === WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_DELIVERING; } }