first commit

This commit is contained in:
2023-09-12 21:41:04 +02:00
commit 3361a7f053
13284 changed files with 2116755 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
<?php
class WPML_TM_Post extends WPML_TM_Translatable_Element {
/** @var array|null|WP_Post */
private $wp_post;
protected function init( $id ) {
$this->wp_post = get_post( $id );
}
protected function get_type() {
return 'post';
}
protected function get_total_words() {
return $this->word_count_records->get_post_word_count( $this->id )->get_total_words();
}
public function get_type_name( $label = null ) {
$post_type = $this->wp_post->post_type;
$post_type_label = ucfirst( $post_type );
$post_type_object = get_post_type_object( $post_type );
if ( isset( $post_type_object ) ) {
$post_type_object_item = $post_type_object;
$temp_post_type_label = '';
if ( isset( $post_type_object_item->labels->$label ) ) {
$temp_post_type_label = $post_type_object_item->labels->$label;
}
if ( trim( $temp_post_type_label ) == '' ) {
if ( isset( $post_type_object_item->labels->singular_name ) ) {
$temp_post_type_label = $post_type_object_item->labels->singular_name;
} elseif ( $label && $post_type_object_item->labels->name ) {
$temp_post_type_label = $post_type_object_item->labels->name;
}
}
if ( trim( $temp_post_type_label ) != '' ) {
$post_type_label = $temp_post_type_label;
}
}
return $post_type_label;
}
}

View File

@@ -0,0 +1,18 @@
<?php
class WPML_TM_String extends WPML_TM_Translatable_Element {
protected function init( $id ) {}
protected function get_type() {
return 'string';
}
protected function get_total_words() {
return $this->word_count_records->get_string_word_count( $this->id );
}
public function get_type_name( $label = null ) {
return __( 'String', 'wpml-translation-management' );
}
}

View File

