Test Failed
Push — develop ( e65255...277958 )
by Paul
10:13
created

ProductController::renderProductColumnValues()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 2
nc 2
nop 2
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\Helpers\Arr;
9
use GeminiLabs\SiteReviews\Helpers\Svg;
10
use GeminiLabs\SiteReviews\HookProxy;
11
use GeminiLabs\SiteReviews\Modules\Html\Builder;
12
use GeminiLabs\SiteReviews\Modules\Html\Tags\ReviewTag;
13
use GeminiLabs\SiteReviews\Modules\SchemaParser;
14
use GeminiLabs\SiteReviews\Review;
15
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...
16
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...
17
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...
18
19
class ProductController implements ControllerContract
20
{
21
    use HookProxy;
22
23
    /**
24
     * @filter render_block_core/shortcode
25
     */
26
    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

26
    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...
27
    {
28
        $postId = $parentBlock->context['postId'] ?? 0;
29
        $postType = $parentBlock->context['postType'] ?? '';
30
        if ('sc_product' !== $postType) {
31
            return $content;
32
        }
33
        if (!str_contains($content, '[site_review')) {
34
            return $content;
35
        }
36
        if (!str_contains($content, 'assigned_posts')) {
37
            return $content;
38
        }
39
        if (!str_contains($content, 'post_id')) {
40
            return $content;
41
        }
42
        $pattern = '/(assigned_posts\s*=\s*(["\']?))(.*?)\2(?=\s|$)/';
43
        return preg_replace_callback($pattern, function ($match) use ($postId) {
44
            $value = preg_replace('/\bpost_id\b/', $postId, $match[3]);
45
            return $match[1].$value.$match[2];
46
        }, $content);
47
    }
48
49
    /**
50
     * @filter block_type_metadata_settings
51
     */
52
    public function filterBlockRenderCallback(array $settings, array $metadata): array
53
    {
54
        $name = $metadata['name'] ?? '';
55
        $targets = [
56
            'surecart/product-list-sort',
57
            'surecart/product-list-sort-radio-group-template',
58
        ];
59
        if (!in_array($name, $targets)) {
60
            return $settings;
61
        }
62
        $controllerPath = wp_normalize_path(
63
            realpath(dirname($metadata['file']).'/'.remove_block_asset_path_prefix('file:./controller.php'))
64
        );
65
        if (!file_exists($controllerPath)) {
66
            return $settings;
67
        }
68
        $settings['render_callback'] = static function ($attributes, $content, $block) use ($controllerPath, $metadata) {
69
            $view = require $controllerPath;
70
            $templatePath = wp_normalize_path(
71
                realpath(dirname($metadata['file']).'/'.remove_block_asset_path_prefix($view))
72
            );
73
            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...
74
                && 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...
75
                && 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...
76
                && 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...
77
                $options[] = [
78
                    'checked' => 'asc' === $query_order && 'rating' === $query_order_by,
79
                    'href' => $params->addArg('order', 'asc')->addArg('orderby', 'rating')->url(),
80
                    'label' => esc_html__('Rating, low to high', 'site-reviews'),
81
                    'value' => 'rating:asc',
82
                ];
83
                $options[] = [
84
                    'checked' => 'desc' === $query_order && 'rating' === $query_order_by,
85
                    'href' => $params->addArg('order', 'desc')->addArg('orderby', 'rating')->url(),
86
                    'label' => esc_html__('Rating, high to low', 'site-reviews'),
87
                    'value' => 'rating:desc',
88
                ];
89
            }
90
            ob_start();
91
            require $templatePath;
92
            return ob_get_clean();
93
        };
94
        return $settings;
95
    }
96
97
    /**
98
     * @param string[] $columns
99
     *
100
     * @filter manage_sc-products_columns
101
     */
102
    public function filterProductColumns(array $columns): array
103
    {
104
        $svg = Svg::get('assets/images/icon.svg', [
105
            'height' => 24,
106
            'style' => 'display:flex; flex-shrink:0; margin: -4px 0;',
107
            'width' => 24,
108
        ]);
109
        $columns[glsr()->prefix.'rating'] = glsr(Builder::class)->div([
110
            'style' => 'display:flex; align-items:center; justify-content:center;',
111
            'text' => sprintf('%s<span>%s</span>', $svg, _x('Reviews', 'admin-text', 'site-reviews')),
112
        ]);
113
        return $columns;
114
    }
115
116
    /**
117
     * @filter surecart/product/json_schema
118
     */
119
    public function filterProductSchema(array $schema, Product $product): array
0 ignored issues
show
Unused Code introduced by
The parameter $product 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

119
    public function filterProductSchema(array $schema, /** @scrutinizer ignore-unused */ Product $product): 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...
