ProductController   F
last analyzed

Complexity

Total Complexity 71

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 71
eloc 201
dl 0
loc 490
ccs 0
cts 230
cp 0
rs 2.7199
c 1
b 1
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A option() 0 14 3
A renderBulkEditField() 0 4 3
A printInlineStyle() 0 12 1
A modifyProductQuery() 0 21 5
A filterGetRatingHtml() 0 12 2
A updateProductData() 0 21 5
A filterStructuredData() 0 15 2
A renderReviews() 0 12 4
A filterWidgetArgsTopRatedProducts() 0 12 2
A filterProductAverageRating() 0 4 1
A registerMetaBoxes() 0 3 1
A filterProductDataTabs() 0 10 1
A setMetaQueriesForFilteredRatings() 0 16 3
A filterProductTaxQuery() 0 20 5
A filterGetStarRatingHtml() 0 4 1
A buildMetaQuery() 0 6 1
A renderLoopRating() 0 15 4
A updateProductRatingCounts() 0 8 2
A renderQuickEditField() 0 4 3
A renderProductDataPanel() 0 5 1
A renderTitleRating() 0 12 3
A filterProductReviewCount() 0 4 1
A filterCommentsTemplate() 0 6 3
A filterProductRatingCounts() 0 3 1
A filterProductMetaQuery() 0 20 5
A filterProductTabs() 0 12 3
A filterProductPostClauses() 0 7 2
A filterWoocommerceTemplate() 0 9 3

How to fix   Complexity   

Complex Class