@@ -0,0 +1,93 @@
<?php
class WPML_TM_Translatable_Element_Provider {
/** @var WPML_TM_Word_Count_Records $word_count_records */
private $word_count_records;
/** @var WPML_TM_Word_Count_Single_Process $single_process */
private $single_process;
/** @var null|WPML_ST_Package_Factory $st_package_factory */
private $st_package_factory;
public function __construct(
WPML_TM_Word_Count_Records $word_count_records,
WPML_TM_Word_Count_Single_Process $single_process,
WPML_ST_Package_Factory $st_package_factory = null
) {
$this->word_count_records = $word_count_records;
$this->single_process = $single_process;
$this->st_package_factory = $st_package_factory;
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return null|WPML_TM_Package_Element|WPML_TM_Post|WPML_TM_String
*/
public function get_from_job( WPML_TM_Job_Entity $job ) {
$id = $job->get_original_element_id();
switch ( $job->get_type() ) {
case WPML_TM_Job_Entity::POST_TYPE:
return $this->get_post( $id );
case WPML_TM_Job_Entity::STRING_TYPE:
return $this->get_string( $id );
case WPML_TM_Job_Entity::PACKAGE_TYPE:
return $this->get_package( $id );
}
return null;
}
/**
* @param string $type
* @param int $id
*
* @return null|WPML_TM_Package_Element|WPML_TM_Post|WPML_TM_String
*/
public function get_from_type( $type, $id ) {
switch ( $type ) {
case 'post':
return $this->get_post( $id );
case 'string':
return $this->get_string( $id );
case 'package':
return $this->get_package( $id );
}
return null;
}
/**
* @param int $id
*
* @return WPML_TM_Post
*/
private function get_post( $id ) {
return new WPML_TM_Post( $id, $this->word_count_records, $this->single_process );
}
/**
* @param int $id
*
* @return WPML_TM_String
*/
private function get_string( $id ) {
return new WPML_TM_String( $id, $this->word_count_records, $this->single_process );
}
/**
* @param int $id
*
* @return WPML_TM_Package_Element
*/
private function get_package( $id ) {
return new WPML_TM_Package_Element( $id, $this->word_count_records, $this->single_process, $this->st_package_factory );
}
}

View File

@@ -0,0 +1,56 @@
<?php
abstract class WPML_TM_Translatable_Element {
/** @var WPML_TM_Word_Count_Records $word_count_records */
protected $word_count_records;
/** @var WPML_TM_Word_Count_Single_Process $single_process */
protected $single_process;
/** @var int $id */
protected $id;
/**
* @param int $id
* @param WPML_TM_Word_Count_Records $word_count_records
* @param WPML_TM_Word_Count_Single_Process $single_process
*/
public function __construct(
$id,
WPML_TM_Word_Count_Records $word_count_records,
WPML_TM_Word_Count_Single_Process $single_process
) {
$this->word_count_records = $word_count_records;
$this->single_process = $single_process;
$this->set_id( $id );
}
public function set_id( $id ) {
if ( ! $id ) {
return;
}
$this->id = $id;
$this->init( $id );
}
abstract protected function init( $id );
abstract public function get_type_name( $label = null );
abstract protected function get_type();
abstract protected function get_total_words();
/** @return int */
public function get_words_count() {
$total_words = $this->get_total_words();
if ( $total_words ) {
return $total_words;
}
$this->single_process->process( $this->get_type(), $this->id );
return $this->get_total_words();
}
}

View File

@@ -0,0 +1,9 @@
<?php
interface IWPML_TM_Count {
public function get_total_words();
public function get_words_to_translate( $lang );
}

View File

@@ -0,0 +1,44 @@
<?php
class WPML_TM_Count_Composite implements IWPML_TM_Count {
/** @var IWPML_TM_Count[] $counts */
private $counts = array();
public function add_count( IWPML_TM_Count $count ) {
$this->counts[] = $count;
}
/** @var IWPML_TM_Count[] $counts */
public function add_counts( $counts ) {
foreach ( $counts as $count ) {
$this->add_count( $count );
}
}
/**
* @param string $lang
*
* @return int
*/
public function get_words_to_translate( $lang ) {
$words = 0;
foreach ( $this->counts as $count ) {
$words += $count->get_words_to_translate( $lang );
}
return $words;
}
/** @return int */
public function get_total_words() {
$words = 0;
foreach ( $this->counts as $count ) {
$words += $count->get_total_words();
}
return $words;
}
}

View File

@@ -0,0 +1,69 @@
<?php
class WPML_TM_Count implements IWPML_TM_Count {
/** @var int $total */
private $total = 0;
/** @var array $to_translate */
private $to_translate;
/**
* @param string|null $json_data
*/
public function __construct( $json_data = null ) {
if ( $json_data ) {
$this->set_properties_from_json( $json_data );
}
}
/** @param string $json_data */
public function set_properties_from_json( $json_data ) {
$data = json_decode( $json_data, true );
if ( isset( $data['total'] ) ) {
$this->total = (int) $data['total'];
}
if ( isset( $data['to_translate'] ) ) {
$this->to_translate = $data['to_translate'];
}
}
/** @return int */
public function get_total_words() {
return $this->total;
}
/** @param int $total */
public function set_total_words( $total ) {
$this->total = $total;
}
/**
* @param string $lang
*
* @return int|null
*/
public function get_words_to_translate( $lang ) {
if ( isset( $this->to_translate[ $lang ] ) ) {
return (int) $this->to_translate[ $lang ];
}
return null;
}
/** @return string */
public function to_string() {
return json_encode(
array(
'total' => $this->total,
'to_translate' => $this->to_translate,
)
);
}
public function set_words_to_translate( $lang, $quantity ) {
$this->to_translate[ $lang ] = $quantity;
}
}

View File

@@ -0,0 +1,47 @@
<?php
class WPML_TM_Word_Count_Admin_Hooks implements IWPML_Action {
/** @var WPML_WP_API $wp_api */
private $wp_api;
public function __construct( WPML_WP_API $wp_api ) {
$this->wp_api = $wp_api;
}
public function add_hooks() {
if ( $this->wp_api->is_dashboard_tab() ) {
add_action( 'wpml_tm_dashboard_word_count_estimation', array( $this, 'display_dialog_open_link' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
add_filter( 'wpml_words_count_url', array( $this, 'words_count_url_filter' ) );
}
public function enqueue_scripts() {
wp_enqueue_script(
'word-count-report',
WPML_TM_URL . '/dist/js/word-count/app.js',
array( 'jquery-ui-dialog' ),
WPML_TM_VERSION
);
}
/**
* @param string $default_url
*
* @return string
*/
public function words_count_url_filter( $default_url ) {
return $this->wp_api->get_tm_url( 'dashboard', '#words-count' );
}
public function display_dialog_open_link() {
echo '<a href="#" class="js-word-count-dialog-open"
data-nonce="' . wp_create_nonce( WPML_TM_Word_Count_Hooks_Factory::NONCE_ACTION ) . '"
data-dialog-title="' . esc_attr__( 'Word count estimation', 'wpml-translation-management' ) . '"
data-cancel="' . esc_attr__( 'Cancel', 'wpml-translation-management' ) . '"
data-loading-text="' . esc_attr__( 'Initializing...', 'wpml-translation-management' ) . '">'
. esc_html__( 'Word count for the entire site', 'wpml-translation-management' ) . '</a>';
}
}

View File

@@ -0,0 +1,64 @@
<?php
class WPML_TM_Word_Count_Ajax_Hooks implements IWPML_Action {
/** @var WPML_TM_Word_Count_Report $report */
private $report;
/** @var WPML_TM_Word_Count_Background_Process_Factory $process_factory*/
private $process_factory;
/** @var bool $requested_types_status */
private $requested_types_status;
public function __construct(
WPML_TM_Word_Count_Report $report,
WPML_TM_Word_Count_Background_Process_Factory $process_factory,
$requested_types_status
) {
$this->report = $report;
$this->process_factory = $process_factory;
$this->requested_types_status = $requested_types_status;
}
public function add_hooks() {
add_action( 'wp_ajax_wpml_word_count_get_report', array( $this, 'get_report' ) );
add_action( 'wp_ajax_wpml_word_count_start_count', array( $this, 'start_count' ) );
add_action( 'wp_ajax_wpml_word_count_cancel_count', array( $this, 'cancel_count' ) );
}
public function __call( $method_name, $arguments ) {
check_admin_referer( WPML_TM_Word_Count_Hooks_Factory::NONCE_ACTION, 'nonce' );
$this->{$method_name}();
wp_send_json_success();
}
private function get_report() {
$data = array(
'status' => $this->requested_types_status,
'report' => $this->report->render(),
);
wp_send_json_success( $data );
}
private function start_count() {
$requested_types = array();
foreach ( array( 'post_types', 'package_kinds' ) as $group ) {
$requested_types[ $group ] = isset( $_POST['requested_types'][ $group ] )
? filter_var_array( $_POST['requested_types'][ $group ] ) : array();
}
$requested_types_process = $this->process_factory->create_requested_types();
$this->report->set_requested_types( $requested_types );
$requested_types_process->init( $requested_types );
}
private function cancel_count() {
update_option(
WPML_TM_Word_Count_Hooks_Factory::OPTION_KEY_REQUESTED_TYPES_STATUS,
WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED
);
}
}

View File

@@ -0,0 +1,122 @@
<?php
class WPML_TM_Word_Count_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
const PROCESS_PENDING = 'pending';
const PROCESS_IN_PROGRESS = 'in-progress';
const PROCESS_COMPLETED = 'completed';
const OPTION_KEY_REQUESTED_TYPES_STATUS = 'wpml_word_count_requested_types_status';
const NONCE_ACTION = 'wpml_tm_word_count_ajax';
private $hooks = array();
private $requested_types_status;
private $translation_element_factory;
private $words_count_background_process_factory;
private $words_count_single_process_factory;
public function create() {
$this->requested_types_status = get_option( self::OPTION_KEY_REQUESTED_TYPES_STATUS, false );
$this->add_refresh_hooks();
$this->add_process_hooks();
$this->add_admin_hooks();
$this->add_ajax_hooks();
return $this->hooks;
}
private function add_refresh_hooks() {
if ( $this->is_heartbeat_autosave() ) {
return;
}
$this->hooks['refresh'] = new WPML_TM_Word_Count_Refresh_Hooks(
$this->get_words_count_single_process_factory(),
$this->get_translation_element_factory(),
class_exists( 'WPML_ST_Package_Factory' ) ? new WPML_ST_Package_Factory() : null
);
}
private function add_process_hooks() {
if ( $this->requested_types_status === self::PROCESS_IN_PROGRESS ) {
$this->hooks['process'] = new WPML_TM_Word_Count_Process_Hooks(
$this->get_words_count_background_process_factory()
);
}
}
private function add_admin_hooks() {
if ( is_admin() ) {
$this->hooks['admin'] = new WPML_TM_Word_Count_Admin_Hooks( new WPML_WP_API() );
}
}
private function add_ajax_hooks() {
if ( isset( $_POST['module'] ) && 'wpml_word_count' === $_POST['module'] && wpml_is_ajax() ) {
$report_view = new WPML_TM_Word_Count_Report_View(
new WPML_Twig_Template_Loader( array( WPML_TM_PATH . WPML_TM_Word_Count_Report_View::TEMPLATE_PATH ) ),
new WPML_WP_Cron_Check( new WPML_PHP_Functions() )
);
$records_factory = new WPML_TM_Word_Count_Records_Factory();
$report = new WPML_TM_Word_Count_Report(
$report_view,
$records_factory->create(),
$this->get_sitepress(),
$this->requested_types_status,
class_exists( 'WPML_Package_Helper' ) ? new WPML_Package_Helper() : null
);
$this->hooks['ajax'] = new WPML_TM_Word_Count_Ajax_Hooks(
$report,
$this->get_words_count_background_process_factory(),
$this->requested_types_status
);
}
}
/**
* @return WPML_TM_Word_Count_Single_Process_Factory
*/
private function get_words_count_single_process_factory() {
if ( ! $this->words_count_single_process_factory ) {
$this->words_count_single_process_factory = new WPML_TM_Word_Count_Single_Process_Factory();
}
return $this->words_count_single_process_factory;
}
private function get_translation_element_factory() {
if ( ! $this->translation_element_factory ) {
$this->translation_element_factory = new WPML_Translation_Element_Factory( $this->get_sitepress() );
}
return $this->translation_element_factory;
}
/**
* @return WPML_TM_Word_Count_Background_Process_Factory
*/
private function get_words_count_background_process_factory() {
if ( ! $this->words_count_background_process_factory ) {
$this->words_count_background_process_factory = new WPML_TM_Word_Count_Background_Process_Factory();
}
return $this->words_count_background_process_factory;
}
/**
* @return SitePress
*/
private function get_sitepress() {
global $sitepress;
return $sitepress;
}
private function is_heartbeat_autosave() {
return isset( $_POST['data']['wp_autosave'] );
}
}

View File

@@ -0,0 +1,21 @@
<?php
class WPML_TM_Word_Count_Process_Hooks implements IWPML_Action {
/** @var WPML_TM_Word_Count_Background_Process_Factory $process_factory */
private $process_factory;
/**
* @param WPML_TM_Word_Count_Background_Process_Factory $process_factory
*/
public function __construct( WPML_TM_Word_Count_Background_Process_Factory $process_factory ) {
$this->process_factory = $process_factory;
}
/**
* We need to include the hooks located in WP_Async_Request::__construct.
*/
public function add_hooks() {
$this->process_factory->create_requested_types();
}
}

View File

@@ -0,0 +1,103 @@
<?php
use WPML\FP\Fns;
class WPML_TM_Word_Count_Refresh_Hooks implements IWPML_Action {
const PRIORITY_AFTER_PB_STRING_REGISTRATION = 50;
/** @var WPML_TM_Word_Count_Single_Process_Factory $single_process_factory */
private $single_process_factory;
/** @var WPML_Translation_Element_Factory $element_factory */
private $element_factory;
/** @var WPML_ST_Package_Factory|null $st_package_factory */
private $st_package_factory;
/** @var array $packages_to_refresh */
private $packages_to_refresh = array();
public function __construct(
WPML_TM_Word_Count_Single_Process_Factory $single_process_factory,
WPML_Translation_Element_Factory $element_factory,
WPML_ST_Package_Factory $st_package_factory = null
) {
$this->single_process_factory = $single_process_factory;
$this->element_factory = $element_factory;
$this->st_package_factory = $st_package_factory;
}
public function add_hooks() {
add_action( 'save_post', Fns::memorize( array( $this, 'enqueue_post_word_count_refresh' ) ) );
if ( $this->st_package_factory ) {
add_action( 'wpml_register_string', array( $this, 'register_string_action' ), 10, 3 );
add_action( 'wpml_set_translated_strings', array( $this, 'set_translated_strings_action' ), 10, 2 );
add_action( 'wpml_delete_unused_package_strings', array( $this, 'delete_unused_package_strings_action' ) );
}
}
/** @param int $post_id */
public function enqueue_post_word_count_refresh( $post_id ) {
add_action( 'shutdown', $this->get_refresh_post_word_count( $post_id ), self::PRIORITY_AFTER_PB_STRING_REGISTRATION );
}
/** @param int $post_id */
public function get_refresh_post_word_count( $post_id ) {
return function() use ( $post_id ) {
$post_element = $this->element_factory->create( $post_id, 'post' );
if ( ! $post_element->is_translatable() ) {
return;
}
if ( $post_element->get_source_element() ) {
$post_id = $post_element->get_source_element()->get_id();
}
$this->single_process_factory->create()->process( 'post', $post_id );
};
}
/**
* @param string $string_value
* @param string $string_name
* @param array $package
*/
public function register_string_action( $string_value, $string_name, $package ) {
$this->defer_package_refresh( $package );
}
/** @param array $package */
public function delete_unused_package_strings_action( $package ) {
$this->defer_package_refresh( $package );
}
/**
* @param array $translations
* @param array $package
*/
public function set_translated_strings_action( $translations, $package ) {
$this->defer_package_refresh( $package );
}
/** @param array $package_array */
private function defer_package_refresh( $package_array ) {
$st_package = $this->st_package_factory->create( $package_array );
if ( $st_package->ID && ! $st_package->post_id && ! in_array( $st_package->ID, $this->packages_to_refresh, true ) ) {
$this->packages_to_refresh[] = $st_package->ID;
if ( ! has_action( 'shutdown', array( $this, 'refresh_packages_word_count' ) ) ) {
add_action( 'shutdown', array( $this, 'refresh_packages_word_count' ) );
}
}
}
public function refresh_packages_word_count() {
foreach ( $this->packages_to_refresh as $package_to_refresh ) {
$this->single_process_factory->create()->process( 'package', $package_to_refresh );
}
}
}

View File

@@ -0,0 +1,7 @@
<?php
interface IWPML_TM_Word_Calculator_Post {
public function count_words( WPML_Post_Element $post_element, $lang = null );
}

View File

@@ -0,0 +1,103 @@
<?php
class WPML_TM_Word_Calculator_Post_Custom_Fields implements IWPML_TM_Word_Calculator_Post {
/** @var WPML_TM_Word_Calculator $calculator */
private $calculator;
/** @var array|null $cf_settings from `$sitepress_settings['translation-management']['custom_fields_translation']` */
private $cf_settings;
/** @var array $fields_to_count */
private $fields_to_count = array();
/**
* WPML_TM_Word_Calculator_Post_Custom_Fields constructor.
*
* $cf_settings:
*
* <code>
* $array = [
* 'custom-field-1' => WPML_TRANSLATE_CUSTOM_FIELD,
* 'custom-field-2' => WPML_COPY_CUSTOM_FIELD,
* 'custom-field-3' => WPML_IGNORE_CUSTOM_FIELD,
* 'custom-field-4' => WPML_IGNORE_CUSTOM_FIELD,
* 'custom-field-5' => WPML_COPY_ONCE_CUSTOM_FIELD,
* ]
* </code>
*
* @param \WPML_TM_Word_Calculator $calculator An instance of WPML_TM_Word_Calculator.
* @param array|null $cf_settings An associative array where they key is the name of the custom field and the value is an integer representing the translation method.
*
* @see inc/constants.php for the values of the constsnts
*/
public function __construct( WPML_TM_Word_Calculator $calculator, array $cf_settings = null ) {
$this->calculator = $calculator;
$this->cf_settings = $cf_settings;
}
public function count_words( WPML_Post_Element $post_element, $lang = null ) {
$words = 0;
$post_id = $post_element->get_id();
$post_lang = $post_element->get_language_code();
if ( ! $post_lang || ! $post_id || ! $this->is_registered_type( $post_element )
|| empty( $this->cf_settings ) || ! is_array( $this->cf_settings )
) {
return $words;
}
$cf_to_count = $this->get_translatable_fields_to_count( $post_id );
foreach ( $cf_to_count as $cf ) {
$custom_fields_value = get_post_meta( $post_id, $cf );
if ( ! $custom_fields_value ) {
continue;
}
if ( is_scalar( $custom_fields_value ) ) {
// only support scalar values for now
$words += $this->calculator->count_words( $custom_fields_value, $post_lang );
} else {
foreach ( $custom_fields_value as $custom_fields_value_item ) {
if ( $custom_fields_value_item && is_scalar( $custom_fields_value_item ) ) {
// only support scalar values for now
$words += $this->calculator->count_words( $custom_fields_value_item, $post_lang );
}
}
}
}
return (int) $words;
}
/** @return bool */
private function is_registered_type( WPML_Post_Element $post_element ) {
$post_types = get_post_types();
return in_array( $post_element->get_type(), $post_types );
}
/** @return array */
private function get_translatable_fields_to_count( $post_id ) {
if ( ! $this->fields_to_count ) {
foreach ( $this->cf_settings as $cf => $mode ) {
if ( (int) $mode === WPML_TRANSLATE_CUSTOM_FIELD ) {
$this->fields_to_count[] = $cf;
}
}
}
/**
* Allow to modify the custom fields whose words will be counted.
*
* @param array $fields_to_count The fields to include when counting the words.
* @param int $post_id The ID of the post for which we are counting the words.
*
* @see \WPML_TM_Word_Calculator_Post_Custom_Fields::__construct
*/
return apply_filters( 'wpml_words_count_custom_fields_to_count', $this->fields_to_count, $post_id );
}
}

View File

@@ -0,0 +1,48 @@
<?php
class WPML_TM_Word_Calculator_Post_Object implements IWPML_TM_Word_Calculator_Post {
/** @var WPML_TM_Word_Calculator $calculator */
private $calculator;
/** @var WPML_TM_Word_Calculator_Post_Packages $packages_calculator */
private $packages_calculator;
public function __construct(
WPML_TM_Word_Calculator $calculator,
WPML_TM_Word_Calculator_Post_Packages $packages_calculator
) {
$this->calculator = $calculator;
$this->packages_calculator = $packages_calculator;
}
/**
* @param WPML_Post_Element $post_element
* @param string $lang
*
* @return int
*/
public function count_words( WPML_Post_Element $post_element, $lang = null ) {
$words = 0;
$wp_post = $post_element->get_wp_object();
$source_lang = $post_element->get_language_code();
if ( $wp_post ) {
$words += $this->calculator->count_words( $wp_post->post_title, $source_lang );
$words += $this->calculator->count_words( $wp_post->post_excerpt, $source_lang );
$words += $this->calculator->count_words( $wp_post->post_name, $source_lang );
if ( $this->has_string_packages( $post_element ) ) {
$words += $this->packages_calculator->count_words( $post_element, $lang );
} else {
$words += $this->calculator->count_words( $wp_post->post_content, $source_lang );
}
}
return $words;
}
private function has_string_packages( WPML_Post_Element $post_element ) {
return (bool) $this->packages_calculator->count_words( $post_element, null );
}
}

View File

@@ -0,0 +1,51 @@
<?php
class WPML_TM_Word_Calculator_Post_Packages implements IWPML_TM_Word_Calculator_Post {
/** @var WPML_TM_Word_Count_Records $records */
private $records;
/** @var WPML_TM_Count_Composite[] $package_counts */
private $package_counts;
public function __construct( WPML_TM_Word_Count_Records $records ) {
$this->records = $records;
}
/**
* @param WPML_Post_Element $post_element
* @param string|null $lang
*
* @return int
*/
public function count_words( WPML_Post_Element $post_element, $lang = null ) {
if ( $lang ) {
return $this->get_package_counts( $post_element )->get_words_to_translate( $lang );
} else {
return $this->get_package_counts( $post_element )->get_total_words();
}
}
/**
* @param WPML_Post_Element $post_element
*
* @return WPML_TM_Count_Composite
*/
private function get_package_counts( WPML_Post_Element $post_element ) {
$post_id = $post_element->get_id();
if ( ! isset( $this->package_counts[ $post_id ] ) ) {
$counts = $this->records->get_packages_word_counts( $post_id );
$word_count_composite = new WPML_TM_Count_Composite();
foreach ( $counts as $count ) {
$word_count_composite->add_count( $count );
}
$this->package_counts[ $post_id ] = $word_count_composite;
}
return $this->package_counts[ $post_id ];
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-2327
*/
class WPML_TM_Word_Calculator {
const ASIAN_LANGUAGE_CHAR_SIZE = 6;
/** @var WPML_PHP_Functions $php_functions */
private $php_functions;
public function __construct( WPML_PHP_Functions $php_functions ) {
$this->php_functions = $php_functions;
}
/**
* @param string $string
* @param string $language_code
*
* @return int
*/
public function count_words( $string, $language_code ) {
$sanitized_source = $this->sanitize_string( $string );
$words = 0;
if ( in_array( $language_code, self::get_asian_languages() ) ) {
$words += strlen( strip_tags( $sanitized_source ) ) / self::ASIAN_LANGUAGE_CHAR_SIZE;
} else {
$words += count( preg_split( '/[\s,:;!\.\?\(\)\[\]\-_\'"\\/]+/', $sanitized_source, 0, PREG_SPLIT_NO_EMPTY ) );
}
return (int) $words;
}
/** @return bool */
private function exclude_shortcodes_in_words_count() {
if ( $this->php_functions->defined( 'EXCLUDE_SHORTCODES_IN_WORDS_COUNT' ) ) {
return (bool) $this->php_functions->constant( 'EXCLUDE_SHORTCODES_IN_WORDS_COUNT' );
}
return false;
}
/**
* @param string $source
*
* @return string
*/
private function sanitize_string( $source ) {
$result = $source;
$result = html_entity_decode( $result );
$result = strip_tags( $result );
$result = trim( $result );
$result = $this->strip_urls( $result );
if ( $this->exclude_shortcodes_in_words_count() ) {
$result = strip_shortcodes( $result );
} else {
$result = $this->extract_content_in_shortcodes( $result );
}
return $result;
}
/**
* @param string $string
*
* @return string
*/
private function extract_content_in_shortcodes( $string ) {
return preg_replace( '#(?:\[/?)[^/\]]+/?\]#s', '', $string );
}
/**
* @param string $string
*
* @return string
*/
private function strip_urls( $string ) {
return preg_replace( '/\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i', '', $string );
}
public static function get_asian_languages() {
return array( 'ja', 'ko', 'zh-hans', 'zh-hant', 'mn', 'ne', 'hi', 'pa', 'ta', 'th' );
}
}

View File

@@ -0,0 +1,6 @@
<?php
interface IWPML_TM_Word_Count_Set {
public function process( $id );
}

View File

@@ -0,0 +1,44 @@
<?php
class WPML_TM_Word_Count_Set_Package {
/** @var WPML_ST_Package_Factory $package_factory */
private $package_factory;
/** @var WPML_TM_Word_Count_Records $records */
private $records;
/** @var array $active_langs */
private $active_langs;
public function __construct(
WPML_ST_Package_Factory $package_factory,
WPML_TM_Word_Count_Records $records,
array $active_langs
) {
$this->package_factory = $package_factory;
$this->records = $records;
$this->active_langs = $active_langs;
}
/** @param int $package_id */
public function process( $package_id ) {
$package = $this->package_factory->create( $package_id );
$word_counts = new WPML_TM_Count();
foreach ( $this->active_langs as $lang ) {
if ( $package->get_package_language() === $lang ) {
$word_counts->set_total_words(
$this->records->get_string_words_to_translate_per_lang( null, $package_id )
);
} else {
$word_counts->set_words_to_translate(
$lang,
$this->records->get_string_words_to_translate_per_lang( $lang, $package_id )
);
}
}
$this->records->set_package_word_count( $package_id, $word_counts );
}
}

View File

@@ -0,0 +1,70 @@
<?php
class WPML_TM_Word_Count_Set_Post {
/** @var WPML_Translation_Element_Factory $element_factory */
private $element_factory;
/** @var WPML_TM_Word_Count_Records $records */
private $records;
/** @var IWPML_TM_Word_Calculator_Post[] $post_calculators */
private $post_calculators;
/** @var array $active_langs */
private $active_langs;
/** @var WPML_Post_Element $post_element */
private $post_element;
/**
* @param WPML_Translation_Element_Factory $element_factory
* @param WPML_TM_Word_Count_Records $records
* @param IWPML_TM_Word_Calculator_Post[] $calculators
* @param array $active_langs
*/
public function __construct(
WPML_Translation_Element_Factory $element_factory,
WPML_TM_Word_Count_Records $records,
array $calculators,
array $active_langs
) {
$this->element_factory = $element_factory;
$this->records = $records;
$this->post_calculators = $calculators;
$this->active_langs = $active_langs;
}
/**
* @param int $post_id
*/
public function process( $post_id ) {
$this->post_element = $this->element_factory->create( $post_id, 'post' );
$word_count = new WPML_TM_Count();
foreach ( $this->active_langs as $lang ) {
if ( $this->post_element->get_language_code() === $lang ) {
$word_count->set_total_words( $this->calculate_in_lang( null ) );
} else {
$word_count->set_words_to_translate( $lang, $this->calculate_in_lang( $lang ) );
}
}
$this->records->set_post_word_count( $post_id, $word_count );
}
/**
* @param string $lang
*
* @return int
*/
private function calculate_in_lang( $lang ) {
$words = 0;
foreach ( $this->post_calculators as $calculator ) {
$words += $calculator->count_words( $this->post_element, $lang );
}
return $words;
}
}

View File

@@ -0,0 +1,24 @@
<?php
class WPML_TM_Word_Count_Set_String {
/** @var WPML_TM_Word_Count_Records $records */
private $records;
/** @var WPML_TM_Word_Calculator $calculator */
private $calculator;
public function __construct( WPML_TM_Word_Count_Records $records, WPML_TM_Word_Calculator $calculator ) {
$this->records = $records;
$this->calculator = $calculator;
}
/**
* @param int $string_id
*/
public function process( $string_id ) {
$string = $this->records->get_string_value_and_language( $string_id );
$word_count = $this->calculator->count_words( $string->value, $string->language );
$this->records->set_string_word_count( $string_id, $word_count );
}
}

View File

@@ -0,0 +1,34 @@
<?php
class WPML_TM_Word_Count_Setters_Factory {
/**
* @return IWPML_TM_Word_Count_Set[]
*/
public function create() {
global $sitepress;
$records_factory = new WPML_TM_Word_Count_Records_Factory();
$records = $records_factory->create();
$calculator = new WPML_TM_Word_Calculator( new WPML_PHP_Functions() );
$active_langs = array_keys( $sitepress->get_active_languages() );
$post_calculators = array(
new WPML_TM_Word_Calculator_Post_Object( $calculator, new WPML_TM_Word_Calculator_Post_Packages( $records ) ),
new WPML_TM_Word_Calculator_Post_Custom_Fields( $calculator, \WPML\TM\Settings\Repository::getCustomFields() ),
);
$setters = array(
'post' => new WPML_TM_Word_Count_Set_Post( new WPML_Translation_Element_Factory( $sitepress ), $records, $post_calculators, $active_langs ),
);
if ( class_exists( 'WPML_ST_Package_Factory' ) ) {
$setters['string'] = new WPML_TM_Word_Count_Set_String( $records, $calculator );
$setters['package'] = new WPML_TM_Word_Count_Set_Package( new WPML_ST_Package_Factory(), $records, $active_langs );
}
return $setters;
}
}

View File

@@ -0,0 +1,20 @@
<?php
class WPML_TM_Word_Count_Single_Process_Factory {
public function create() {
/** @var wpdb $wpdb */
global $wpdb;
$setters_factory = new WPML_TM_Word_Count_Setters_Factory();
$dependencies_builder = null;
if ( class_exists( 'WPML_ST_String_Dependencies_Builder' ) ) {
$dependencies_builder = new WPML_ST_String_Dependencies_Builder(
new WPML_ST_String_Dependencies_Records( $wpdb )
);
}
return new WPML_TM_Word_Count_Single_Process( $setters_factory->create(), $dependencies_builder );
}
}

View File

@@ -0,0 +1,37 @@
<?php
class WPML_TM_Word_Count_Single_Process {
/** @var IWPML_TM_Word_Count_Set[] $setters */
private $setters;
/** @var WPML_ST_String_Dependencies_Builder $dependencies_builder */
private $dependencies_builder;
/**
* @param IWPML_TM_Word_Count_Set[] $setters
* @param WPML_ST_String_Dependencies_Builder $dependencies_builder
*/
public function __construct( array $setters, WPML_ST_String_Dependencies_Builder $dependencies_builder = null ) {
$this->setters = $setters;
$this->dependencies_builder = $dependencies_builder;
}
/**
* @param string $element_type
* @param int $element_id
*/
public function process( $element_type, $element_id ) {
if ( $this->dependencies_builder ) {
$dependencies_tree = $this->dependencies_builder->from( $element_type, $element_id );
while ( ! $dependencies_tree->iteration_completed() ) {
$node = $dependencies_tree->get_next();
$this->setters[ $node->get_type() ]->process( $node->get_id() );
$node->detach();
}
} elseif ( 'post' === $element_type ) {
$this->setters['post']->process( $element_id );
}
}
}

View File

@@ -0,0 +1,22 @@
<?php
class WPML_TM_Word_Count_Background_Process_Factory {
const PREFIX = 'wpml_tm';
const ACTION_REQUESTED_TYPES = 'word_count_background_process_requested_types';
/**
* @return WPML_TM_Word_Count_Background_Process_Requested_Types
*/
public function create_requested_types() {
$records_factory = new WPML_TM_Word_Count_Records_Factory();
$records = $records_factory->create();
$setters_factory = new WPML_TM_Word_Count_Setters_Factory();
$setters = $setters_factory->create();
$requested_types_items = new WPML_TM_Word_Count_Queue_Items_Requested_Types( $records );
return new WPML_TM_Word_Count_Background_Process_Requested_Types( $requested_types_items, $setters, $records );
}
}

View File

@@ -0,0 +1,114 @@
<?php
class WPML_TM_Word_Count_Background_Process_Requested_Types extends WPML_TM_Word_Count_Background_Process {
/** @var WPML_TM_Word_Count_Queue_Items_Requested_Types $queue */
protected $queue;
/** @var WPML_TM_Word_Count_Records $records */
private $records;
/**
* @param WPML_TM_Word_Count_Queue_Items_Requested_Types $queue_items
* @param IWPML_TM_Word_Count_Set[] $setters
*/
public function __construct(
WPML_TM_Word_Count_Queue_Items_Requested_Types $queue_items,
array $setters,
WPML_TM_Word_Count_Records $records
) {
/** We need to set the action before constructing the parent class `WP_Async_Request` */
$this->action = WPML_TM_Word_Count_Background_Process_Factory::ACTION_REQUESTED_TYPES;
parent::__construct( $queue_items, $setters );
$this->records = $records;
add_filter(
'wpml_tm_word_count_background_process_requested_types_memory_exceeded',
array(
$this,
'memory_exceeded_filter',
)
);
}
public function init( $requested_types ) {
$this->queue->reset( $requested_types );
$this->records->reset_all( $requested_types );
$this->dispatch();
}
public function dispatch() {
update_option(
WPML_TM_Word_Count_Hooks_Factory::OPTION_KEY_REQUESTED_TYPES_STATUS,
WPML_TM_Word_Count_Hooks_Factory::PROCESS_IN_PROGRESS
);
parent::dispatch();
}
public function complete() {
update_option(
WPML_TM_Word_Count_Hooks_Factory::OPTION_KEY_REQUESTED_TYPES_STATUS,
WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED
);
parent::complete();
}
/**
* Filter result of memory_exceeded() function in WP_Background_Process class.
* Used by it get_memory_limit() function of WP_Background_Process class contains a number of bugs,
* producing wrong result when 'memory_limit' setting in php.ini is in human readable format like '1G'.
*
* @return bool
*/
public function memory_exceeded_filter() {
$memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
$current_memory = memory_get_usage( true );
return $current_memory >= $memory_limit;
}
/**
* Get memory limit in bytes.
*
* @return int
*/
protected function get_memory_limit() {
if ( function_exists( 'ini_get' ) ) {
$memory_limit = ini_get( 'memory_limit' );
} else {
// Sensible default.
$memory_limit = '128M';
}
if ( ! $memory_limit || - 1 === intval( $memory_limit ) ) {
// Unlimited, set to 32GB.
$memory_limit = '32000M';
}
return $this->convert_shorthand_to_bytes( $memory_limit );
}
/**
* Converts a shorthand byte value to an integer byte value.
*
* @param string $value A (PHP ini) byte value, either shorthand or ordinary.
* @return int An integer byte value.
*/
protected function convert_shorthand_to_bytes( $value ) {
$value = strtolower( trim( $value ) );
$bytes = (int) $value;
if ( false !== strpos( $value, 'g' ) ) {
$bytes *= 1024 * 1024 * 1024;
} elseif ( false !== strpos( $value, 'm' ) ) {
$bytes *= 1024 * 1024;
} elseif ( false !== strpos( $value, 'k' ) ) {
$bytes *= 1024;
}
// Deal with large (float) values which run into the maximum integer size.
return min( $bytes, PHP_INT_MAX );
}
}

View File

@@ -0,0 +1,59 @@
<?php
abstract class WPML_TM_Word_Count_Background_Process extends WP_Background_Process {
/** @var IWPML_TM_Word_Count_Queue_Items $queue */
protected $queue;
/** @var IWPML_TM_Word_Count_Set[] $setters */
private $setters;
/**
* @param IWPML_TM_Word_Count_Queue_Items $queue
* @param IWPML_TM_Word_Count_Set[] $setters
*/
public function __construct( IWPML_TM_Word_Count_Queue_Items $queue, array $setters ) {
/** We need to set the prefix and the identifier before constructing the parent class `WP_Async_Request` */
$this->prefix = WPML_TM_Word_Count_Background_Process_Factory::PREFIX;
$this->action = WPML_TM_Word_Count_Background_Process_Factory::ACTION_REQUESTED_TYPES;
parent::__construct();
$this->queue = $queue;
$this->setters = $setters;
}
/**
* This abstract method is not implemented because we override the `handle` method.
*/
protected function task( $item ) {}
protected function handle() {
$this->lock_process();
while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->queue->is_completed() ) {
list( $id, $type ) = $this->queue->get_next();
if ( $id && $type ) {
$this->setters[ $type ]->process( $id );
$this->queue->remove( $id, $type );
}
}
$this->queue->save();
$this->unlock_process();
if ( $this->queue->is_completed() ) {
$this->complete();
} else {
$this->dispatch();
}
wp_die();
}
protected function is_queue_empty() {
return $this->queue->is_completed();
}
}

View File

@@ -0,0 +1,21 @@
<?php
interface IWPML_TM_Word_Count_Queue_Items {
/**
* @return array|null a tuple containing the element id and type or null if queue is empty
*/
public function get_next();
/**
* @param int $id
* @param string $type
*/
public function remove( $id, $type );
/** @return bool */
public function is_completed();
public function save();
}

View File

@@ -0,0 +1,170 @@
<?php
class WPML_TM_Word_Count_Queue_Items_Requested_Types implements IWPML_TM_Word_Count_Queue_Items {
const OPTION_KEY = 'wpml_word_count_queue_items_requested_types';
const STEP_STANDALONE_PACKAGES = 1;
const STEP_POST_PACKAGES = 2;
const STEP_POSTS = 3;
const STEP_COMPLETED = 4;
/** @var WPML_TM_Word_Count_Records $records */
private $records;
/** @var array $requested_types to be processed */
private $requested_types;
/** @var string $step */
private $step;
/** @var array|null $items */
private $items = array(
'string' => array(),
'package' => array(),
'post' => array(),
);
public function __construct( WPML_TM_Word_Count_Records $records ) {
$this->records = $records;
}
/**
* @return array|null a tuple containing the element id and type or null if queue is empty
*/
public function get_next() {
$this->init_queue();
foreach ( array( 'string', 'package', 'post' ) as $type ) {
if ( $this->items[ $type ] ) {
return array( reset( $this->items[ $type ] ), $type );
}
}
return null;
}
private function init_queue() {
if ( ! $this->step ) {
$this->restore_queue_from_db();
}
if ( ! $this->has_items() ) {
$this->init_step();
}
}
private function restore_queue_from_db() {
$this->step = self::STEP_STANDALONE_PACKAGES;
$options = get_option( self::OPTION_KEY, array() );
if ( isset( $options['step'] ) ) {
$this->step = $options['step'];
}
if ( isset( $options['requested_types'] ) ) {
$this->requested_types = $options['requested_types'];
}
if ( isset( $options['items'] ) ) {
$this->items = $options['items'];
}
}
private function init_step() {
switch ( $this->step ) {
case self::STEP_STANDALONE_PACKAGES:
$this->add_standalone_packages_to_queue();
break;
case self::STEP_POST_PACKAGES:
$this->add_post_packages_to_queue();
break;
case self::STEP_POSTS:
$this->add_posts_to_queue();
break;
}
$this->make_item_keys_equals_to_id();
$this->maybe_move_to_next_step();
}
private function add_standalone_packages_to_queue() {
if ( ! empty( $this->requested_types['package_kinds'] ) ) {
$this->items['package'] = $this->records->get_package_ids_from_kind_slugs( $this->requested_types['package_kinds'] );
$this->items['string'] = $this->records->get_strings_ids_from_package_ids( $this->items['package'] );
}
}
private function add_post_packages_to_queue() {
if ( ! empty( $this->requested_types['post_types'] ) ) {
$this->items['package'] = $this->records->get_package_ids_from_post_types( $this->requested_types['post_types'] );
$this->items['string'] = $this->records->get_strings_ids_from_package_ids( $this->items['package'] );
}
}
private function add_posts_to_queue() {
if ( ! empty( $this->requested_types['post_types'] ) ) {
$this->items['post'] = $this->records->get_post_source_ids_from_types( $this->requested_types['post_types'] );
}
}
private function make_item_keys_equals_to_id() {
foreach ( $this->items as $type => $ids ) {
if ( $this->items[ $type ] ) {
$this->items[ $type ] = array_combine( array_values( $this->items[ $type ] ), $this->items[ $type ] );
}
}
}
private function maybe_move_to_next_step() {
if ( ! $this->has_items() && ! $this->is_completed() ) {
$this->step++;
$this->init_step();
}
}
/**
* @param int $id
* @param string $type
*/
public function remove( $id, $type ) {
if ( isset( $this->items[ $type ][ $id ] ) ) {
unset( $this->items[ $type ][ $id ] );
}
$this->maybe_move_to_next_step();
}
/** @return bool */
private function has_items() {
return ! empty( $this->items['string'] )
|| ! empty( $this->items['package'] )
|| ! empty( $this->items['post'] );
}
/** @return bool */
public function is_completed() {
return $this->step === self::STEP_COMPLETED;
}
public function save() {
$options = array(
'step' => $this->step,
'requested_types' => $this->requested_types,
'items' => $this->items,
);
update_option( self::OPTION_KEY, $options, false );
}
public function reset( array $requested_types ) {
$this->step = self::STEP_STANDALONE_PACKAGES;
$this->requested_types = $requested_types;
$this->items = null;
$this->save();
}
}

View File

@@ -0,0 +1,124 @@
<?php
class WPML_TM_Word_Count_Post_Records {
const META_KEY = '_wpml_word_count';
/** @var wpdb $wpdb */
private $wpdb;
public function __construct( wpdb $wpdb ) {
$this->wpdb = $wpdb;
}
/**
* Returns only IDs in the source language
*
* @return array
*/
public function get_all_ids_without_word_count() {
$query = "
SELECT ID FROM {$this->wpdb->posts} AS p
LEFT JOIN {$this->wpdb->prefix}icl_translations AS t
ON t.element_id = p.ID AND t.element_type = CONCAT('post_', p.post_type)
WHERE t.source_language_code IS NULL
AND t.element_type IS NOT NULL
AND p.ID NOT IN(
SELECT post_id FROM {$this->wpdb->postmeta}
WHERE meta_key = '" . self::META_KEY . "'
);
";
return array_map( 'intval', $this->wpdb->get_col( $query ) );
}
/**
* @param int $post_id
*
* @return string raw word count
*/
public function get_word_count( $post_id ) {
return get_post_meta( $post_id, self::META_KEY, true );
}
/**
* @param int $post_id
* @param string $word_count raw word count
*
* @return bool|int
*/
public function set_word_count( $post_id, $word_count ) {
return update_post_meta( $post_id, self::META_KEY, $word_count );
}
public function reset_all( array $post_types ) {
if ( ! $post_types ) {
return;
}
$query = "DELETE pm FROM {$this->wpdb->postmeta} AS pm
INNER JOIN {$this->wpdb->posts} AS p
ON p.ID = pm.post_id
WHERE pm.meta_key = %s AND p.post_type IN(" . wpml_prepare_in( $post_types ) . ")";
$this->wpdb->query( $this->wpdb->prepare( $query, self::META_KEY ) );
}
/**
* @param array $post_types
*
* @return array
*/
public function get_source_ids_from_types( array $post_types ) {
$query = "SELECT ID FROM {$this->wpdb->posts} AS p
LEFT JOIN {$this->wpdb->prefix}icl_translations AS t
ON t.element_id = p.ID AND t.element_type = CONCAT('post_', p.post_type)
WHERE t.source_language_code IS NULL
AND t.language_code IS NOT NULL
AND p.post_type IN (" . wpml_prepare_in( $post_types ) . ")";
return array_map( 'intval', $this->wpdb->get_col( $query ) );
}
/**
* @param string $post_type
*
* @return int
*/
public function count_source_items_by_type( $post_type ) {
$query = "SELECT COUNT(*) FROM {$this->wpdb->posts} AS p
LEFT JOIN {$this->wpdb->prefix}icl_translations AS t
ON t.element_id = p.ID AND t.element_type = CONCAT('post_', p.post_type)
WHERE t.source_language_code IS NULL
AND t.element_type = %s
AND p.post_status <> 'trash'";
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, 'post_' . $post_type ) );
}
public function count_word_counts_by_type( $post_type ) {
$query = "SELECT COUNT(*) FROM {$this->wpdb->postmeta} AS pm
LEFT JOIN {$this->wpdb->posts} AS p
ON p.ID = pm.post_id
WHERE pm.meta_key = '" . self::META_KEY . "'
AND p.post_type = %s
AND p.post_status <> 'trash'";
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, $post_type ) );
}
/**
* @param string $post_type
*
* @return array
*/
public function get_word_counts_by_type( $post_type ) {
$query = "SELECT meta_value FROM {$this->wpdb->postmeta} AS pm
LEFT JOIN {$this->wpdb->posts} AS p
ON p.ID = pm.post_id
WHERE p.post_type = %s AND pm.meta_key = %s
AND p.post_status <> 'trash'";
return $this->wpdb->get_col( $this->wpdb->prepare( $query, $post_type, self::META_KEY ) );
}
}

