PluginProbe ʕ •ᴥ•ʔ
WPML Multilingual & Multicurrency for WooCommerce / 5.5.7
WPML Multilingual & Multicurrency for WooCommerce v5.5.7
5.5.7 5.3.8 5.3.9 5.4.3 5.4.4 5.4.5 5.5.1 5.5.1.1 5.5.2.2 5.5.2.3 5.5.3 5.5.3.1 5.5.4 5.5.5 5.5.6 trunk 0.9 1.0 1.1 1.2 1.3 1.4 1.5 2.0 2.2 2.3 2.3.1 2.3.2 3.0 3.0.1 3.1 3.2 3.2.1 3.2.2 3.3 3.3.1 3.3.2 3.3.3 3.3.4 3.4 3.4.1 3.4.2 3.4.3 3.5 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.6 3.6.1 3.6.10 3.6.11 3.6.2 3.6.3 3.6.4 3.6.5 3.6.5.1 3.6.6 3.6.7 3.6.8 3.6.9 3.7 3.7.1 3.7.10 3.7.11 3.7.12 3.7.13 3.7.14 3.7.15 3.7.16 3.7.2 3.7.3 3.7.4 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.9.0 3.9.1 3.9.1.1 3.9.2 3.9.3 3.9.4 3.9.5 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.10.0 4.10.1 4.10.2 4.10.3 4.10.4 4.11.2 4.11.3.2 4.11.5 4.11.6 4.12.1 4.12.4 4.12.5 4.12.6 4.2.0 4.2.0.1 4.2.1 4.2.1.1 4.2.10 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.7.1 4.2.8 4.2.8.1 4.2.9 4.3.0 4.3.1 4.3.2 4.3.2.1 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.4.0 4.4.1 4.4.2 4.4.2.1 4.5.0 4.6.0 4.6.1 4.6.2 4.6.2.1 4.6.3 4.6.5 4.6.6 4.6.7 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.7.5 4.7.6 4.7.7 4.7.8 4.7.9 4.8.0 4.9.0 4.9.1 5.0.1 5.0.2 5.1.1 5.1.2 5.1.3 5.2.0 5.2.1 5.3.2 5.3.3.1 5.3.4 5.3.5 5.3.6 5.3.7
woocommerce-multilingual / inc / class-wcml-comments.php
woocommerce-multilingual / inc Last commit date
admin-menus 1 week ago currencies 1 week ago template-classes 1 week ago translation-editor 1 week ago class-wcml-ajax-setup.php 1 week ago class-wcml-attributes.php 1 week ago class-wcml-capabilities.php 1 week ago class-wcml-cart-removed-items-widget.php 1 year ago class-wcml-cart-switch-lang-functions.php 1 week ago class-wcml-cart-sync-warnings.php 1 week ago class-wcml-cart.php 1 week ago class-wcml-comments.php 1 week ago class-wcml-compatibility.php 1 week ago class-wcml-coupons.php 1 week ago class-wcml-dependencies.php 1 week ago class-wcml-emails.php 1 week ago class-wcml-endpoints.php 1 week ago class-wcml-install.php 1 week ago class-wcml-languages-upgrader.php 1 week ago class-wcml-locale.php 1 week ago class-wcml-orders.php 1 week ago class-wcml-products-screen-options.php 1 week ago class-wcml-products.php 1 week ago class-wcml-reports.php 1 week ago class-wcml-requests.php 1 week ago class-wcml-resources.php 1 week ago class-wcml-store-pages.php 1 week ago class-wcml-terms.php 1 week ago class-wcml-tp-support.php 1 week ago class-wcml-troubleshooting.php 1 week ago class-wcml-upgrade.php 1 week ago class-wcml-url-translation.php 1 week ago class-wcml-wc-gateways.php 1 week ago class-wcml-wc-shipping.php 1 week ago class-wcml-wc-strings.php 1 week ago class-wcml-widgets.php 1 week ago constants.php 1 week ago functions-pure.php 1 week ago functions.php 1 week ago installer-loader.php 1 week ago missing-php-functions.php 1 week ago wcml-core-functions.php 1 week ago wcml-switch-lang-request.php 1 week ago
class-wcml-comments.php
447 lines
1 <?php
2
3 use WCML\Utilities\DB;
4 use WPML\FP\Fns;
5 use WPML\FP\Obj;
6 use WPML\Convert\Ids;
7
8 class WCML_Comments {
9
10 const WCML_AVERAGE_RATING_KEY = '_wcml_average_rating';
11 const WCML_RATING_COUNT_KEY = '_wcml_rating_count';
12 const WCML_REVIEW_COUNT_KEY = '_wcml_review_count';
13 const WC_AVERAGE_RATING_KEY = '_wc_average_rating';
14 const WC_RATING_COUNT_KEY = '_wc_rating_count';
15 const WC_REVIEW_COUNT_KEY = '_wc_review_count';
16 const COMMENT_TYPE_REVIEW = 'review';
17
18 private $woocommerce_wpml;
19 private $sitepress;
20 private $post_translations;
21 private $wpdb;
22
23 public function __construct( woocommerce_wpml $woocommerce_wpml, SitePress $sitepress, WPML_Post_Translation $post_translations, wpdb $wpdb ) {
24 $this->woocommerce_wpml = $woocommerce_wpml;
25 $this->sitepress = $sitepress;
26 $this->post_translations = $post_translations;
27 $this->wpdb = $wpdb;
28 }
29
30 public function add_hooks() {
31
32 add_action( 'wp_insert_comment', [ $this, 'add_comment_rating' ] );
33 add_action( 'woocommerce_review_before_comment_meta', [ $this, 'add_comment_flag' ], 9 );
34 add_action( 'woocommerce_review_before_comment_text', [ $this, 'open_lang_div' ] );
35 add_action( 'woocommerce_review_after_comment_text', [ $this, 'close_lang_div' ] );
36
37 add_action( 'added_comment_meta', [ $this, 'maybe_duplicate_comment_rating' ], 10, 4 );
38
39 add_filter( 'get_post_metadata', [ $this, 'filter_average_rating' ], 10, 4 );
40 add_filter( 'comments_clauses', [ $this, 'comments_clauses' ], 10, 2 );
41 add_action( 'comment_form_before', [ $this, 'comments_link' ] );
42
43 add_filter( 'wpml_is_comment_query_filtered', [ $this, 'is_comment_query_filtered' ], 10, 3 );
44 add_action( 'trashed_comment', [ $this, 'recalculate_average_rating_on_comment_hook' ], 10, 2 );
45 add_action( 'deleted_comment', [ $this, 'recalculate_average_rating_on_comment_hook' ], 10, 2 );
46 add_action( 'untrashed_comment', [ $this, 'recalculate_average_rating_on_comment_hook' ], 10, 2 );
47 add_action(
48 'woocommerce_product_set_visibility',
49 Fns::withoutRecursion( Fns::noop(), [ $this, 'recalculate_comment_rating' ] ),
50 9
51 );
52
53 if ( ! defined( 'WPSEO_VERSION' )
54 && 'all' === WPML\FP\Obj::prop( 'clang', $_GET )
55 && ! $this->is_reviews_in_all_languages_by_default_selected()
56 ) {
57 add_action( 'wp_head', [ $this, 'no_index_all_reviews_page' ], 10 );
58 }
59
60 add_filter( 'woocommerce_top_rated_products_widget_args', [ $this, 'top_rated_products_widget_args' ] );
61 add_filter( 'woocommerce_rating_filter_count', [ $this, 'woocommerce_rating_filter_count' ], 10, 3 );
62
63 add_filter( 'the_comments', [ $this, 'translate_product_ids' ] );
64
65 add_filter( 'wpml_skip_comment_duplication', [ $this, 'skip_review_duplication' ], 10, 3 );
66
67 add_action( 'pre_get_comments', [ $this, 'maybe_partition_comment_cache' ] );
68 }
69
70 public function maybe_partition_comment_cache( $query ) {
71 if ( empty( $query->query_vars['post_id'] ) ) {
72 return;
73 }
74
75 $post_id = (int) $query->query_vars['post_id'];
76 $post_type = Obj::path( [ 'query_vars', 'post_type' ], $query );
77
78 $is_post_type_from_id = false;
79 if ( ! $post_type && $post_id ) {
80 $post_type = get_post_type( $post_id );
81 $is_post_type_from_id = true;
82 }
83
84 if ( 'product' !== $post_type ) {
85 return;
86 }
87
88 if (
89 ( $is_post_type_from_id && 'product' !== $post_type )
90 ||
91 ( 'product' !== get_post_type( $post_id ) )
92 ) {
93 return;
94 }
95
96 if ( ! $this->is_reviews_in_all_languages( $post_id, $query ) ) {
97 return;
98 }
99
100 $translation_ids = $this->get_translations_ids( $post_id );
101 sort( $translation_ids );
102
103 $query->query_vars['cache_domain'] = 'wcml_product_reviews_all:' . md5( implode( ',', $translation_ids ) );
104 }
105
106 public function add_comment_rating( $comment_id ) {
107
108 if ( isset( $_POST['comment_post_ID'] ) ) {
109
110 $product_id = sanitize_text_field( $_POST['comment_post_ID'] );
111
112 if ( 'product' === get_post_type( $product_id ) ) {
113
114 $this->recalculate_comment_rating( $product_id );
115 }
116 }
117 }
118
119 public function recalculate_comment_rating( $product_id ) {
120
121 $translations = $this->post_translations->get_element_translations( $product_id );
122 $average_ratings_sum = 0;
123 $average_ratings_count = 0;
124 $reviews_count = 0;
125 $ratings_count = [];
126
127 foreach ( $translations as $translation ) {
128 $product = wc_get_product( $translation );
129
130 if ( ! $product ) {
131 continue;
132 }
133
134 $ratings = WC_Comments::get_rating_counts_for_product( $product );
135 $review_count = WC_Comments::get_review_count_for_product( $product );
136
137 if ( is_array( $ratings ) ) {
138 foreach ( $ratings as $rating => $count ) {
139 $average_ratings_sum += $rating * $count;
140 $average_ratings_count += $count;
141 if ( isset( $ratings_count[ $rating ] ) ) {
142 $ratings_count[ $rating ] += $count;
143 } else {
144 $ratings_count[ $rating ] = $count;
145 }
146 }
147 }
148
149 if ( $review_count ) {
150 $reviews_count += $review_count;
151 } else {
152 update_post_meta( $translation, self::WCML_AVERAGE_RATING_KEY, null );
153 update_post_meta( $translation, self::WCML_REVIEW_COUNT_KEY, null );
154 update_post_meta( $translation, self::WCML_RATING_COUNT_KEY, null );
155 }
156 }
157
158 if ( $average_ratings_sum ) {
159
160 $average_rating = number_format( $average_ratings_sum / $average_ratings_count, 2, '.', '' );
161
162 foreach ( $translations as $translation ) {
163 update_post_meta( $translation, self::WCML_AVERAGE_RATING_KEY, $average_rating );
164 update_post_meta( $translation, self::WCML_REVIEW_COUNT_KEY, $reviews_count );
165 update_post_meta( $translation, self::WCML_RATING_COUNT_KEY, $ratings_count );
166
167 WC_Comments::clear_transients( $translation );
168 }
169 }
170
171 }
172
173 public function filter_average_rating( $value, $object_id, $meta_key, $single ) {
174
175 $filtered_value = $value;
176
177 if ( in_array( $meta_key, [ self::WC_AVERAGE_RATING_KEY, self::WC_REVIEW_COUNT_KEY, self::WC_RATING_COUNT_KEY ], true ) && 'product' === get_post_type( $object_id ) ) {
178
179 if ( ! metadata_exists( 'post', $object_id, self::WCML_RATING_COUNT_KEY ) ) {
180 $this->recalculate_comment_rating( $object_id );
181 }
182
183 switch ( $meta_key ) {
184 case self::WC_AVERAGE_RATING_KEY:
185 $filtered_value = get_post_meta( $object_id, self::WCML_AVERAGE_RATING_KEY, $single );
186 if ( empty( $filtered_value ) ) {
187 $filtered_value = 0;
188 }
189 break;
190 case self::WC_REVIEW_COUNT_KEY:
191 if ( $this->is_reviews_in_all_languages( $object_id ) ) {
192 $filtered_value = get_post_meta( $object_id, self::WCML_REVIEW_COUNT_KEY, $single );
193 }
194 break;
195 case self::WC_RATING_COUNT_KEY:
196 $filtered_value = get_post_meta( $object_id, self::WCML_RATING_COUNT_KEY, $single );
197 if ( $single ) {
198 $filtered_value = [ $filtered_value ];
199 }
200 break;
201 }
202 }
203
204 return ! empty( $filtered_value ) || $filtered_value === 0 ? $filtered_value : $value;
205 }
206
207 public function comments_clauses( $clauses, $obj ) {
208
209 if ( $this->is_reviews_in_all_languages( $obj->query_vars['post_id'] ) ) {
210 $ids = $this->get_translations_ids( $obj->query_vars['post_id'] );
211
212 $clauses['where'] = str_replace( 'comment_post_ID = ' . $obj->query_vars['post_id'], 'comment_post_ID IN (' . DB::prepareIn( $ids, '%d' ) . ')', $clauses['where'] );
213 }
214
215 return $clauses;
216 }
217
218 private function get_translations_ids( $product_id ) {
219
220 $translations = $this->post_translations->get_element_translations( $product_id );
221
222 return array_filter( $translations );
223
224 }
225
226 public function comments_link() {
227
228 if ( is_product() ) {
229 if ( $this->is_reviews_in_all_languages( get_the_ID() ) ) {
230 $this->show_link_to_current_language_reviews();
231 } else {
232 $this->show_link_to_all_reviews();
233 }
234 }
235 }
236
237 private function is_reviews_in_all_languages_by_default_selected() {
238 return (bool) $this->woocommerce_wpml->get_setting( 'reviews_in_all_languages', false );
239 }
240
241 private function show_link_to_all_reviews() {
242 $comments_link = add_query_arg( [ 'clang' => 'all' ] );
243 $all_languages_reviews_count = $this->get_reviews_count( 'all' );
244 $current_language_reviews_count = $this->get_reviews_count();
245
246 if ( $all_languages_reviews_count > $current_language_reviews_count ) {
247 /* translators: %s is the number of reviews */
248 $comments_link_text = sprintf( __( 'Show reviews in all languages (%s)', 'woocommerce-multilingual' ), $all_languages_reviews_count );
249 echo '<p><a id="lang-comments-link" href="' . esc_url( $comments_link ) . '" rel="nofollow" class="all-languages-reviews" >' . esc_html( $comments_link_text ) . '</a></p>';
250 }
251 }
252
253 private function show_link_to_current_language_reviews() {
254
255 $current_language_reviews_count = $this->get_reviews_count();
256 $current_language = $this->sitepress->get_current_language();
257
258 $comments_link = add_query_arg( [ 'clang' => $current_language ] );
259 $language_details = $this->sitepress->get_language_details( $current_language );
260 /* translators: %1$s is a language name and %2$s is the number of reviews */
261 $comments_link_text = sprintf( __( 'Show only reviews in %1$s (%2$s)', 'woocommerce-multilingual' ), $language_details['display_name'], $current_language_reviews_count );
262
263 echo '<p><a id="lang-comments-link" href="' . esc_url( $comments_link ) . '" rel="nofollow" class="current-language-reviews" >' . esc_html( $comments_link_text ) . '</a></p>';
264
265 }
266
267 public function is_comment_query_filtered( $filtered, $post_id, $comment_query = null ) {
268
269 if ( $this->is_reviews_in_all_languages( $post_id, $comment_query ) ) {
270 $filtered = false;
271 }
272
273 return $filtered;
274 }
275
276 public function add_comment_flag( $comment ) {
277 $comment_language = $this->get_comment_language_on_all_languages_reviews( $comment );
278 if ( $comment_language ) {
279 printf(
280 '<div style="float: left; padding: 6px 5px 0 0;"><img src="%s" width="18" height="12" alt="%s"></div>',
281 esc_url( $this->sitepress->get_flag_url( $comment_language ) ),
282 esc_attr( $this->sitepress->get_display_language_name( $comment_language ) )
283 );
284 }
285 }
286
287 public function open_lang_div( $comment ) {
288 $comment_language = $this->get_comment_language_on_all_languages_reviews( $comment );
289 if ( $comment_language ) {
290 printf( '<div lang="%s">', esc_attr( $comment_language ) );
291
292 if ( self::is_translated( $comment ) ) {
293 echo '<span class="wcml-review-translated">(' . esc_html__( 'translated', 'woocommerce-multilingual' ) . ')</span>';
294 }
295 }
296 }
297
298 public function close_lang_div( $comment ) {
299 if ( $this->get_comment_language_on_all_languages_reviews( $comment ) ) {
300 print( '</div>' );
301 }
302 }
303
304 private function get_comment_language_on_all_languages_reviews( $comment ) {
305 if ( self::is_translated( $comment ) ) {
306 return $this->sitepress->get_current_language();
307 }
308
309 $commentPostId = self::getOriginalPostId( $comment );
310 if ( $this->is_reviews_in_all_languages( $commentPostId ) ) {
311 return $this->post_translations->get_element_lang_code( $commentPostId );
312 }
313
314 return null;
315 }
316
317 public function is_reviews_in_all_languages( $product_id, $comment_query = null ) {
318 $reviewsLang = Obj::prop( 'clang', $_GET );
319 $post_type = Obj::path( [ 'query_vars', 'post_type' ], $comment_query );
320
321 if ( ! $post_type && $product_id ) {
322 $post_type = get_post_type( $product_id );
323 }
324
325 return (
326 'all' === $reviewsLang
327 || ( ! $reviewsLang && $this->is_reviews_in_all_languages_by_default_selected() )
328 ) && 'product' === $post_type;
329 }
330
331 public function get_reviews_count( $language = false ) {
332
333 remove_filter( 'get_post_metadata', [ $this, 'filter_average_rating' ], 10 );
334
335 if ( ! metadata_exists( 'post', get_the_ID(), self::WCML_REVIEW_COUNT_KEY ) ) {
336 $this->recalculate_comment_rating( get_the_ID() );
337 }
338
339 if ( 'all' === $language ) {
340 $reviews_count = get_post_meta( get_the_ID(), self::WCML_REVIEW_COUNT_KEY, true );
341 } else {
342 $reviews_count = get_post_meta( get_the_ID(), self::WC_REVIEW_COUNT_KEY, true );
343 }
344
345 add_filter( 'get_post_metadata', [ $this, 'filter_average_rating' ], 10, 4 );
346
347 return $reviews_count;
348 }
349
350 public function recalculate_average_rating_on_comment_hook( $comment_id, $comment ) {
351
352 if ( ! $comment ) {
353 $comment = get_comment( $comment_id );
354 }
355
356 if ( in_array( get_post_type( $comment->comment_post_ID ), [ 'product', 'product_variation' ] ) ) {
357 $this->recalculate_comment_rating( (int) $comment->comment_post_ID );
358 }
359 }
360
361 public function top_rated_products_widget_args( $args ) {
362 $args['meta_key'] = self::WCML_AVERAGE_RATING_KEY;
363
364 return $args;
365 }
366
367 public function woocommerce_rating_filter_count( $label, $count, $rating ) {
368
369 $ratingTerm = get_term_by( 'name', 'rated-' . $rating, 'product_visibility' );
370
371 $productsCountInCurrentLanguage = $this->wpdb->get_var( $this->wpdb->prepare( "
372 SELECT COUNT( DISTINCT tr.object_id )
373 FROM %s tr
374 LEFT JOIN %s t ON t.element_id = tr.object_id
375 WHERE tr.term_taxonomy_id = %d AND t.element_type='post_product' AND t.language_code = %s
376 ", $this->wpdb->term_relationships, $this->wpdb->prefix . 'icl_translations', $ratingTerm->term_taxonomy_id, $this->sitepress->get_current_language() ) );
377
378 return "({$productsCountInCurrentLanguage})";
379 }
380
381 public function maybe_duplicate_comment_rating( $meta_id, $comment_id, $meta_key, $meta_value ) {
382 if ( 'rating' === $meta_key && wpml_get_setting_filter( null, 'sync_comments_on_duplicates' ) ) {
383 remove_action( 'added_comment_meta', [ $this, 'maybe_duplicate_comment_rating' ], 10 );
384 foreach ( $this->get_duplicated_comments( $comment_id ) as $duplicate ) {
385 add_comment_meta( $duplicate, 'rating', $meta_value );
386
387 }
388 $product_id = get_comment( $comment_id )->comment_post_ID;
389 $this->recalculate_comment_rating( $product_id );
390 add_action( 'added_comment_meta', [ $this, 'maybe_duplicate_comment_rating' ], 10, 4 );
391 }
392 }
393
394 private function get_duplicated_comments( $comment_id ) {
395 return $this->wpdb->get_col(
396 $this->wpdb->prepare(
397 "SELECT comment_id
398 FROM {$this->wpdb->commentmeta}
399 WHERE meta_key = '_icl_duplicate_of'
400 AND meta_value = %d", $comment_id
401 )
402 );
403 }
404
405 public function no_index_all_reviews_page() {
406 echo '<meta name="robots" content="noindex">';
407 }
408
409 public function translate_product_ids( $comments ) {
410 $convertProductId = function( $comment ) {
411 if ( self::COMMENT_TYPE_REVIEW === Obj::prop( 'comment_type', $comment ) ) {
412 $comment->wcml_default_comment_post_ID = Obj::prop( 'comment_post_ID', $comment );
413
414 $comment = Obj::assoc(
415 'comment_post_ID',
416 Ids::convert( Obj::prop( 'comment_post_ID', $comment ), 'product', true ),
417 $comment
418 );
419 }
420
421 return $comment;
422 };
423
424 return wpml_collect( $comments )
425 ->map( $convertProductId )
426 ->toArray();
427 }
428
429 public static function is_translated( $comment ) {
430 return (bool) Obj::prop( 'is_translated', $comment );
431 }
432
433 public function skip_review_duplication( $skip, $comment_id, $comment ) {
434 if ( isset( $comment['comment_type'] ) && 'review' === $comment['comment_type'] ) {
435 if ( $this->is_reviews_in_all_languages_by_default_selected() ) {
436 return true;
437 }
438 }
439 return $skip;
440 }
441
442 public static function getOriginalPostId( $comment ) {
443 return Obj::prop( 'wcml_default_comment_post_ID', $comment )
444 ?: Obj::prop( 'comment_post_ID', $comment );
445 }
446 }
447