120
    {
121
        $data = glsr(SchemaParser::class)->generate();
122
        $aggregateRatingSchema = Arr::get($data, 'aggregateRating');
123
        $reviewSchema = Arr::get($data, 'review');
124
        if ($aggregateRatingSchema) {
125
            $schema['aggregateRating'] = $aggregateRatingSchema;
126
        }
127
        if ($reviewSchema) {
128
            $schema['review'] = $reviewSchema;
129
        }
130
        // remove Site Reviews generated schema
131
        add_filter('site-reviews/schema/all', '__return_empty_array');
132
        return $schema;
133
    }
134
135
    /**
136
     * @filter site-reviews/review/value/author
137
     */
138
    public function filterReviewAuthorTagValue(string $value, ReviewTag $tag): string
139
    {
140
        $ownership = glsr_get_option('integrations.surecart.ownership', [], 'array');
141
        if (!in_array('labeled', $ownership)) {
142
            return $value;
143
        }
144
        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

144
        if ($tag->review->/** @scrutinizer ignore-call */ hasProductOwner()) {
Loading history...
145
            $text = esc_attr__('verified owner', 'site-reviews');
146
            $value = sprintf('%s <em data-verified-owner="1">(%s)</em>', $value, $text);
147
        }
148
        return $value;
149
    }
150
151
    /**
152
     * @filter site-reviews/review/call/hasProductOwner
153
     */
154
    public function filterReviewCallbackHasProductOwner(Review $review): bool
155
    {
156
        $verified = get_post_meta($review->ID, '_sc_verified', true);
157
        if ('' !== $verified) {
158
            return (bool) $verified;
159
        }
160
        $review->refresh(); // refresh the review first!
161
        $verified = false;
162
        foreach ($review->assigned_posts as $postId) {
163
            if ('sc_product' === get_post_type($postId)) {
164
                $verified = $this->isProductOwner($review->author_id, $postId);
165
                break; // only check the first product
166
            }
167
        }
168
        update_post_meta($review->ID, '_sc_verified', (int) $verified);
169
        return $verified;
170
    }
171
172
    /**
173
     * @filter site-reviews/build/template/reviews-form
174
     */
175
    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

175
    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...
176
    {
177
        if ('sc_product' !== get_post_type()) {
178
            return $template;
179
        }
180
        $ownership = glsr_get_option('integrations.surecart.ownership', [], 'array');
181
        if (!in_array('restricted', $ownership)) {
182
            return $template;
183
        }
184
        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

184
        if ($this->isProductOwner(get_current_user_id(), /** @scrutinizer ignore-type */ get_the_ID())) {
Loading history...
185
            return $template;
186
        }
187
        return glsr(Builder::class)->p([
188
            'text' => esc_html__('Only logged in customers who have purchased this product may leave a review.', 'woocommerce'),
189
        ]);
190
    }
191
192
    /**
193
     * @filter site-reviews/shortcode/site_reviews/attributes
194
     * @filter site-reviews/shortcode/site_reviews_form/attributes
195
     * @filter site-reviews/shortcode/site_reviews_summary/attributes
196
     */
197
    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

197
    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...
198
    {
199
        $refererQuery = wp_parse_args(wp_parse_url((string) wp_get_referer(), \PHP_URL_QUERY));
200
        $template = $refererQuery['p'] ?? ''; // Get the current Site Editor template
201
        if (!str_starts_with((string) $template, '/wp_template/surecart/') && 'sc_product' !== get_post_type()) {
202
            return $attributes;
203
        }
204
        if ($style = glsr_get_option('integrations.surecart.style')) {
205
            $attributes['data-style'] = $style;
206
        }
207
        return $attributes;
208
    }
209
210
    /**
211
     * @action parse_query
212
     */
213
    public function parseProductQuery(\WP_Query $query): void
214
    {
215
        if ('sc_product' !== $query->get('post_type')) {
216
            return;
217
        }
218
        if ('rating' !== $query->get('orderby')) {
219
            return;
220
        }
221
        $metaQuery = $query->get('meta_query', []);
222
        $order = $query->get('order', 'desc');
223
        if ('bayesian' === glsr_get_option('integrations.surecart.sorting')) {
224
            $metaQuery[] = $this->buildMetaQuery('glsr_ranking', CountManager::META_RANKING);
225
            $query->set('meta_query', $metaQuery);
226
            $query->set('orderby', ['glsr_ranking' => $order]);
227
        } else {
228
            $metaQuery[] = $this->buildMetaQuery('glsr_average', CountManager::META_AVERAGE);
229
            $metaQuery[] = $this->buildMetaQuery('glsr_reviews', CountManager::META_REVIEWS);
230
            $query->set('meta_query', $metaQuery);
231
            $query->set('orderby', ['glsr_average' => $order, 'glsr_reviews' => $order]);
232
        }
233
    }
234
235
    /**
236
     * @action init
237
     */
238
    public function registerBlockPatterns(): void