View File

@@ -0,0 +1,18 @@
<?php
class WPML_TM_Word_Count_Records_Factory {
/**
* @return \WPML_TM_Word_Count_Records
* @throws \Auryn\InjectionException
*/
public function create() {
return \WPML\Container\make(
'\WPML_TM_Word_Count_Records',
[
':package_records' => \WPML\Container\make( '\WPML_ST_Word_Count_Package_Records' ),
':string_records' => \WPML\Container\make( '\WPML_ST_Word_Count_String_Records' ),
]
);
}
}

View File

@@ -0,0 +1,285 @@
<?php
class WPML_TM_Word_Count_Records {
/** @var WPML_TM_Word_Count_Post_Records $post_records */
private $post_records;
/** @var WPML_ST_Word_Count_Package_Records|null $package_records */
private $package_records;
/** @var WPML_ST_Word_Count_String_Records|null $string_records */
private $string_records;
public function __construct(
WPML_TM_Word_Count_Post_Records $post_records,
WPML_ST_Word_Count_Package_Records $package_records = null,
WPML_ST_Word_Count_String_Records $string_records = null
) {
$this->post_records = $post_records;
$this->package_records = $package_records;
$this->string_records = $string_records;
}
/** @return array */
public function get_all_post_ids_without_word_count() {
return $this->post_records->get_all_ids_without_word_count();
}
/**
* @param $post_id
*
* @return WPML_TM_Count
*/
public function get_post_word_count( $post_id ) {
return new WPML_TM_Count( $this->post_records->get_word_count( $post_id ) );
}
/**
* @param int $post_id
* @param WPML_TM_Count $word_count
*
* @return bool|int
*/
public function set_post_word_count( $post_id, WPML_TM_Count $word_count ) {
return $this->post_records->set_word_count( $post_id, $word_count->to_string() );
}
/** @return array */
public function get_all_package_ids() {
if ( $this->package_records ) {
return $this->package_records->get_all_package_ids();
}
return array();
}
/** @return array */
public function get_packages_ids_without_word_count() {
if ( $this->package_records ) {
return $this->package_records->get_packages_ids_without_word_count();
}
return array();
}
/**
* @param int $post_id
*
* @return WPML_TM_Count[]
*/
public function get_packages_word_counts( $post_id ) {
$counts = array();
if ( $this->package_records ) {
$raw_counts = $this->package_records->get_word_counts( $post_id );
foreach ( $raw_counts as $raw_count ) {
$counts[] = new WPML_TM_Count( $raw_count );
}
}
return $counts;
}
/**
* @param int $package_id
* @param WPML_TM_Count $word_count
*/
public function set_package_word_count( $package_id, WPML_TM_Count $word_count ) {
if ( $this->package_records ) {
$this->package_records->set_word_count( $package_id, $word_count->to_string() );
}
}
/**
* @param int $package_id
*
* @return WPML_TM_Count
*/
public function get_package_word_count( $package_id ) {
if ( $this->package_records ) {
return new WPML_TM_Count( $this->package_records->get_word_count( $package_id ) );
}
return new WPML_TM_Count();
}
/** @return int */
public function get_strings_total_words() {
if ( $this->string_records ) {
return $this->string_records->get_total_words();
}
return 0;
}
/** @return array */
public function get_all_string_values_without_word_count() {
if ( $this->string_records ) {
return $this->string_records->get_all_values_without_word_count();
}
return array();
}
/**
* @param string $lang
* @param int|null $package_id
*
* @return int
*/
public function get_string_words_to_translate_per_lang( $lang, $package_id = null ) {
if ( $this->string_records ) {
return $this->string_records->get_words_to_translate_per_lang( $lang, $package_id );
}
return 0;
}
public function get_string_value_and_language( $string_id ) {
if ( $this->string_records ) {
return $this->string_records->get_value_and_language( $string_id );
}
return (object) array(
'value' => null,
'language' => null,
);
}
/**
* @param int $id
* @param int $word_count
*/
public function set_string_word_count( $id, $word_count ) {
if ( $this->string_records ) {
$this->string_records->set_word_count( $id, $word_count );
}
}
/**
* @param int $id
*
* @return int
*/
public function get_string_word_count( $id ) {
if ( $this->string_records ) {
return $this->string_records->get_word_count( $id );
}
return 0;
}
public function reset_all( array $requested_types ) {
if ( $this->package_records && isset( $requested_types['package_kinds'] ) ) {
$this->package_records->reset_all( $requested_types['package_kinds'] );
}
if ( isset( $requested_types['post_types'] ) ) {
$this->post_records->reset_all( $requested_types['post_types'] );
}
}
/**
* @param array $kinds
*
* @return array
*/
public function get_package_ids_from_kind_slugs( array $kinds ) {
if ( $this->package_records ) {
return $this->package_records->get_ids_from_kind_slugs( $kinds );
}
return array();
}
/**
* @param array $post_types
*
* @return array
*/
public function get_package_ids_from_post_types( array $post_types ) {
if ( $this->package_records ) {
return $this->package_records->get_ids_from_post_types( $post_types );
}
return array();
}
/**
* @param array $package_ids
*
* @return array
*/
public function get_strings_ids_from_package_ids( array $package_ids ) {
if ( $this->string_records ) {
return $this->string_records->get_ids_from_package_ids( $package_ids );
}
return array();
}
/**
* @param array $package_ids
*
* @return array
*/
public function get_post_source_ids_from_types( array $package_ids ) {
return $this->post_records->get_source_ids_from_types( $package_ids );
}
/**
* @param string $type
*
* @return int
*/
public function count_items_by_type( $group, $type ) {
if ( $this->package_records && 'package_kinds' === $group ) {
return $this->package_records->count_items_by_kind_not_part_of_posts( $type );
} elseif ( 'post_types' === $group ) {
return $this->post_records->count_source_items_by_type( $type );
}
return 0;
}
public function count_word_counts_by_type( $group, $type ) {
if ( $this->package_records && 'package_kinds' === $group ) {
return $this->package_records->count_word_counts_by_kind( $type );
} elseif ( 'post_types' === $group ) {
return $this->post_records->count_word_counts_by_type( $type );
}
return 0;
}
public function get_word_counts_by_type( $group, $type ) {
if ( $this->package_records && 'package_kinds' === $group ) {
$counts = $this->package_records->get_word_counts_by_kind( $type );
return $this->build_count_composite_from_raw_counts( $counts );
} elseif ( 'post_types' === $group ) {
$counts = $this->post_records->get_word_counts_by_type( $type );
return $this->build_count_composite_from_raw_counts( $counts );
}
return new WPML_TM_Count_Composite();
}
/**
* @param array $raw_counts
*
* @return WPML_TM_Count_Composite
*/
private function build_count_composite_from_raw_counts( array $raw_counts ) {
$count_composite = new WPML_TM_Count_Composite();
foreach ( $raw_counts as $raw_count ) {
$count = new WPML_TM_Count( $raw_count );
$count_composite->add_count( $count );
}
return $count_composite;
}
}

