ProductController   F
last analyzed

Complexity

Total Complexity 71

Size/Duplication

Total Lines 481
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 71
eloc 192
dl 0
loc 481
ccs 0
cts 225
cp 0
rs 2.7199
c 0
b 0
f 0

28 Methods

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