PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.0
1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 All 50 releases
fluent-cart / app / Services / ShortCodeParser / Parsers / ReviewParser.php

ReviewParser.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.7.0, at app/Services/ShortCodeParser/Parsers/ReviewParser.php

139 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\ShortCodeParser\Parsers;
4
5 use FluentCart\App\Models\ProductReview;
6 use FluentCart\App\Services\ProductReviewService;
7 use FluentCart\Framework\Support\Arr;
8
9 /**
10 * {{review.*}} — the review a notification is about.
11 *
12 * The other parsers all reach their subject through an order. A review has no
13 * order to reach through: it can be written before one, after one, or without
14 * one at all, so its own row is the only thing the email may read from. That
15 * is also why review notifications declare their own `to` — the built-in
16 * customer recipient resolves to {{order.customer.email}}, which is nothing
17 * here — and why `recipient_email` goes through the same resolver every
18 * review notification uses, so no two emails can disagree about who wrote it.
19 *
20 * Handed either the model or its array form: the mailer passes models through
21 * to the template parser, but a stored custom body can be parsed against
22 * whatever a caller serialised.
23 */
24 class ReviewParser extends BaseParser
25 {
26 /** @var ProductReview|array|null */
27 private $review;
28
29 /** @var mixed */
30 private $product;
31
32 /** @var ProductReview|array|null the store's reply, when the email is about one */
33 private $reply;
34
35 public function __construct($data)
36 {
37 $this->review = Arr::get($data, 'review');
38 $this->product = Arr::get($data, 'product');
39 $this->reply = Arr::get($data, 'reply');
40 parent::__construct($data);
41 }
42
43 public function parse($accessor = null, $code = null, $transformer = null): ?string
44 {
45 $review = $this->review;
46
47 if (!$review) {
48 return '';
49 }
50
51 switch ($accessor) {
52 case 'reviewer_name':
53 return esc_html($this->attributeOf($review, 'reviewer_name'));
54 case 'reviewer_email':
55 return esc_html($this->attributeOf($review, 'reviewer_email'));
56 case 'recipient_email':
57 // Raw, not escaped: this is an address for the To header. A
58 // model goes through the resolver, which refuses anything
59 // is_email() would; an array form gets the same refusal.
60 if ($review instanceof ProductReview) {
61 return ProductReviewService::resolveNotificationRecipient($review);
62 }
63 $address = trim($this->attributeOf($review, 'reviewer_email'));
64 return $address !== '' && is_email($address) ? $address : '';
65 case 'title':
66 return esc_html($this->attributeOf($review, 'title'));
67 case 'content':
68 // `review` is the column and is hidden from the array form;
69 // `content` is the accessor and is appended to it. Read the
70 // one that is present in both shapes.
71 return wp_kses_post($this->attributeOf($review, 'content'));
72 case 'rating':
73 return (string) (int) $this->attributeOf($review, 'rating');
74 case 'rating_stars':
75 return $this->ratingStars((int) $this->attributeOf($review, 'rating'));
76 case 'status':
77 return esc_html($this->attributeOf($review, 'status'));
78 case 'product_title':
79 return esc_html($this->productTitle());
80 case 'product_url':
81 return esc_url($this->productUrl());
82 case 'reply_content':
83 return $this->reply ? wp_kses_post($this->attributeOf($this->reply, 'content')) : '';
84 case 'replier_name':
85 return $this->reply ? esc_html($this->attributeOf($this->reply, 'reviewer_name')) : '';
86 default:
87 // Unknown accessor: null hands the code to the fallback filter
88 // rather than printing an empty string over a typo.
89 return null;
90 }
91 }
92
93 /**
94 * One field, off a model or an array.
95 *
96 * @param ProductReview|array $source
97 * @param string $key
98 * @return string
99 */
100 private function attributeOf($source, string $key): string
101 {
102 if (is_object($source)) {
103 return (string) ($source->{$key} ?? '');
104 }
105
106 return (string) Arr::get((array) $source, $key, '');
107 }
108
109 private function productTitle(): string
110 {
111 $title = $this->attributeOf($this->product ?: [], 'post_title');
112
113 if ($title === '') {
114 $postId = (int) $this->attributeOf($this->review, 'post_id');
115 $title = $postId ? (string) get_post_field('post_title', $postId, 'raw') : '';
116 }
117
118 return $title;
119 }
120
121 private function productUrl(): string
122 {
123 $postId = (int) ($this->attributeOf($this->product ?: [], 'ID') ?: $this->attributeOf($this->review, 'post_id'));
124
125 return $postId ? (string) get_permalink($postId) : '';
126 }
127
128 /**
129 * Five stars, the given number filled. Plain text entities rather than an
130 * image, so it survives every mail client's image blocking.
131 */
132 private function ratingStars(int $rating): string
133 {
134 $rating = max(0, min(5, $rating));
135
136 return str_repeat('&#9733;', $rating) . str_repeat('&#9734;', 5 - $rating);
137 }
138 }
139