View File

@@ -0,0 +1,44 @@
<?php
class WPML_TM_Word_Count_Report_View {
const TEMPLATE_PATH = '/templates/words-count';
const TEMPLATE_FILE = 'report.twig';
/** @var WPML_Twig_Template_Loader $loader */
private $loader;
/** @var WPML_WP_Cron_Check $cron_check */
private $cron_check;
public function __construct( WPML_Twig_Template_Loader $loader, WPML_WP_Cron_Check $cron_check ) {
$this->loader = $loader;
$this->cron_check = $cron_check;
}
public function show( array $model ) {
$model['strings'] = self::get_strings();
$model['cron_is_on'] = $this->cron_check->verify();
return $this->loader->get_template()->show( $model, self::TEMPLATE_FILE );
}
public static function get_strings() {
return array(
'contentType' => __( 'Type', 'wpml-translation-management' ),
'itemsCount' => __( 'Items', 'wpml-translation-management' ),
'wordCount' => __( 'Words', 'wpml-translation-management' ),
'estimatedTime' => __( 'Count time', 'wpml-translation-management' ),
'total' => __( 'Total', 'wpml-translation-management' ),
'recalculate' => __( 'Recalculate', 'wpml-translation-management' ),
'cancel' => __( 'Cancel', 'wpml-translation-management' ),
'needsRefresh' => __( 'Needs refresh - Some items of this type are not counted', 'wpml-translation-management' ),
'inMinute' => __( '%d minute', 'wpml-translation-management' ),
'inMinutes' => __( '%d minutes', 'wpml-translation-management' ),
'cronWarning' => __( 'We detected a possible issue blocking the word count process. Please verify the following settings:', 'wpml-translation-management' ),
'cronTips' => array(
__( 'Your site should be publicly accessible or the server should have access to the site.', 'wpml-translation-management' ),
__( 'The constant DISABLE_WP_CRON should not be set to true.', 'wpml-translation-management' ),
),
);
}
}