239
    {
240
        register_block_pattern(glsr()->id.'/surecart-product-reviews', [
241
            'title' => _x('Product Reviews', 'admin-text', 'site-reviews'),
242
            'categories' => ['surecart_product_page'],
243
            'blockTypes' => ['surecart/product-page'],
244
            'priority' => 2,
245
            'content' => '
246
                <!-- wp:group {"layout":{"type":"constrained"}} -->
247
                <div class="wp-block-group">
248
                    <!-- wp:columns {"align":"wide"} -->
249
                    <div class="wp-block-columns alignwide">
250
                        <!-- wp:column {"width":"100%","className":"is-style-default","layout":{"type":"default"}} -->
251
                        <div class="wp-block-column is-style-default" style="flex-basis:100%">
252
                            <!-- wp:heading {"className":"is-style-text-subtitle"} -->
253
                            <h2 class="wp-block-heading is-style-text-subtitle">Reviews</h2>
254
                            <!-- /wp:heading -->
255
                            <!-- wp:site-reviews/reviews {"assigned_posts":["post_id"],"hide":["title"],"id":"reviews","pagination":"ajax","schema":1} /-->
256
                            <!-- wp:heading {"className":"is-style-text-subtitle"} -->
257
                            <h2 class="wp-block-heading is-style-text-subtitle">Submit a Review</h2>
258
                            <!-- /wp:heading -->
259
                            <!-- wp:site-reviews/form {"assigned_posts":["post_id"],"hide":["name","email","title"],"reviews_id":"reviews"} /--></div>
260
                        <!-- /wp:column -->
261
                        </div>
262
                    <!-- /wp:columns -->
263
                    </div>
264
                <!-- /wp:group -->',
265
            // 'postTypes' => ['sc_product'],
266
            // 'templateTypes' => ['sc_product'],
267
        ]);
268
    }
269
270
    /**
271
     * @action init:11
272
     */
273
    public function registerBlocks(): void
274
    {
275
        register_block_type_from_metadata(glsr()->path('assets/blocks/surecart_product_rating'));
276
        register_block_type_from_metadata(glsr()->path('assets/blocks/surecart_product_reviews'));
277
    }
278
279
    /**
280
     * @filter surecart/product/attributes_set
281
     */
282
    public function registerProductAttributes(Product $product): void
283
    {
284
        $postId = $product->post->ID ?? 0;
285
        if (0 === $postId) {
286
            return;
287
        }
288
        $ratingInfo = glsr_get_ratings([
289
            'assigned_posts' => $postId,
290
        ]);
291
        $product->setAttribute('rating', $ratingInfo->average);
292
        $product->setAttribute('reviews', $ratingInfo->reviews);
293
        $product->setAttribute('ranking', $ratingInfo->ranking);
294
    }
295
296
    /**
297
     * @action manage_sc-products_custom_column
298
     */
299
    public function renderProductColumnValues(string $column, $product): void
300
    {
301
        if (glsr()->prefix.'rating' !== $column) {
302
            return;
303
        }
304
        echo glsr(Builder::class)->a([
305
            'href' => add_query_arg('assigned_post', $product->post->ID, glsr_admin_url()),
306
            'text' => $product->reviews,
307
        ]);
308
    }
309
310
    /**
311
     * @param string $which
312
     *
313
     * @action manage_products_extra_tablenav
314
     */
315
    public function renderProductTableInlineStyles($which): void
316
    {
317
        if ('top' !== $which) {
318
            return;
319
        }
320
        echo '<style>'.
321
        '@media screen and (min-width: 783px) {'.
322
            '.fixed .column-glsr_rating { width: 5%; }'.
323
            'th.column-glsr_rating span { display: none; }'.
324
            'td.column-glsr_rating { text-align: center; }'.
325
        '}'.
326
        '</style>';
327
    }
328
329
    /**
330
     * @action site-reviews/review/created
331
     */
332
    public function verifyProductOwner(Review $review): void
333
    {
334
        $review->hasProductOwner();
335
    }
336
337
    protected function buildMetaQuery(string $orderbyKey, string $metaKey): array
338
    {
339
        return [
340
            'relation' => 'OR',
341
            $orderbyKey => ['key' => $metaKey, 'compare' => 'NOT EXISTS'], // this comes first!
342
            ['key' => $metaKey, 'compare' => 'EXISTS'],
343
        ];
344
    }
345
346
    protected function isProductOwner(int $userId, int $productId): bool
347
    {
348
        if (!$user = User::getUserBy('id', $userId)) { // @phpstan-ignore-line
349
            return false;
350
        }
351
        if (!$customer = $user->customer()) { // @phpstan-ignore-line
352
            return false;
353
        }
354
        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

354
        if (!$product = /** @scrutinizer ignore-call */ sc_get_product($productId)) {
Loading history...
355
            return false;
356
        }
357
        $purchases = Purchase::where([ // @phpstan-ignore-line
358
            'customer_ids' => [$customer->id],
359
            'product_ids' => [$product->id], // @phpstan-ignore-line
360
        ])->get();
361
        return !empty($purchases);
362
    }
363
}
364