Files
doitinpoland.com/wp-content/plugins/sitepress-multilingual-cms/classes/xliff/class-wpml-tm-xliff-frontend.php
2023-09-12 21:41:04 +02:00

786 lines
22 KiB
PHP

<?php
/**
* WPML_TM_Xliff_Frontend class file
*
* @package wpml-translation-management
*/
use WPML\FP\Obj;
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once WPML_TM_PATH . '/inc/wpml_zip.php';
/**
* Class WPML_TM_Xliff_Frontend
*/
class WPML_TM_Xliff_Frontend extends WPML_TM_Xliff_Shared {
/**
* Success admin notices
*
* @var array
*/
private $success;
/**
* Attachments
*
* @var array
*/
private $attachments = array();
/**
* SitePress instance
*
* @var SitePress
*/
private $sitepress;
/**
* Name of archive
*
* @var string
*/
private $export_archive_name;
/**
* Priority of late initialisation
*
* @var int
*/
private $late_init_priority = 9999;
/**
* Is simple xml turned on
*
* @var bool
*/
private $simplexml_on;
/**
* WPML_TM_Xliff_Frontend constructor
*
* @param WPML_Translation_Job_Factory $job_factory Job factory.
* @param SitePress $sitepress SitePress instance.
* @param boolean $simplexml_on Is simple xml turned on.
*/
public function __construct( WPML_Translation_Job_Factory $job_factory, SitePress $sitepress, $simplexml_on ) {
parent::__construct( $job_factory );
$this->sitepress = $sitepress;
$this->simplexml_on = $simplexml_on;
}
/**
* Get available xliff versions
*
* @return array
*/
public function get_available_xliff_versions() {
return array(
'10' => '1.0',
'11' => '1.1',
'12' => '1.2',
);
}
/**
* Get init priority
*
* @return int
*/
public function get_init_priority() {
return isset( $_POST['xliff_upload'] ) ||
( isset( $_GET['wpml_xliff_action'] ) && $_GET['wpml_xliff_action'] === 'download' ) ||
isset( $_POST['wpml_xliff_export_all_filtered'] ) ?
$this->get_late_init_priority() : 10;
}
/**
* Get late init priority
*
* @return int
*/
public function get_late_init_priority() {
return $this->late_init_priority;
}
/**
* Init class
*
* @return bool
* @throws Exception Throws an exception in case of errors.
*/
public function init() {
$this->attachments = array();
$this->error = null;
if ( $this->sitepress->get_wp_api()->is_admin() ) {
add_action( 'admin_head', array( $this, 'js_scripts' ) );
add_action(
'wp_ajax_set_xliff_options',
array(
$this,
'ajax_set_xliff_options',
),
10,
2
);
if ( ! $this->sitepress->get_setting( 'xliff_newlines' ) ) {
$this->sitepress->set_setting( 'xliff_newlines', WPML_XLIFF_TM_NEWLINES_ORIGINAL, true );
}
if ( ! $this->sitepress->get_setting( 'tm_xliff_version' ) ) {
$this->sitepress->set_setting( 'tm_xliff_version', '12', true );
}
if ( 1 < count( $this->sitepress->get_languages( false, true ) ) ) {
add_filter(
'wpml_translation_queue_actions',
array(
$this,
'translation_queue_add_actions',
)
);
add_action(
'wpml_xliff_select_actions',
array(
$this,
'translation_queue_xliff_select_actions',
),
10,
4
);
add_action(
'wpml_translation_queue_after_display',
array(
$this,
'translation_queue_after_display',
),
10,
2
);
add_action(
'wpml_translator_notification',
array(
$this,
'translator_notification',
),
10,
0
);
add_filter(
'wpml_new_job_notification',
array(
$this,
'new_job_notification',
),
10,
2
);
add_filter(
'wpml_new_job_notification_attachments',
array(
$this,
'new_job_notification_attachments',
)
);
}
if (
isset( $_GET['wpml_xliff_action'] ) &&
$_GET['wpml_xliff_action'] === 'download' &&
wp_verify_nonce( $_GET['nonce'], 'xliff-export' )
) {
$archive = $this->get_xliff_archive(
Obj::propOr( '', 'xliff_version', $_GET ),
explode( ',', Obj::propOr( '', 'jobs', $_GET ) )
);
$this->stream_xliff_archive( $archive );
}
add_action( 'wp_ajax_wpml_xliff_upload', function () {
if ( $this->import_xliff( Obj::propOr( [], 'import', $_FILES ) ) ) {
wp_send_json_success( $this->success );
} else {
wp_send_json_error( $this->error );
}
} );
}
return true;
}
/**
* Set xliff options
*/
public function ajax_set_xliff_options() {
check_ajax_referer( 'icl_xliff_options_form_nonce', 'security' );
$newlines = isset( $_POST['icl_xliff_newlines'] ) ? (int) $_POST['icl_xliff_newlines'] : 0;
$this->sitepress->set_setting( 'xliff_newlines', $newlines, true );
$version = isset( $_POST['icl_xliff_version'] ) ? (int) $_POST['icl_xliff_version'] : 0;
$this->sitepress->set_setting( 'tm_xliff_version', $version, true );
wp_send_json_success(
array(
'message' => 'OK',
'newlines_saved' => $newlines,
'version_saved' => $version,
)
);
}
/**
* New job notification
*
* @param array $mail Email content.
* @param int $job_id Job id.
*
* @return array
*/
public function new_job_notification( $mail, $job_id ) {
$tm_settings = $this->sitepress->get_setting( 'translation-management', array() );
if ( isset( $tm_settings['notification']['include_xliff'] ) && $tm_settings['notification']['include_xliff'] ) {
$xliff_version = $this->get_user_xliff_version();
$xliff_file = $this->get_xliff_file( $job_id, $xliff_version );
$temp_dir = get_temp_dir();
$file_name = $temp_dir . get_bloginfo( 'name' ) . '-translation-job-' . $job_id . '.xliff';
$fh = fopen( $file_name, 'w' );
if ( $fh ) {
fwrite( $fh, $xliff_file );
fclose( $fh );
$mail['attachment'] = $file_name;
$this->attachments[ $job_id ] = $file_name;
$mail['body'] .= __( ' - A xliff file is attached.', 'wpml-translation-management' );
}
}
return $mail;
}
/**
* Get zip name from jobs
*
* @param array $job_ids Job ids.
*
* @return string
*/
private function get_zip_name_from_jobs( $job_ids ) {
$min_job = min( $job_ids );
$max_job = max( $job_ids );
if ( $max_job === $min_job ) {
return get_bloginfo( 'name' ) . '-translation-job-' . $max_job . '.zip';
} else {
return get_bloginfo( 'name' ) . '-translation-job-' . $min_job . '-' . $max_job . '.zip';
}
}
/**
* New job notification attachments
*
* @param array $attachments Job notification attachments.
*
* @return array
*/
public function new_job_notification_attachments( $attachments ) {
$found = false;
$archive = new wpml_zip();
foreach ( $attachments as $index => $attachment ) {
if ( in_array( $attachment, $this->attachments, true ) ) {
$fh = fopen( $attachment, 'r' );
$xliff_file = fread( $fh, filesize( $attachment ) );
fclose( $fh );
$archive->addFile( $xliff_file, basename( $attachment ) );
unset( $attachments[ $index ] );
$found = true;
}
}
if ( $found ) {
// Add the zip file to the attachments.
$archive_data = $archive->getZipData();
$temp_dir = get_temp_dir();
$file_name = $temp_dir . $this->get_zip_name_from_jobs( array_keys( $this->attachments ) );
$fh = fopen( $file_name, 'w' );
fwrite( $fh, $archive_data );
fclose( $fh );
$attachments[] = $file_name;
}
return $attachments;
}
/**
* Get xliff file
*
* @param int $job_id Job id.
* @param string $xliff_version Xliff version.
*
* @return string
*/
private function get_xliff_file( $job_id, $xliff_version = WPML_XLIFF_DEFAULT_VERSION ) {
return wpml_tm_xliff_factory()
->create_writer( $xliff_version )
->generate_job_xliff( $job_id );
}
/**
* Get xliff archive
*
* @param string $xliff_version Xliff version.
* @param array|null $job_ids Job ids.
*
* @return wpml_zip
*
* @throws Exception Throws an exception in case of errors.
*/
public function get_xliff_archive( $xliff_version, $job_ids = array() ) {
global $wpdb, $current_user;
if ( empty( $job_ids ) && isset( $_GET['xliff_export_data'] ) ) {
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$data = json_decode( base64_decode( $_GET['xliff_export_data'] ) );
// phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$job_ids = isset( $data->job ) ? array_keys( (array) $data->job ) : array();
}
$archive = new wpml_zip();
foreach ( $job_ids as $job_id ) {
$xliff_file = $this->get_xliff_file( $job_id, $xliff_version );
// Assign the job to this translator.
$rid = $wpdb->get_var(
$wpdb->prepare(
"SELECT rid
FROM {$wpdb->prefix}icl_translate_job
WHERE job_id=%d ",
$job_id
)
);
$data_value = array( 'translator_id' => $current_user->ID );
$data_where = array( 'job_id' => $job_id );
$wpdb->update( $wpdb->prefix . 'icl_translate_job', $data_value, $data_where );
$data_where = array( 'rid' => $rid );
$wpdb->update( $wpdb->prefix . 'icl_translation_status', $data_value, $data_where );
$archive->addFile( $xliff_file, get_bloginfo( 'name' ) . '-translation-job-' . $job_id . '.xliff' );
}
$this->export_archive_name = $this->get_zip_name_from_jobs( $job_ids );
$archive->finalize();
return $archive;
}
/**
* Stream xliff archive
*
* @param wpml_zip $archive Zip archive.
*
* @throws Exception Throws an exception in case of errors.
*/
private function stream_xliff_archive( $archive ) {
if ( is_a( $archive, 'wpml_zip' ) ) {
if ( defined( 'WPML_SAVE_XLIFF_PATH' ) && trim( WPML_SAVE_XLIFF_PATH ) ) {
$this->save_zip_file( WPML_SAVE_XLIFF_PATH, $archive );
}
$archive->sendZip( $this->export_archive_name );
}
exit;
}
/**
* Save zip file
*
* @param string $path Where to save the archive.
* @param wpml_zip $archive Zip archive.
*/
private function save_zip_file( $path, $archive ) {
$path = trailingslashit( $path );
if ( ! is_dir( $path ) ) {
$result = mkdir( $path );
if ( ! $result ) {
return;
}
}
$archive->setZipFile( $path . $this->export_archive_name );
}
/**
* Stops any redirects from happening when we call the
* translation manager to save the translations.
*
* @return null
*/
public function stop_redirect() {
return null;
}
/**
* Import xliff file
*
* @param array $file Xliff file data.
*
* @return bool|WP_Error
*/
private function import_xliff( $file ) {
global $current_user;
// We don't want any redirects happening when we save the translation.
add_filter( 'wp_redirect', array( $this, 'stop_redirect' ) );
$this->success = array();
$contents = array();
if ( 0 === (int) $file['size'] ) {
$this->error = new WP_Error( 'empty_file', __( 'You are trying to import an empty file.', 'wpml-translation-management' ) );
return false;
} elseif ( isset( $file['tmp_name'] ) && $file['tmp_name'] ) {
$fh = fopen( $file['tmp_name'], 'r' );
$data = fread( $fh, 4 );
fclose( $fh );
if ( $data[0] == 'P' && $data[1] == 'K' && $data[2] == chr( 03 ) && $data[3] == chr( 04 ) ) {
if ( class_exists( 'ZipArchive' ) ) {
$z = new ZipArchive();
$zopen = $z->open( $file['tmp_name'], 4 );
if ( true !== $zopen ) {
$this->error = new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.', 'wpml-translation-management' ) );
return false;
}
$empty_files = array();
for ( $i = 0; $i < $z->numFiles; $i ++ ) {
if ( ! $info = $z->statIndex( $i ) ) {
$this->error = new WP_Error( 'stat_failed', __( 'Could not retrieve file from archive.', 'wpml-translation-management' ) );
return false;
}
$content = $z->getFromIndex( $i );
if ( false === (bool) $content ) {
$empty_files[] = $info['name'];
}
$contents[ $info['name'] ] = $content;
}
if ( $empty_files ) {
$this->error = new WP_Error( 'extract_failed', __( 'The archive contains one or more empty files.', 'wpml-translation-management' ), $empty_files );
return false;
}
} else {
require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
$archive = new PclZip( $file['tmp_name'] );
// Is the archive valid?
$archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING );
if ( false == $archive_files ) {
$this->error = new WP_Error( 'incompatible_archive', __( 'You are trying to import an incompatible Archive.', 'wpml-translation-management' ), $archive->errorInfo( true ) );
return false;
}
if ( 0 === count( $archive_files ) ) {
$this->error = new WP_Error( 'empty_archive', __( 'You are trying to import an empty archive.', 'wpml-translation-management' ) );
return false;
}
$empty_files = array();
foreach ( $archive_files as $content ) {
if ( false === (bool) $content['content'] ) {
$empty_files[] = $content['filename'];
}
$contents[ $content['filename'] ] = $content['content'];
}
if ( $empty_files ) {
$this->error = new WP_Error( 'extract_failed', __( 'The archive contains one or more empty files.', 'wpml-translation-management' ), $empty_files );
return false;
}
}
} else {
$fh = fopen( $file['tmp_name'], 'r' );
$data = fread( $fh, $file['size'] );
fclose( $fh );
$contents[ $file['name'] ] = $data;
}
foreach ( $contents as $name => $content ) {
if ( $this->validate_file_name( $name ) ) {
list( $job, $job_data ) = $this->validate_file( $name, $content, $current_user );
if ( null !== $this->error ) {
return $job_data;
}
kses_remove_filters();
wpml_tm_save_data( $job_data );
kses_init();
// translators: %s: job id.
$this->success[] = $job->job_id;
}
}
return true;
}
return false;
}
/**
* Translation queue actions
*
* @param array $actions Actions.
* @param string $action_name Action name.
* @param array $translation_jobs Translation jobs.
* @param string $action
*/
public function translation_queue_xliff_select_actions( $actions, $action_name, $translation_jobs, $action ) {
if ( $this->has_translation_jobs( $translation_jobs ) && sizeof( $actions ) > 0 ) :
$currentAction = $action ?: false;
?>
<div class="alignleft actions">
<select name="<?php echo esc_html( $action_name ); ?>">
<option
value="-1" <?php echo $currentAction == false ? "selected='selected'" : ''; ?>><?php _e( 'Bulk Actions' ); ?></option>
<?php foreach ( $actions as $key => $action ) : ?>
<option <?php disabled( ! $this->simplexml_on ); ?>
value="<?php echo $key; ?>" <?php echo $currentAction == $key && $this->simplexml_on ? "selected='selected'" : ''; ?>><?php echo $action; ?></option>
<?php endforeach; ?>
</select>
<input
id="js-wpml-do-action"
type="submit" value="<?php esc_attr_e( 'Apply' ); ?>"
name="do<?php echo esc_html( $action_name ); ?>"
class="button-secondary action"/>
</div>
<?php
endif;
}
/**
* Has translation jobs
*
* @param array $translation_jobs Translation jobs.
*
* @return bool
*/
private function has_translation_jobs( $translation_jobs ) {
return $translation_jobs && array_key_exists( 'jobs', $translation_jobs ) && $translation_jobs['jobs'];
}
/**
* Get xliff version select options
*
* @return string
*/
private function get_xliff_version_select_options() {
$output = '';
$user_version = (int) $this->get_user_xliff_version();
foreach ( $this->get_available_xliff_versions() as $value => $label ) {
$user_version = false === $user_version ? $value : $user_version;
$output .= '<option value="' . $value . '"';
$output .= $user_version === $value ? 'selected="selected"' : '';
$output .= '>XLIFF ' . $label . '</option>';
}
return $output;
}
/**
* Adds the various possible XLIFF versions to translations queue page's export actions on display
*
* @param array $actions Actions.
*
* @return array
*/
public function translation_queue_add_actions( $actions ) {
foreach ( $this->get_available_xliff_versions() as $key => $value ) {
// translators: %s: XLIFF version.
$actions[ $key ] = sprintf( __( 'Export XLIFF %s', 'wpml-translation-management' ), $value );
}
return $actions;
}
/**
* Show error messages in admin notices
*/
public function admin_notices_error() {
if ( is_wp_error( $this->error ) ) {
?>
<div class="message error">
<p><?php echo $this->error->get_error_message(); ?></p>
<?php
if ( $this->error->get_error_data() ) {
?>
<ol>
<li><?php echo implode( '</li><li>', $this->error->get_error_data() ); ?></li>
</ol>
<?php
}
?>
</div>
<?php
}
}
/**
* Show success messages in admin notices
*/
public function admin_notices_success() {
?>
<div class="message updated"><p>
<ul>
<?php
foreach ( $this->success as $message ) {
echo '<li>' . $message . '</li>';
}
?>
</ul>
</p></div>
<?php
}
/**
* Check translation queue after display
*
* @param array $translation_jobs Translation jobs.
*/
public function translation_queue_after_display( $translation_jobs = array() ) {
if ( ! $this->has_translation_jobs( $translation_jobs ) ) {
return;
}
$export_label = esc_html__( 'Export all jobs:', 'wpml-translation-management' );
$cookie_filters = WPML_Translations_Queue::get_cookie_filters();
if ( $cookie_filters ) {
$type = __( 'All types', 'wpml-translation-management' );
if ( ! empty( $cookie_filters['type'] ) ) {
$post_slug = preg_replace( '/^post_|^package_/', '', $cookie_filters['type'], 1 );
$post_types = $this->sitepress->get_translatable_documents( true );
$post_types = apply_filters( 'wpml_get_translatable_types', $post_types );
if ( array_key_exists( $post_slug, $post_types ) ) {
$type = $post_types[ $post_slug ]->label;
}
}
$from = ! empty( $cookie_filters['from'] )
? $this->sitepress->get_display_language_name( $cookie_filters['from'] )
: __( 'Any language', 'wpml-translation-management' );
$to = ! empty( $cookie_filters['to'] )
? $this->sitepress->get_display_language_name( $cookie_filters['to'] )
: __( 'Any language', 'wpml-translation-management' );
$status = ! empty( $cookie_filters['status'] ) && (int) $cookie_filters['status'] !== ICL_TM_WAITING_FOR_TRANSLATOR
? TranslationManagement::status2text( $cookie_filters['status'] )
: ( ! empty( $cookie_filters['status'] ) ? __( 'Available to translate', 'wpml-translation-management' ) : 'All statuses' );
$export_label = sprintf(
// translators: %1: post type, %2: from language, %3: to language, %4: status.
esc_html__( 'Export all filtered jobs of %1$s from %2$s to %3$s in %4$s:', 'wpml-translation-management' ),
'<b>' . $type . '</b>',
'<b>' . $from . '</b>',
'<b>' . $to . '</b>',
'<b>' . $status . '</b>'
);
}
?>
<br/>
<table class="widefat">
<thead>
<tr>
<th><?php esc_html_e( 'Import / Export XLIFF', 'wpml-translation-management' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<?php if ( ! $this->simplexml_on ) : ?>
<div class="otgs-notice error">
<p>
<strong><?php esc_html_e( 'SimpleXML missing!', 'wpml-translation-management' ); ?></strong>
</p>
<p>
<?php esc_html_e( 'SimpleXML extension is required for using XLIFF files in WPML Translation Management.', 'wpml-translation-management' ); ?>
<a href="https://wpml.org/?page_id=716"><?php esc_html_e( 'WPML Minimum Requirements', 'wpml-translation-management' ); ?></a>
</p>
</div>
<?php endif; ?>
<form method="post" id="translation-xliff-export-all-filtered" action="">
<label for="wpml_xliff_export_all_filtered"><?php echo $export_label; ?></label>
<select name="xliff_version" class="select" <?php disabled( ! $this->simplexml_on ); ?>>
<?php
echo $this->get_xliff_version_select_options();
?>
</select>
<input
type="submit"
value="<?php esc_attr_e( 'Export', 'wpml-translation-management' ); ?>"
<?php
disabled( ! $this->simplexml_on );
?>
name="wpml_xliff_export_all_filtered" id="xliff_download"
class="button-secondary action"/>
<input
type="hidden" value="<?php echo wp_create_nonce( 'xliff-export-all-filtered' ); ?>"
name="nonce">
</form>
<hr>
<form enctype="multipart/form-data" method="post" id="translation-xliff-upload" action="">
<label for="upload-xliff-file"><?php _e( 'Select the xliff file or zip file to upload from your computer:&nbsp;', 'wpml-translation-management' ); ?></label>
<input
type="file" id="upload-xliff-file"
name="import" <?php disabled( ! $this->simplexml_on ); ?> />
<input
type="submit" value="<?php _e( 'Upload', 'wpml-translation-management' ); ?>"
name="xliff_upload" id="xliff_upload" class="button-secondary action"
<?php
disabled( ! $this->simplexml_on );
?>
/>
</form>
</td>
</tr>
</tbody>
</table>
<?php
}
/**
* Print online js script
*/
public function js_scripts() {
?>
<script type="text/javascript">
var wpml_xliff_ajax_nonce = '<?php echo wp_create_nonce( 'icl_xliff_options_form_nonce' ); ?>';
</script>
<?php
}
/**
* Provide translator notification
*/
public function translator_notification() {
$checked = $this->sitepress->get_setting( 'include_xliff_in_notification' ) ? 'checked="checked"' : '';
?>
<input
type="checkbox" name="include_xliff" id="icl_include_xliff"
value="1" <?php echo $checked; ?>/>
<label
for="icl_include_xliff"><?php _e( 'Include XLIFF files in notification emails', 'wpml-translation-management' ); ?></label>
<?php
}
/**
* Get user xliff version
*
* @return bool|string
*/
private function get_user_xliff_version() {
return $this->sitepress->get_setting( 'tm_xliff_version', false );
}
}