PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / app / Http / Controllers / ProductDownloadablesController.php

ProductDownloadablesController.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Http/Controllers/ProductDownloadablesController.php

262 lines 9.8 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\Http\Controllers;
4
5 use FluentCart\Api\Resource\ProductDownloadResource;
6 use FluentCart\App\Helpers\Helper;
7 use FluentCart\App\Http\Requests\ProductDownloadable\ProductDownloadableBulkFileRequest;
8 use FluentCart\App\Http\Requests\ProductDownloadable\ProductDownloadableFileRequest;
9 use FluentCart\App\Models\Product;
10 use FluentCart\App\Models\ProductDetail;
11 use FluentCart\App\Models\ProductDownload;
12 use FluentCart\App\Services\FileSystem\StorageBucketResolver;
13 use FluentCart\App\Services\FileSystem\StoragePath;
14 use FluentCart\App\Services\URL;
15 use FluentCart\Framework\Http\Request\Request;
16 use FluentCart\Framework\Http\URL as BaseUrl;
17 use FluentCart\Framework\Support\Arr;
18 use FluentCart\Framework\Support\Str;
19 use FluentCart\Framework\Validator\Validator;
20
21 class ProductDownloadablesController extends Controller
22 {
23 public function syncDownloadableFiles(Request $request): \WP_REST_Response
24 {
25 $productDownloadableBulkFileRequest = new ProductDownloadableBulkFileRequest();
26 $validator = Validator::make(
27 $request->all(),
28 $productDownloadableBulkFileRequest->rules(),
29 $productDownloadableBulkFileRequest->messages()
30 );
31 $validationErrors = [];
32
33
34 if ($validator->fails()) {
35 $validationErrors = $validator->errors();
36 }
37
38
39 if (count($validationErrors) !== 0) {
40 return $this->sendError($validationErrors);
41 }
42
43 $data = $request->get('downloadable_files');
44 $sanitizedData = [];
45
46 foreach ($data as $downloadableFile) {
47
48 $variationIds = Arr::get($downloadableFile, 'product_variation_id');
49 if (is_array($variationIds)) {
50 $variationIds = array_map('intval', $variationIds);
51 } else {
52 $variationIds = [];
53 }
54
55 $sanitizedData[] = [
56 'id' => intval(Arr::get($downloadableFile, 'id')),
57 'post_id' => intval(Arr::get($downloadableFile, 'post_id')),
58 'product_variation_id' => $variationIds,
59 'title' => sanitize_text_field(Arr::get($downloadableFile, 'title')),
60 'type' => sanitize_text_field(Arr::get($downloadableFile, 'type')),
61 'driver' => sanitize_text_field(Arr::get($downloadableFile, 'driver')),
62 'bucket' => sanitize_text_field(Arr::get($downloadableFile, 'bucket')),
63 'file_name' => sanitize_text_field(Arr::get($downloadableFile, 'file_name')),
64 'file_path' => sanitize_text_field(Arr::get($downloadableFile, 'file_path')),
65 'file_url' => sanitize_text_field(Arr::get($downloadableFile, 'file_url')),
66 'settings' => Arr::get($downloadableFile, 'settings'),
67 'serial' => sanitize_text_field(Arr::get($downloadableFile, 'serial')),
68 'file_size' => sanitize_text_field(Arr::get($downloadableFile, 'file_size')),
69 ];
70 }
71
72
73 $fileData['downloadable_files'] = $sanitizedData;
74
75
76 unset($fileData['downloadable_files']['*']);
77
78 $productId = $request->getSafe('postId', 'intval');
79
80 foreach ($fileData['downloadable_files'] as &$file) {
81
82 $file['download_identifier'] = Str::uuid();
83 $file['post_id'] = $productId;
84 $file['file_path'] = !empty($file['file_path']) ? $file['file_path'] : $file['file_name'];
85 $file['file_url'] = !empty($file['file_url']) ? $file['file_url'] : $file['file_name'];
86
87 // file_path is later composed onto the storage directory to read the
88 // file back, so a relative segment stored here would read outside it.
89 // Checked after the file_name fallback, which feeds the same column.
90 if (!StoragePath::isSafe($file['file_path'])) {
91 return $this->sendError([
92 'message' => __('Invalid file path', 'fluent-cart')
93 ], 422);
94 }
95
96 $file['product_variation_id'] = json_encode(
97 Arr::get($file, 'product_variation_id', [])
98 );
99
100 $fileName = $file['file_name'];
101 $fileName = explode('_____fluent-cart_____', $fileName)[0];
102 $fileName = explode('__fluent-cart__', $fileName)[0];
103 $file['file_name'] = $fileName;
104
105 // settings.bucket decides which cloud bucket the download is later signed
106 // against, so it is resolved from the driver's own settings rather than
107 // taken from the request — otherwise any editable product could point at
108 // a sibling bucket the store credentials happen to read.
109 $settings = $this->withResolvedBucket(Arr::get($file, 'settings', []), $file['driver']);
110 if (is_wp_error($settings)) {
111 return $this->sendError([
112 'message' => $settings->get_error_message()
113 ], 422);
114 }
115
116 $file['settings'] = json_encode($settings);
117
118 unset($file['id']);
119 unset($file['bucket']);
120 }
121
122
123 ProductDetail::query()->where('post_id', $productId)->update([
124 'manage_downloadable' => 1
125 ]);
126
127
128 $isCreated = ProductDownload::query()->insert($fileData['downloadable_files']);
129 if ($isCreated) {
130 return $this->sendSuccess([
131 'downloadable_files' => ProductDownloadResource::search(['post_id' => $productId])
132 ]);
133 } else {
134 return $this->sendError([
135 'message' => __('Failed to attach downloadable files', 'fluent-cart')
136 ]);
137 }
138 }
139
140 /**
141 * Replace the caller-supplied settings.bucket with the bucket the selected
142 * driver is actually configured to use.
143 *
144 * @param mixed $settings the submitted settings blob
145 * @param string $driver the submitted driver slug
146 * @return array|\WP_Error WP_Error when the driver is unknown, disabled, or
147 * bucket-backed with no configured bucket
148 */
149 private function withResolvedBucket($settings, $driver)
150 {
151 $settings = is_array($settings) ? $settings : [];
152
153 $bucket = StorageBucketResolver::resolve($driver);
154 if (is_wp_error($bucket)) {
155 return $bucket;
156 }
157
158 $settings['bucket'] = $bucket;
159
160 return $settings;
161 }
162
163 public function getDownloadableUrl($downloadableId)
164 {
165 $productDownload = ProductDownload::query()->findOrFail($downloadableId);
166
167
168 if (empty($productDownload)) {
169 return $this->sendError([
170 'message' => __('Failed to generate downloadable link', 'fluent-cart')
171 ]);
172 }
173
174
175 return $this->sendSuccess([
176 'url' => Helper::generateDownloadFileLink($productDownload, null, 60 * 60 * 24 * 7, true)
177 ]);
178
179
180 }
181
182 public function update(ProductDownloadableFileRequest $request, $downloadId)
183 {
184 $productDownload = ProductDownload::query()->findOrFail($downloadId);
185 $data = $request->getSafe($request->sanitize());
186
187
188 $fileName = Arr::get($data, 'file_name');
189
190 $filePath = Arr::get($data, 'file_path') ?: $fileName;
191 $fileUrl = Arr::get($data, 'file_url') ?: $fileName;
192
193 // Same containment as the sync path: file_path is composed onto the
194 // storage directory when the file is read back.
195 if (!StoragePath::isSafe($filePath)) {
196 return $this->sendError([
197 'message' => __('Invalid file path', 'fluent-cart')
198 ], 422);
199 }
200
201 $productVariationId = Arr::get($data, 'product_variation_id', []);
202 $fileName = explode('_____fluent-cart_____', $fileName)[0];
203 $fileName = explode('__fluent-cart__', $fileName)[0];
204
205
206
207 if (is_array($productVariationId)) {
208 $productVariationId = array_values(array_filter(array_map('intval', $productVariationId)));
209 } else {
210 $productVariationId = [];
211 }
212
213 // Same server-side bucket resolution as the sync path — editing a file must
214 // not be a second way to substitute an unconfigured bucket.
215 $settings = $this->withResolvedBucket(Arr::get($data, 'settings', []), Arr::get($data, 'driver'));
216 if (is_wp_error($settings)) {
217 return $this->sendError([
218 'message' => $settings->get_error_message()
219 ], 422);
220 }
221
222 $productDownload->product_variation_id = $productVariationId;
223 $productDownload->title = Arr::get($data, 'title');
224 $productDownload->type = Arr::get($data, 'type');
225 $productDownload->driver = Arr::get($data, 'driver');
226 $productDownload->file_name = $fileName;
227 $productDownload->file_path = $filePath;
228 $productDownload->file_url = $fileUrl;
229 $productDownload->settings = $settings;
230 $productDownload->serial = Arr::get($data, 'serial');
231
232 if ($productDownload->save()) {
233 return $this->sendSuccess([
234 'message' => __('Product downloadable files updated successfully', 'fluent-cart')
235 ]);
236 } else {
237 return $this->sendError([
238 'message' => __('Failed to update product downloadable files', 'fluent-cart')
239 ]);
240 }
241
242 }
243
244 public function delete($downloadableFileId)
245 {
246 $response = ProductDownloadResource::delete($downloadableFileId);
247 if (is_wp_error($response)) {
248 return $this->sendError([
249 'message' => $response->get_error_message()
250 ]);
251 }
252 return $this->sendSuccess([
253 'message' => $response['message']
254 ]);
255 }
256
257 public function updateDownloadableFile(Request $request, Product $product, ProductDownload $productDownload)
258 {
259
260 }
261 }
262