PluginProbe ʕ •ᴥ•ʔ
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments / 4.6.3
SureCart – Ecommerce Made Easy For Selling Physical Products, Digital Downloads, Subscriptions, Donations, & Payments v4.6.3
4.6.3 4.6.2 4.6.1 4.6.0 4.5.1 4.5.0 4.4.2 4.4.1 4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.3 4.2.2 4.2.1 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.11.1 1.11.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.10.0 2.10.1 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.12.0 2.13.0 2.14.0 2.14.1 2.15.0 2.15.1 2.16.0 2.16.1 2.16.2 2.16.3 2.17.0 2.17.1 2.17.2 2.18.0 2.19.0 2.19.2 2.19.3 2.19.4 2.2.0 2.2.1 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.26.0 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.2 2.29.3 2.29.4 2.3.0 2.3.1 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.40.0 2.40.1 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.9.0 3.0.0 3.0.0-RC1 3.0.0-RC2 3.0.0-beta1 3.0.0-beta2 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.10.0 3.10.1 3.11.0 3.12.0 3.13.0 3.13.1 3.13.2 3.13.3 3.13.4 3.14.0 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.16.6 3.16.7 3.16.8 3.17.0 3.17.1 3.17.2 3.17.3 3.17.4 3.17.5 3.17.6 3.18.0 3.19.0 3.19.1 3.19.2 3.2.0 3.2.1 3.2.2 3.20.0 3.20.1 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 4.0.0 4.0.1 4.0.2 4.0.3 trunk 4.1.0 0.2.19.1 4.1.1 1.0.0 4.2.0 1.0.1 1.0.2
surecart / app / src / Concerns / StripsPrivateCatalogFields.php
surecart / app / src / Concerns Last commit date
Arrayable.php 4 years ago HasBlockTheme.php 4 years ago Objectable.php 1 year ago RestrictsAnonymousReads.php 3 weeks ago SanitizesRestParams.php 3 months ago StripsPrivateCatalogFields.php 6 days ago
StripsPrivateCatalogFields.php
330 lines
1 <?php
2
3 namespace SureCart\Concerns;
4
5 /**
6 * Strips private catalog data from anonymous-facing serializations — view/embed
7 * REST responses in places the item schema can't reach (expanded sub-objects)
8 * and catalog data serialized into public page HTML (see PublicCatalogData).
9 */
10 trait StripsPrivateCatalogFields {
11 /**
12 * Private field keys excluded from stripping, keyed by catalog object type.
13 *
14 * Empty by default so REST responses strip everything. The public HTML
15 * serializer overrides this to keep the few fields the storefront renders
16 * from — see \SureCart\Support\PublicCatalogData for the list and reasons.
17 *
18 * @return array
19 */
20 protected function preservedCatalogFields() {
21 return array();
22 }
23
24 /**
25 * Strip the given private fields from a catalog response array.
26 *
27 * @param array $data Response data.
28 * @param array $fields Default field keys to strip.
29 * @param string $type Catalog object type the fields belong to.
30 *
31 * @return array
32 */
33 private function stripPrivateFields( $data, $fields, $type ) {
34 $preserved = $this->preservedCatalogFields()[ $type ] ?? array();
35 if ( ! empty( $preserved ) ) {
36 $fields = array_diff( $fields, $preserved );
37 }
38
39 /**
40 * Filters the private field keys stripped from view/embed catalog
41 * REST responses.
42 *
43 * WARNING: this is a security control. These endpoints proxy the
44 * private platform API with the store's secret token — removing a
45 * key from this list re-exposes private platform data (stock, skus,
46 * metadata, offer internals, customer PII) to anonymous callers.
47 *
48 * @param array $fields Field keys stripped from the response.
49 * @param string $type Catalog object type (product, variant, variant_option, price, review, product_collection, bundle_item).
50 * @param array $data The response data being stripped.
51 */
52 $fields = apply_filters( 'surecart/rest/private_catalog_fields', $fields, $type, $data );
53
54 foreach ( (array) $fields as $field ) {
55 unset( $data[ $field ] );
56 }
57
58 return $data;
59 }
60
61 /**
62 * Strip private fields from a product array.
63 *
64 * Covers fields the products schema strips at the top level, so it can
65 * also be applied to products expanded on other resources (e.g. prices).
66 *
67 * @param array|mixed $product Product response data. Non-arrays pass through untouched.
68 *
69 * @return array|mixed
70 */
71 protected function stripPrivateProductFields( $product ) {
72 if ( ! is_array( $product ) ) {
73 return $product;
74 }
75
76 $product = $this->stripPrivateFields(
77 $product,
78 [
79 // private sub-objects — never for anonymous callers.
80 'commission_structure',
81 'downloads',
82 'current_release_download',
83 'files',
84 'shipping_profile',
85 // internals the schema strips on the products endpoint.
86 // metrics stays — the product list renders price ranges from it.
87 'available_stock',
88 'held_stock',
89 'stock',
90 'status',
91 'archived',
92 'archived_at',
93 'discarded_at',
94 'cataloged_at',
95 'sku',
96 'metadata',
97 'dimensions',
98 'weight',
99 'weight_unit',
100 'tax_category',
101 'tax_enabled',
102 'purchase_limit',
103 ],
104 'product'
105 );
106
107 if ( ! empty( $product['variants']['data'] ) && is_array( $product['variants']['data'] ) ) {
108 $product['variants']['data'] = array_map( [ $this, 'stripPrivateVariantFields' ], $product['variants']['data'] );
109 }
110
111 if ( ! empty( $product['variant_options']['data'] ) && is_array( $product['variant_options']['data'] ) ) {
112 $product['variant_options']['data'] = array_map( [ $this, 'stripPrivateVariantOptionFields' ], $product['variant_options']['data'] );
113 }
114
115 if ( ! empty( $product['prices']['data'] ) && is_array( $product['prices']['data'] ) ) {
116 $product['prices']['data'] = array_map( [ $this, 'stripPrivatePriceFields' ], $product['prices']['data'] );
117 }
118
119 if ( ! empty( $product['reviews']['data'] ) && is_array( $product['reviews']['data'] ) ) {
120 $product['reviews']['data'] = array_map( [ $this, 'stripPrivateReviewFields' ], $product['reviews']['data'] );
121 }
122
123 if ( ! empty( $product['product_collections']['data'] ) && is_array( $product['product_collections']['data'] ) ) {
124 $product['product_collections']['data'] = array_map( [ $this, 'stripPrivateCollectionFields' ], $product['product_collections']['data'] );
125 }
126
127 if ( ! empty( $product['bundle_items']['data'] ) && is_array( $product['bundle_items']['data'] ) ) {
128 $product['bundle_items']['data'] = array_map( [ $this, 'stripPrivateBundleItemFields' ], $product['bundle_items']['data'] );
129 }
130
131 // accessor-derived copies (Model::toArray appends every get*Attribute) leak the same fields.
132 foreach ( [ 'active_prices', 'active_ad_hoc_prices' ] as $key ) {
133 if ( ! empty( $product[ $key ] ) && is_array( $product[ $key ] ) ) {
134 $product[ $key ] = array_map( [ $this, 'stripPrivatePriceFields' ], $product[ $key ] );
135 }
136 }
137
138 if ( ! empty( $product['in_stock_variants'] ) && is_array( $product['in_stock_variants'] ) ) {
139 $product['in_stock_variants'] = array_map( [ $this, 'stripPrivateVariantFields' ], $product['in_stock_variants'] );
140 }
141
142 if ( ! empty( $product['initial_price'] ) ) {
143 $product['initial_price'] = $this->stripPrivatePriceFields( $product['initial_price'] );
144 }
145
146 foreach ( [ 'initial_variant', 'first_variant_with_stock' ] as $key ) {
147 if ( ! empty( $product[ $key ] ) ) {
148 $product[ $key ] = $this->stripPrivateVariantFields( $product[ $key ] );
149 }
150 }
151
152 return $product;
153 }
154
155 /**
156 * Strip private fields from a variant array.
157 *
158 * @param array|mixed $variant Variant response data. Non-arrays pass through untouched.
159 *
160 * @return array|mixed
161 */
162 protected function stripPrivateVariantFields( $variant ) {
163 if ( ! is_array( $variant ) ) {
164 return $variant;
165 }
166
167 return $this->stripPrivateFields(
168 $variant,
169 [
170 'available_stock',
171 'held_stock',
172 'stock',
173 'sku',
174 'metadata',
175 'dimensions',
176 'weight',
177 'weight_unit',
178 // signed download urls — never for anonymous callers.
179 'downloads',
180 'current_release_download',
181 ],
182 'variant'
183 );
184 }
185
186 /**
187 * Strip private fields from a variant option array.
188 *
189 * Variant options ride along on products and bundle items
190 * (variant_options / component_variant_options) and carry metadata.
191 *
192 * @param array|mixed $option Variant option response data. Non-arrays pass through untouched.
193 *
194 * @return array|mixed
195 */
196 protected function stripPrivateVariantOptionFields( $option ) {
197 if ( ! is_array( $option ) ) {
198 return $option;
199 }
200
201 return $this->stripPrivateFields(
202 $option,
203 [
204 'metadata',
205 ],
206 'variant_option'
207 );
208 }
209
210 /**
211 * Strip private fields from a bundle item array.
212 *
213 * A bundle item expands the component product (and its variants) the
214 * bundle checkout renders — the nested product carries the same private
215 * fields as a top-level one, so it gets the same treatment.
216 *
217 * @param array|mixed $item Bundle item response data. Non-arrays pass through untouched.
218 *
219 * @return array|mixed
220 */
221 protected function stripPrivateBundleItemFields( $item ) {
222 if ( ! is_array( $item ) ) {
223 return $item;
224 }
225
226 $item = $this->stripPrivateFields(
227 $item,
228 [
229 'metadata',
230 ],
231 'bundle_item'
232 );
233
234 foreach ( [ 'component_product', 'bundle_product' ] as $key ) {
235 if ( ! empty( $item[ $key ] ) && is_array( $item[ $key ] ) ) {
236 $item[ $key ] = $this->stripPrivateProductFields( $item[ $key ] );
237 }
238 }
239
240 if ( ! empty( $item['component_variants']['data'] ) && is_array( $item['component_variants']['data'] ) ) {
241 $item['component_variants']['data'] = array_map( [ $this, 'stripPrivateVariantFields' ], $item['component_variants']['data'] );
242 }
243
244 if ( ! empty( $item['component_variant_options']['data'] ) && is_array( $item['component_variant_options']['data'] ) ) {
245 $item['component_variant_options']['data'] = array_map( [ $this, 'stripPrivateVariantOptionFields' ], $item['component_variant_options']['data'] );
246 }
247
248 return $item;
249 }
250
251 /**
252 * Strip private fields from a price array.
253 *
254 * Keeps scratch_amount — the storefront renders it as the compare-at price.
255 *
256 * @param array|mixed $price Price response data. Non-arrays pass through untouched.
257 *
258 * @return array|mixed
259 */
260 protected function stripPrivatePriceFields( $price ) {
261 if ( ! is_array( $price ) ) {
262 return $price;
263 }
264
265 $price = $this->stripPrivateFields(
266 $price,
267 [
268 'metadata',
269 'archived_at',
270 'discarded_at',
271 ],
272 'price'
273 );
274
275 // a price can expand its product — it carries the same private fields.
276 if ( ! empty( $price['product'] ) && is_array( $price['product'] ) ) {
277 $price['product'] = $this->stripPrivateProductFields( $price['product'] );
278 }
279
280 return $price;
281 }
282
283 /**
284 * Strip customer PII and purchase internals from a review array.
285 *
286 * @param array|mixed $review Review response data. Non-arrays pass through untouched.
287 *
288 * @return array|mixed
289 */
290 protected function stripPrivateReviewFields( $review ) {
291 if ( ! is_array( $review ) ) {
292 return $review;
293 }
294
295 return $this->stripPrivateFields(
296 $review,
297 [
298 'customer',
299 'purchase',
300 ],
301 'review'
302 );
303 }
304
305 /**
306 * Strip private fields from a product collection array.
307 *
308 * Covers the fields the collections schema makes edit-only, so it can
309 * also be applied to collections expanded on products.
310 *
311 * @param array|mixed $collection Product collection response data. Non-arrays pass through untouched.
312 *
313 * @return array|mixed
314 */
315 protected function stripPrivateCollectionFields( $collection ) {
316 if ( ! is_array( $collection ) ) {
317 return $collection;
318 }
319
320 return $this->stripPrivateFields(
321 $collection,
322 [
323 'metadata',
324 'archived_at',
325 ],
326 'product_collection'
327 );
328 }
329 }
330