View File

@@ -0,0 +1,209 @@
<?php
class WPML_TM_Word_Count_Report {
const OPTION_KEY = 'wpml_word_count_report';
const POSTS_PER_MINUTE = 1200;
const PACKAGES_PER_MINUTE = 5000;
const POST_TYPES = 'post_types';
const PACKAGE_KINDS = 'package_kinds';
const IS_REQUESTED = 'isRequested';
/** @var WPML_TM_Word_Count_Records $records */
private $records;
/** @var WPML_TM_Word_Count_Report_View $view */
private $view;
/** @var SitePress $sitepress */
private $sitepress;
/** @var array $post_types */
private $post_types;
/** @var WPML_Package_Helper $st_package_helper */
private $st_package_helper;
/** @var array $package_kinds */
private $package_kinds = array();
/** @var bool $requested_types_status */
private $requested_types_status;
/** @var array $data */
private $data;
/**
* WPML_TM_Word_Count_Report constructor.
*
* @param WPML_TM_Word_Count_Report_View $view
* @param WPML_TM_Word_Count_Records $records
* @param SitePress $sitepress
* @param string|false $requested_types_status
* @param WPML_Package_Helper|null $st_package_helper
*/
public function __construct(
WPML_TM_Word_Count_Report_View $view,
WPML_TM_Word_Count_Records $records,
SitePress $sitepress,
$requested_types_status,
WPML_Package_Helper $st_package_helper = null
) {
$this->view = $view;
$this->records = $records;
$this->sitepress = $sitepress;
$this->st_package_helper = $st_package_helper;
$this->requested_types_status = $requested_types_status;
}
/**
* @return string
*/
public function render() {
$this->init_data();
$data = array(
self::POST_TYPES => array(),
self::PACKAGE_KINDS => array(),
);
foreach ( $this->get_post_types() as $post_type_ ) {
$data[ self::POST_TYPES ][ $post_type_->name ] = $this->build_type_row( self::POST_TYPES, $post_type_ );
}
foreach ( $this->get_package_kinds() as $package_kind ) {
$data[ self::PACKAGE_KINDS ][ $package_kind->name ] = $this->build_type_row( self::PACKAGE_KINDS, $package_kind );
}
$this->data = $data;
$this->save_data();
$model = array(
'countInProgress' => $this->requested_types_status === WPML_TM_Word_Count_Hooks_Factory::PROCESS_IN_PROGRESS,
'data' => $this->data,
'totals' => $this->get_totals(),
);
return $this->view->show( $model );
}
private function is_requested( $group, $type ) {
return isset( $this->data[ $group ][ $type ][ self::IS_REQUESTED ] )
&& $this->data[ $group ][ $type ][ self::IS_REQUESTED ];
}
/**
* @param string $group
* @param WP_Post_Type|stdClass $type_object
*
* @return array|null
*/
private function build_type_row( $group, $type_object ) {
$count_items = $this->records->count_items_by_type( $group, $type_object->name );
// Do not include in the report if it has no item
if ( ! $count_items ) {
return null;
}
$count_words = '';
$status = WPML_TM_Word_Count_Hooks_Factory::PROCESS_PENDING;
$completed_items = $this->records->count_word_counts_by_type( $group, $type_object->name );
if ( $this->is_requested( $group, $type_object->name ) ) {
$status = $completed_items < $count_items
? WPML_TM_Word_Count_Hooks_Factory::PROCESS_IN_PROGRESS
: WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED;
$count_words = $this->records->get_word_counts_by_type( $group, $type_object->name )->get_total_words();
} elseif ( ! empty( $this->data[ $group ][ $type_object->name ]['countWords'] ) ) {
$status = WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED;
$count_words = $this->data[ $group ][ $type_object->name ]['countWords'];
}
$label = $type_object->label;
$items_per_minute = self::POSTS_PER_MINUTE;
if ( self::PACKAGE_KINDS === $group ) {
$items_per_minute = self::PACKAGES_PER_MINUTE;
}
return array(
'group' => $group,
'type' => $type_object->name,
'typeLabel' => $label,
'countItems' => $count_items,
'completedItems' => $completed_items,
'countWords' => $count_words,
'estimatedTime' => ceil( $count_items / $items_per_minute ),
'status' => $status,
'needsRefresh' => $completed_items < $count_items,
'isRequested' => $this->is_requested( $group, $type_object->name ),
);
}
private function get_totals() {
$totals = array(
'completedItems' => 0,
'countItems' => 0,
'countWords' => 0,
'estimatedTime' => 0,
'requestedTypes' => 0,
);
foreach ( $this->data as $group ) {
foreach ( $group as $type ) {
$totals['completedItems'] += (int) $type['completedItems'];
$totals['countItems'] += (int) $type['countItems'];
$totals['countWords'] += (int) $type['countWords'];
$totals['estimatedTime'] += (int) $type['estimatedTime'];
$totals['requestedTypes'] += (int) $type['isRequested'];
}
}
return $totals;
}
public function set_requested_types( array $requested_types ) {
$this->init_data();
$this->set_requested_group( self::POST_TYPES, $this->get_post_types(), $requested_types );
$this->set_requested_group( self::PACKAGE_KINDS, $this->get_package_kinds(), $requested_types );
$this->save_data();
}
private function set_requested_group( $group, $types, $requested_types ) {
foreach ( $types as $type ) {
if ( false === array_search( $type->name, (array) $requested_types[ $group ], true ) ) {
$this->data[ $group ][ $type->name ][ self::IS_REQUESTED ] = false;
} else {
$this->data[ $group ][ $type->name ][ self::IS_REQUESTED ] = true;
}
}
}
private function init_data() {
$this->data = get_option( self::OPTION_KEY, array() );
}
private function save_data() {
$this->data[ self::POST_TYPES ] = array_filter( $this->data[ self::POST_TYPES ] );
$this->data[ self::PACKAGE_KINDS ] = array_filter( $this->data[ self::PACKAGE_KINDS ] );
update_option( self::OPTION_KEY, $this->data, false );
}
private function get_post_types() {
if ( ! $this->post_types ) {
$this->post_types = $this->sitepress->get_translatable_documents();
}
return $this->post_types;
}
public function get_package_kinds() {
if ( $this->st_package_helper && ! $this->package_kinds ) {
$this->package_kinds = $this->st_package_helper->get_translatable_types( array() );
}
return $this->package_kinds;
}
}