Complex classes like ProductController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ProductController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Integrations\WooCommerce\Controllers;
4
5
use GeminiLabs\SiteReviews\Arguments;
6
use GeminiLabs\SiteReviews\Contracts\ControllerContract;
7
use GeminiLabs\SiteReviews\Database\CountManager;
8
use GeminiLabs\SiteReviews\Defaults\SiteReviewsDefaults;
9
use GeminiLabs\SiteReviews\Helpers\Arr;
10
use GeminiLabs\SiteReviews\Helpers\Cast;
11
use GeminiLabs\SiteReviews\Helpers\Str;
12
use GeminiLabs\SiteReviews\Helpers\Svg;
13
use GeminiLabs\SiteReviews\HookProxy;
14
use GeminiLabs\SiteReviews\Integrations\WooCommerce\Metaboxes\ReviewsMetabox;
15
use GeminiLabs\SiteReviews\Modules\Html\Builder;
16
use GeminiLabs\SiteReviews\Modules\Html\Template;
17
use GeminiLabs\SiteReviews\Modules\Rating;
18
use GeminiLabs\SiteReviews\Modules\Sanitizer;
19
use GeminiLabs\SiteReviews\Modules\Schema;
20
use GeminiLabs\SiteReviews\Modules\Style;
21
22
class ProductController implements ControllerContract
23
{
24
    use HookProxy;
25
26
    /**
27
     * @param string $template
28
     *
29
     * @filter comments_template
30
     */
31
    public function filterCommentsTemplate($template): string
32
    {
33
        if (current_theme_supports('woocommerce') && 'product' === get_post_type()) {
34
            return glsr()->path('views/integrations/woocommerce/overrides/single-product-reviews.php');
35
        }
36
        return Cast::toString($template);
37
    }
38
39
    /**
40
     * @param string $html
41
     * @param int    $rating
42
     * @param int    $count
43
     *
44
     * @filter woocommerce_product_get_rating_html
45
     */
46
    public function filterGetRatingHtml($html, $rating, $count): string
47
    {
48
        if (str_contains($html, 'wc-block')) {
49
            $html = str_replace('wc-block-grid__product-rating__stars', '', $html);
50
            return $html;
51
        }
52
        $starsHtml = glsr_star_rating($rating, $count, [
53
            'theme' => glsr_get_option('integrations.woocommerce.style'),
54
        ]);
55
        return glsr(Builder::class)->div([
56
            'class' => glsr(Style::class)->styleClasses(),
57
            'text' => $starsHtml,
58
        ]);
59
    }
60
61
    /**
62
     * @param string $html
63
     * @param int    $rating
64
     * @param int    $count
65
     *
66
     * @filter woocommerce_get_star_rating_html
67
     */
68
    public function filterGetStarRatingHtml($html, $rating, $count): string
69
    {
70
        return glsr_star_rating($rating, $count, [
71
            'theme' => glsr_get_option('integrations.woocommerce.style'),
72
        ]);
73
    }
74
75
    /**
76
     * @param mixed       $value
77
     * @param \WC_Product $product
78
     *
79
     * @filter woocommerce_product_get_average_rating
80
     */
81
    public function filterProductAverageRating($value, $product): float
82
    {
83
        // return glsr_get_ratings(['assigned_posts' => $product->get_id()])->average;
84
        return Cast::toFloat(get_post_meta($product->get_id(), CountManager::META_AVERAGE, true));
85
    }
86
87
    /**
88
     * @param array $tabs
89
     *
90
     * @filter woocommerce_product_data_tabs
91
     */
92
    public function filterProductDataTabs($tabs): array
93
    {
94
        $tabs = Arr::consolidate($tabs);
95
        $tabs[glsr()->id] = [
96
            'label' => glsr()->name,
97
            'target' => glsr()->id,
98
            'priority' => 100,
99
            'class' => [],
100
        ];
101
        return $tabs;
102
    }
103
104
    /**
105
     * @param array $metaQuery
106
     *
107
     * @filter woocommerce_product_query_meta_query
108
     */
109
    public function filterProductMetaQuery($metaQuery): array
110
    {
111
        global $wp_query;
112
        $metaQuery = Arr::consolidate($metaQuery);
113
        $orderby = filter_input(INPUT_GET, 'orderby');
114
        if (!$orderby && !is_search()) {
115
            $orderby = apply_filters('woocommerce_default_catalog_orderby', get_option('woocommerce_default_catalog_orderby'));
116
        }
117
        if ('rating' !== $orderby) {
118
            return $metaQuery;
119
        }
120
        if ('bayesian' === glsr_get_option('integrations.woocommerce.sorting')) {
121
            $metaQuery[] = $this->buildMetaQuery('glsr_ranking', CountManager::META_RANKING);
122
            $wp_query->set('orderby', ['glsr_ranking' => 'DESC']);
123
        } else {
124
            $metaQuery[] = $this->buildMetaQuery('glsr_average', CountManager::META_AVERAGE);
125
            $metaQuery[] = $this->buildMetaQuery('glsr_reviews', CountManager::META_REVIEWS);
126
            $wp_query->set('orderby', ['glsr_average' => 'DESC', 'glsr_reviews' => 'DESC']);
127
        }
128
        return $metaQuery;
129
    }
130
131
    /**
132
     * @param array  $args
133
     * @param string $orderby
134
     *
135
     * @filter woocommerce_get_catalog_ordering_args
136
     */
137
    public function filterProductPostClauses($args, $orderby): array
138
    {
139
        $args = Arr::consolidate($args);
140
        if ('rating' === $orderby) {
141
            remove_filter('posts_clauses', [WC()->query, 'order_by_rating_post_clauses']);
142
        }
143
        return $args;
144
    }
145
146
    /**
147
     * @param mixed       $value
148
     * @param \WC_Product $product
149
     *
150
     * @filter woocommerce_product_get_rating_counts
151
     */
152
    public function filterProductRatingCounts($value, $product): array
153
    {
154
        return glsr_get_ratings(['assigned_posts' => $product->get_id()])->ratings;
155
    }
156
157
    /**
158
     * @param mixed       $value
159
     * @param \WC_Product $product
160
     *
161
     * @filter woocommerce_product_get_review_count
162
     */
163
    public function filterProductReviewCount($value, $product): int
164
    {
165
        // return glsr_get_ratings(['assigned_posts' => $product->get_id()])->reviews;
166
        return Cast::toInt(get_post_meta($product->get_id(), CountManager::META_REVIEWS, true));
167
    }
168
169
    /**
170
     * @param array $tabs
171
     *
172
     * @filter woocommerce_product_tabs
173
     */
174
    public function filterProductTabs($tabs): array
175
    {
176
        global $product;
177
        $tabs = Arr::consolidate($tabs);
178
        if ($product instanceof \WC_Product && $product->get_reviews_allowed()) {
179
            $tabs['reviews'] = [
180
                'callback' => [$this, 'renderReviews'],
181
                'priority' => 30,
182
                'title' => sprintf(__('Reviews (%d)', 'site-reviews'), $product->get_review_count()),
183
            ];
184
        }
185
        return $tabs;
186
    }
187
188
    /**
189
     * @param array $taxQuery
190
     *
191
     * @filter woocommerce_product_query_tax_query
192
     */
193
    public function filterProductTaxQuery($taxQuery): array
194
    {
195
        $taxQuery = Arr::consolidate($taxQuery);
196
        foreach ($taxQuery as $key => $query) {
197
            if (!empty($query['rating_filter'])) {
198
                $filteredRatings = [];
199
                $field = Arr::get($query, 'field');
200
                $taxonomy = Arr::get($query, 'taxonomy');
201
                foreach (Arr::consolidate(Arr::get($query, 'terms')) as $value) {
202
                    $term = get_term_by($field, $value, $taxonomy);
203
                    $filteredRatings[] = Cast::toInt(Str::removePrefix(Arr::get($term, 'slug'), 'rated-'));
204
                }
205
                unset($taxQuery[$key]);
206
                break;
207
            }
208
        }
209
        if (!empty($filteredRatings)) {
210
            $this->setMetaQueriesForFilteredRatings($filteredRatings);
211
        }
212
        return $taxQuery;
213
    }
214
215
    /**
216
     * @param array       $markup
217
     * @param \WC_Product $product
218
     *
219
     * @filter woocommerce_structured_data_product
220
     */
221
    public function filterStructuredData($markup, $product): array
222
    {
223
        $args = glsr(SiteReviewsDefaults::class)->merge([
224
            'assigned_posts' => $product->get_id(),
225
            'display' => 5, // only get the latest 5 reviews
226
            'rating' => 1, // minimum rating
227
        ]);
228
        $markup = Arr::consolidate($markup);
229
        $schema = glsr(Schema::class)->build($args, glsr_get_reviews($args));
230
        if (array_key_exists('review', $schema)) {
231
            $markup['review'] = $schema['review'];
232
        } else {
233
            unset($markup['review']);
234
        }
235
        return $markup;
236
    }
237
238
    /**
239
     * @param array $args
240
     *
241
     * @filter woocommerce_top_rated_products_widget_args
242
     */
243
    public function filterWidgetArgsTopRatedProducts($args): array
244
    {
245
        $args = Arr::consolidate($args);
246
        if ('bayesian' === glsr_get_option('integrations.woocommerce.sorting')) {
247
            $args['meta_query'][] = $this->buildMetaQuery('glsr_ranking', CountManager::META_RANKING);
248
            $args['orderby'] = ['glsr_ranking' => 'DESC'];
249
        } else {
250
            $args['meta_query'][] = $this->buildMetaQuery('glsr_average', CountManager::META_AVERAGE);
251
            $args['meta_query'][] = $this->buildMetaQuery('glsr_reviews', CountManager::META_REVIEWS);
252
            $args['orderby'] = ['glsr_average' => 'DESC', 'glsr_reviews' => 'DESC'];
253
        }
254
        return $args;
255
    }
256
257
    /**
258
     * @param string $template
259
     * @param string $templateName
260
     *
261
     * @filter wc_get_template
262
     */
263
    public function filterWoocommerceTemplate($template, $templateName): string
264
    {
265
        if ('loop/rating.php' === $templateName) {
266
            return glsr()->path('views/integrations/woocommerce/overrides/loop-rating.php');
267
        }
268
        if ('single-product-reviews.php' === $templateName) {
269
            return glsr()->path('views/integrations/woocommerce/overrides/single-product-reviews.php');
270
        }
271
        return Cast::toString($template);
272
    }
273
274
    /**
275
     * @param \WP_Query $query
276
     *
277
     * @action pre_get_posts
278
     */
279
    public function modifyProductQuery($query): void
280
    {
281
        if (!is_a($query, 'Automattic\WooCommerce\Blocks\Utils\BlocksWpQuery')) {
282
            return;
283
        }
284
        if ('rating' !== $query->get('orderby')) {
285
            return;
286
        }
287
        $metaQuery = $query->get('meta_query');
288
        if (empty($metaQuery)) {
289
            $metaQuery = [];
290
        }
291
        if ('bayesian' === glsr_get_option('integrations.woocommerce.sorting')) {
292
            $metaQuery[] = $this->buildMetaQuery('glsr_ranking', CountManager::META_RANKING);
293
            $query->set('meta_query', $metaQuery);
294
            $query->set('orderby', ['glsr_ranking' => 'DESC']);
295
        } else {
296
            $metaQuery[] = $this->buildMetaQuery('glsr_average', CountManager::META_AVERAGE);
297
            $metaQuery[] = $this->buildMetaQuery('glsr_reviews', CountManager::META_REVIEWS);
298
            $query->set('meta_query', $metaQuery);
299
            $query->set('orderby', ['glsr_average' => 'DESC', 'glsr_reviews' => 'DESC']);
300
        }
301
    }
302
303
    /**
304
     * @action admin_head
305
     */
306
    public function printInlineStyle(): void
307
    {
308
        $icon = Svg::encoded('assets/images/icon-static.svg');
309
        echo '<style type="text/css">'.
310
            '#woocommerce-product-data ul.wc-tabs li.site-reviews_tab a::before {'.
311
                'background-color: currentColor;'.
312
                'mask-image: url("'.$icon.'");'.
313
                'mask-position: center;'.
314
                'mask-repeat: no-repeat;'.
315
                'mask-size: 1em;'.
316
            '}'.
317
        '</style>';
318
    }
319
320
    /**
321
     * @param \WP_Post $post
322
     *
323
     * @action add_meta_boxes_product
324
     */
325
    public function registerMetaBoxes($post): void
326
    {
327
        glsr(ReviewsMetabox::class)->register($post);
328
    }
329
330
    /**
331
     * @param string $columnName
332
     * @param string $postType
333
     *
334
     * @action bulk_edit_custom_box
335
     */
336
    public function renderBulkEditField($columnName, $postType): void
337
    {
338
        if ('price' === $columnName && 'product' === $postType) {
339
            glsr()->render('views/integrations/woocommerce/bulk-edit');
340
        }
341
    }
342
343
    /**
344
     * @action woocommerce_after_shop_loop_item_title
345
     */
346
    public function renderLoopRating(): void
347
    {
348
        global $product;
349
        if (!wc_review_ratings_enabled()) {
350
            return;
351
        }
352
        $ratings = glsr_get_ratings(['assigned_posts' => 'post_id']);
353
        if (0 >= $ratings->average && !glsr_get_option('integrations.woocommerce.display_empty', false, 'bool')) {
354
            return;
355
        }
356
        glsr(Template::class)->render('templates/woocommerce/loop/rating', [
357
            'product' => $product,
358
            'ratings' => $ratings,
359
            'style' => glsr(Style::class)->styleClasses(),
360
            'theme' => glsr_get_option('integrations.woocommerce.style'),
361
        ]);
362
    }
363
364
    /**
365
     * @action woocommerce_product_data_panels
366
     */
367
    public function renderProductDataPanel(): void
368
    {
369
        global $product_object;
370
        glsr(Template::class)->render('views/integrations/woocommerce/product-data-panel', [
371
            'product' => $product_object,
372
        ]);
373
    }
374
375
    /**
376
     * @param string $columnName
377
     * @param string $postType
378
     *
379
     * @action quick_edit_custom_box
380
     */
381
    public function renderQuickEditField($columnName, $postType): void
382
    {
383
        if ('price' === $columnName && 'product' === $postType) {
384
            glsr()->render('views/integrations/woocommerce/quick-edit');
385
        }
386
    }
387
388
    /**
389
     * @callback filterProductTabs
390
     */
391
    public function renderReviews(): void
392
    {
393
        global $product;
394
        if ($product instanceof \WC_Product && $product->get_reviews_allowed()) {
395
            $isVerifiedOwner = wc_customer_bought_product('', get_current_user_id(), $product->get_id());
396
            glsr(Template::class)->render('templates/woocommerce/reviews', [
397
                'form' => do_shortcode($this->option($product, 'form')),
398
                'product' => $product,
399
                'ratings' => glsr_get_ratings(['assigned_posts' => 'post_id']),
400
                'reviews' => do_shortcode($this->option($product, 'reviews')),
401
                'summary' => do_shortcode($this->option($product, 'summary')),
402
                'verified' => $isVerifiedOwner || 'no' === get_option('woocommerce_review_rating_verification_required'),
403
            ]);
404
        }
405
    }
406
407
    /**
408
     * @action woocommerce_single_product_summary
409
     */
410
    public function renderTitleRating(): void
411
    {
412
        global $product;
413
        $ratings = glsr_get_ratings(['assigned_posts' => 'post_id']);
414
        if (0 >= $ratings->average && !glsr_get_option('integrations.woocommerce.display_empty', false, 'bool')) {
415
            return;
416
        }
417
        glsr(Template::class)->render('templates/woocommerce/rating', [
418
            'product' => $product,
419
            'ratings' => $ratings,
420
            'style' => glsr(Style::class)->styleClasses(),
421
            'theme' => glsr_get_option('integrations.woocommerce.style'),
422
        ]);
423
    }
424
425
    /**
426
     * This updates the product_visibility rated-* categories.
427
     *
428
     * @action site-reviews/ratings/count/post
429
     */
430
    public function updateProductRatingCounts(int $postId, Arguments $counts): void
431
    {
432
        if ('product' === get_post_type($postId)) {
433
            $product = wc_get_product($postId);
434
            $product->set_rating_counts($counts->ratings);
435
            $product->set_average_rating($counts->average);
436
            $product->set_review_count($counts->reviews);
437
            $product->save();
438
        }
439
    }
440
441
    /**
442
     * @action woocommerce_admin_process_product_object
443
     */
444
    public function updateProductData(\WC_Product $product): void
445
    {
446
        $shortcodes = [
447
            'site_reviews',
448
            'site_reviews_form',
449
            'site_reviews_summary',
450
        ];
451
        foreach ($shortcodes as $shortcode) {
452
            $value = trim(filter_input(INPUT_POST, $shortcode));
453
            $value = glsr(Sanitizer::class)->sanitizeText($value);
454
            if (empty($value)) {
455
                $product->delete_meta_data($shortcode);
456
                continue;
457
            }
458
            if (1 !== preg_match("/^\[{$shortcode}(\s[^\]]*\]|\])$/", $value)) {
459
                continue;
460
            }
461
            if (!str_contains($value, 'assigned_posts')) {
462
                $value = str_replace($shortcode, sprintf('%s assigned_posts="post_id"', $shortcode), $value);
463
            }
464
            $product->update_meta_data($shortcode, $value);
465
        }
466
    }
467
468
    protected function buildMetaQuery(string $orderbyKey, string $metaKey): array
469
    {
470
        return [
471
            'relation' => 'OR',
472
            $orderbyKey => ['key' => $metaKey, 'compare' => 'NOT EXISTS'], // this comes first!
473
            ['key' => $metaKey, 'compare' => 'EXISTS'],
474
        ];
475
    }
476
477
    protected function option(\WC_Product $product, string $key): string
478
    {
479
        $shortcodes = [
480
            'form' => 'site_reviews_form',
481
            'reviews' => 'site_reviews',
482
            'summary' => 'site_reviews_summary',
483
        ];
484
        if (!array_key_exists($key, $shortcodes)) {
485
            return '';
486
        }
487
        if ($override = $product->get_meta($shortcodes[$key])) {
488
            return $override;
489
        }
490
        return glsr_get_option("integrations.woocommerce.{$key}");
491
    }
492
493
    /**
494
     * @param int[] $ratings
495
     */
496
    protected function setMetaQueriesForFilteredRatings(array $ratings): void
497
    {
498
        global $wp_query;
499
        $ratings = Arr::uniqueInt($ratings);
500
        if (!empty($ratings)) {
501
            $metaQuery = Arr::consolidate($wp_query->get('meta_query'));
502
            $metaQueries = ['relation' => 'OR'];
503
            foreach ($ratings as $rating) {
504
                $metaQueries[] = [
505
                    'key' => CountManager::META_AVERAGE,
506
                    'compare' => 'BETWEEN',
507
                    'value' => [$rating - .5, $rating + .49], // compare the rating to a rounded average range
508
                ];
509
            }
510
            $metaQuery[] = $metaQueries;
511
            $wp_query->set('meta_query', $metaQuery);
512
        }
513
    }
514
}
515