Test Failed
Push — develop ( 1a3dd7...d56800 )
by Paul
08:20
created

ProductController   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 43
eloc 130
c 1
b 0
f 0
dl 0
loc 272
rs 8.96

13 Methods

Rating   Name   Duplication   Size   Complexity  
A filterAssignedPostsPostId() 0 21 5
A filterShortcodeAttributes() 0 11 4
A buildMetaQuery() 0 6 1
A parseProductQuery() 0 19 4
A registerBlockPatterns() 0 8 1
A filterReviewAuthorTagValue() 0 11 3
A filterReviewCallbackHasProductOwner() 0 16 4
A registerBlocks() 0 4 1
A verifyProductOwner() 0 3 1
B filterBlockRenderCallback() 0 43 9
A filterReviewFormBuild() 0 14 4
A registerProductAttributes() 0 12 2
A isProductOwner() 0 16 4

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\SureCart\Controllers;
4
5
use GeminiLabs\SiteReviews\Contracts\ControllerContract;
6
use GeminiLabs\SiteReviews\Contracts\ShortcodeContract;
7
use GeminiLabs\SiteReviews\Database\CountManager;
8
use GeminiLabs\SiteReviews\HookProxy;
9
use GeminiLabs\SiteReviews\Modules\Html\Builder;
10
use GeminiLabs\SiteReviews\Modules\Html\Tags\ReviewTag;
11
use GeminiLabs\SiteReviews\Review;
12
use SureCart\Models\Product;
0 ignored issues
show
Bug introduced by
The type SureCart\Models\Product was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use SureCart\Models\Purchase;
0 ignored issues
show
Bug introduced by
The type SureCart\Models\Purchase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use SureCart\Models\User;
0 ignored issues
show
Bug introduced by
The type SureCart\Models\User was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
16
class ProductController implements ControllerContract
17
{
18
    use HookProxy;
19
20
    /**
21
     * @filter render_block_core/shortcode
22
     */
23
    public function filterAssignedPostsPostId(string $content, array $parsedBlock, ?\WP_Block $parentBlock): string
0 ignored issues
show
Unused Code introduced by
The parameter $parsedBlock is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

23
    public function filterAssignedPostsPostId(string $content, /** @scrutinizer ignore-unused */ array $parsedBlock, ?\WP_Block $parentBlock): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
24
    {
25
        $postId = $parentBlock->context['postId'] ?? 0;
26
        $postType = $parentBlock->context['postType'] ?? '';
27
        if ('sc_product' !== $postType) {
28
            return $content;
29
        }
30
        if (!str_contains($content, '[site_review')) {
31
            return $content;
32
        }
33
        if (!str_contains($content, 'assigned_posts')) {
34
            return $content;
35
        }
36
        if (!str_contains($content, 'post_id')) {
37
            return $content;
38
        }
39
        $pattern = '/(assigned_posts\s*=\s*(["\']?))(.*?)\2(?=\s|$)/';
40
        return preg_replace_callback($pattern, function ($match) use ($postId) {
41
            $value = preg_replace('/\bpost_id\b/', $postId, $match[3]);
42
            return $match[1].$value.$match[2];
43
        }, $content);
44
    }
45
46
    /**
47
     * @filter block_type_metadata_settings
48
     */
49
    public function filterBlockRenderCallback(array $settings, array $metadata): array
50
    {
51
        $name = $metadata['name'] ?? '';
52
        $targets = [
53
            'surecart/product-list-sort',
54
            'surecart/product-list-sort-radio-group-template',
55
        ];
56
        if (!in_array($name, $targets)) {
57
            return $settings;
58
        }
59
        $controllerPath = wp_normalize_path(
60
            realpath(dirname($metadata['file']).'/'.remove_block_asset_path_prefix('file:./controller.php'))
61
        );
62
        if (!file_exists($controllerPath)) {
63
            return $settings;
64
        }
65
        $settings['render_callback'] = static function ($attributes, $content, $block) use ($controllerPath, $metadata) {
66
            $view = require $controllerPath;
67
            $templatePath = wp_normalize_path(
68
                realpath(dirname($metadata['file']).'/'.remove_block_asset_path_prefix($view))
69
            );
70
            if (isset($options)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $options seems to never exist and therefore isset should always be false.
Loading history...
71
                && isset($params)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $params seems to never exist and therefore isset should always be false.
Loading history...
72
                && isset($query_order)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $query_order seems to never exist and therefore isset should always be false.
Loading history...
73
                && isset($query_order_by)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $query_order_by seems to never exist and therefore isset should always be false.
Loading history...
74
                $options[] = [
75
                    'checked' => 'asc' === $query_order && 'rating' === $query_order_by,
76
                    'href' => $params->addArg('order', 'asc')->addArg('orderby', 'rating')->url(),
77
                    'label' => esc_html__('Rating, low to high', 'site-reviews'),
78
                    'value' => 'rating:asc',
79
                ];
80
                $options[] = [
81
                    'checked' => 'desc' === $query_order && 'rating' === $query_order_by,
82
                    'href' => $params->addArg('order', 'desc')->addArg('orderby', 'rating')->url(),
83
                    'label' => esc_html__('Rating, high to low', 'site-reviews'),
84
                    'value' => 'rating:desc',
85
                ];
86
            }
87
            ob_start();
88
            require $templatePath;
89
            return ob_get_clean();
90
        };
91
        return $settings;
92
    }
93
94
    /**
95
     * @filter site-reviews/review/value/author
96
     */
97
    public function filterReviewAuthorTagValue(string $value, ReviewTag $tag): string
98
    {
99
        $ownership = glsr_get_option('integrations.surecart.ownership', [], 'array');
100
        if (!in_array('labeled', $ownership)) {
101
            return $value;
102
        }
103
        if ($tag->review->hasProductOwner()) {
0 ignored issues
show
Bug introduced by
The method hasProductOwner() does not exist on GeminiLabs\SiteReviews\Review. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

103
        if ($tag->review->/** @scrutinizer ignore-call */ hasProductOwner()) {
Loading history...
104
            $text = esc_attr__('verified owner', 'site-reviews');
105
            $value = sprintf('%s <em data-verified-owner="1">(%s)</em>', $value, $text);
106
        }
107
        return $value;
108
    }
109
110
    /**
111
     * @filter site-reviews/review/call/hasProductOwner
112
     */
113
    public function filterReviewCallbackHasProductOwner(Review $review): bool
114
    {
115
        $verified = get_post_meta($review->ID, '_sc_verified', true);
116
        if ('' !== $verified) {
117
            return (bool) $verified;
118
        }
119
        $review->refresh(); // refresh the review first!
120
        $verified = false;
121
        foreach ($review->assigned_posts as $postId) {
122
            if ('sc_product' === get_post_type($postId)) {
123
                $verified = $this->isProductOwner($review->author_id, $postId);
124
                break; // only check the first product
125
            }
126
        }
127
        update_post_meta($review->ID, '_sc_verified', (int) $verified);
128
        return $verified;
129
    }
130
131
    /**
132
     * @filter site-reviews/build/template/reviews-form
133
     */
134
    public function filterReviewFormBuild(string $template, array $data): string
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

134
    public function filterReviewFormBuild(string $template, /** @scrutinizer ignore-unused */ array $data): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
135
    {
136
        if ('sc_product' !== get_post_type()) {
137
            return $template;
138
        }
139
        $ownership = glsr_get_option('integrations.surecart.ownership', [], 'array');
140
        if (!in_array('restricted', $ownership)) {
141
            return $template;
142
        }
143
        if ($this->isProductOwner(get_current_user_id(), get_the_ID())) {
0 ignored issues
show
Bug introduced by
It seems like get_the_ID() can also be of type false; however, parameter $productId of GeminiLabs\SiteReviews\I...oller::isProductOwner() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

143
        if ($this->isProductOwner(get_current_user_id(), /** @scrutinizer ignore-type */ get_the_ID())) {
Loading history...
144
            return $template;
145
        }
146
        return glsr(Builder::class)->p([
147
            'text' => esc_html__('Only logged in customers who have purchased this product may leave a review.', 'woocommerce'),
148
        ]);
149
    }
150
151
    /**
152
     * @filter site-reviews/shortcode/site_reviews/attributes
153
     * @filter site-reviews/shortcode/site_reviews_form/attributes
154
     * @filter site-reviews/shortcode/site_reviews_summary/attributes
155
     */
156
    public function filterShortcodeAttributes(array $attributes, ShortcodeContract $shortcode): array
0 ignored issues
show
Unused Code introduced by
The parameter $shortcode is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

156
    public function filterShortcodeAttributes(array $attributes, /** @scrutinizer ignore-unused */ ShortcodeContract $shortcode): array

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
157
    {
158
        $refererQuery = wp_parse_args(wp_parse_url((string) wp_get_referer(), \PHP_URL_QUERY));
159
        $template = $refererQuery['p'] ?? ''; // Get the current Site Editor template
160
        if (!str_starts_with((string) $template, '/wp_template/surecart/') && 'sc_product' !== get_post_type()) {
161
            return $attributes;
162
        }
163
        if ($style = glsr_get_option('integrations.surecart.style')) {
164
            $attributes['data-style'] = $style;
165
        }
166
        return $attributes;
167
    }
168
169
    /**
170
     * @action parse_query
171
     */
172
    public function parseProductQuery(\WP_Query $query): void
173
    {
174
        if ('sc_product' !== $query->get('post_type')) {
175
            return;
176
        }
177
        if ('rating' !== $query->get('orderby')) {
178
            return;
179
        }
180
        $metaQuery = $query->get('meta_query', []);
181
        $order = $query->get('order', 'desc');
182
        if ('bayesian' === glsr_get_option('integrations.surecart.sorting')) {
183
            $metaQuery[] = $this->buildMetaQuery('glsr_ranking', CountManager::META_RANKING);
184
            $query->set('meta_query', $metaQuery);
185
            $query->set('orderby', ['glsr_ranking' => $order]);
186
        } else {
187
            $metaQuery[] = $this->buildMetaQuery('glsr_average', CountManager::META_AVERAGE);
188
            $metaQuery[] = $this->buildMetaQuery('glsr_reviews', CountManager::META_REVIEWS);
189
            $query->set('meta_query', $metaQuery);
190
            $query->set('orderby', ['glsr_average' => $order, 'glsr_reviews' => $order]);
191
        }
192
    }
193
194
    /**
195
     * @action init:11
196
     */
197
    public function registerBlocks(): void
198
    {
199
        register_block_type_from_metadata(glsr()->path('assets/blocks/surecart_product_rating'));
200
        register_block_type_from_metadata(glsr()->path('assets/blocks/surecart_product_reviews'));
201
    }
202
203
    /**
204
     * @action init
205
     */
206
    public function registerBlockPatterns(): void
207
    {
208
        register_block_pattern(glsr()->id.'/surecart-product-reviews', [
209
            'title' => _x('Product Reviews', 'admin-text', 'site-reviews'),
210
            'categories' => ['surecart_product_page'],
211
            'blockTypes' => ['surecart/product-page'],
212
            'priority' => 2,
213
            'content' => '
214
                <!-- wp:group {"layout":{"type":"constrained"}} -->
215
                <div class="wp-block-group">
216
                    <!-- wp:columns {"align":"wide"} -->
217
                    <div class="wp-block-columns alignwide">
218
                        <!-- wp:column {"width":"100%","className":"is-style-default","layout":{"type":"default"}} -->
219
                        <div class="wp-block-column is-style-default" style="flex-basis:100%">
220
                            <!-- wp:heading {"className":"is-style-text-subtitle"} -->
221
                            <h2 class="wp-block-heading is-style-text-subtitle">Reviews</h2>
222
                            <!-- /wp:heading -->
223
                            <!-- wp:site-reviews/reviews {"assigned_posts":["post_id"],"hide":["title"],"id":"reviews","pagination":"ajax","schema":1} /-->
224
                            <!-- wp:heading {"className":"is-style-text-subtitle"} -->
225
                            <h2 class="wp-block-heading is-style-text-subtitle">Submit a Review</h2>
226
                            <!-- /wp:heading -->
227
                            <!-- wp:site-reviews/form {"assigned_posts":["post_id"],"hide":["name","email","title"],"reviews_id":"reviews"} /--></div>
228
                        <!-- /wp:column -->
229
                        </div>
230
                    <!-- /wp:columns -->
231
                    </div>
232
                <!-- /wp:group -->',
233
            // 'postTypes' => ['sc_product'],
234
            // 'templateTypes' => ['sc_product'],
235
        ]);
236
    }
237
238
    /**
239
     * @filter surecart/product/attributes_set
240
     */
241
    public function registerProductAttributes(Product $product): void
242
    {
243
        $postId = $product->post->ID ?? 0;
244
        if (0 === $postId) {
245
            return;
246
        }
247
        $ratingInfo = glsr_get_ratings([
248
            'assigned_posts' => $postId,
249
        ]);
250
        $product->setAttribute('rating', $ratingInfo->average);
251
        $product->setAttribute('reviews', $ratingInfo->reviews);
252
        $product->setAttribute('ranking', $ratingInfo->ranking);
253
    }
254
255
    /**
256
     * @action site-reviews/review/created
257
     */
258
    public function verifyProductOwner(Review $review): void
259
    {
260
        $review->hasProductOwner();
261
    }
262
263
    protected function buildMetaQuery(string $orderbyKey, string $metaKey): array
264
    {
265
        return [
266
            'relation' => 'OR',
267
            $orderbyKey => ['key' => $metaKey, 'compare' => 'NOT EXISTS'], // this comes first!
268
            ['key' => $metaKey, 'compare' => 'EXISTS'],
269
        ];
270
    }
271
272
    protected function isProductOwner(int $userId, int $productId): bool
273
    {
274
        if (!$user = User::getUserBy('id', $userId)) { // @phpstan-ignore-line
275
            return false;
276
        }
277
        if (!$customer = $user->customer()) { // @phpstan-ignore-line
278
            return false;
279
        }
280
        if (!$product = sc_get_product($productId)) {
0 ignored issues
show
Bug introduced by
The function sc_get_product was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

280
        if (!$product = /** @scrutinizer ignore-call */ sc_get_product($productId)) {
Loading history...
281
            return false;
282
        }
283
        $purchases = Purchase::where([ // @phpstan-ignore-line
284
            'customer_ids' => [$customer->id],
285
            'product_ids' => [$product->id], // @phpstan-ignore-line
286
        ])->get();
287
        return !empty($purchases);
288
    }
289
}
290