View File

@@ -0,0 +1,56 @@
<?php
class WPML_TM_Package_Element extends WPML_TM_Translatable_Element {
/** @var WPML_ST_Package_Factory $st_package_factory */
private $st_package_factory;
/** @var WPML_Package $st_package */
private $st_package;
/**
* @param int $id
* @param WPML_TM_Word_Count_Records $word_count_records
* @param WPML_TM_Word_Count_Single_Process $single_process
* @param WPML_ST_Package_Factory|null $st_package_factory
*/
public function __construct(
$id,
WPML_TM_Word_Count_Records $word_count_records,
WPML_TM_Word_Count_Single_Process $single_process,
WPML_ST_Package_Factory $st_package_factory = null
) {
$this->st_package_factory = $st_package_factory;
parent::__construct( $id, $word_count_records, $single_process );
}
/** @param int $id */
protected function init( $id ) {
if ( $this->st_package_factory ) {
$this->st_package = $this->st_package_factory->create( $id );
}
}
protected function get_type() {
return 'package';
}
/** @return int */
protected function get_total_words() {
return $this->word_count_records->get_package_word_count( $this->id )->get_total_words();
}
/**
* @param null $label
*
* @return string
*/
public function get_type_name( $label = null ) {
if ( $this->st_package ) {
return $this->st_package->kind;
}
return __( 'Unknown string Package', 'wpml-translation-management' );
}
}