PluginProbe
ReviewX – Multi-Criteria Reviews for WooCommerce with Google Reviews & Schema / trunk
ReviewX – Multi-Criteria Reviews for WooCommerce with Google Reviews & Schema vtrunk
2.5.1 2.5.0 2.4.1 2.4.0 2.3.11 2.3.10 trunk 1.0.0 1.0.1 1.0.2 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.2 1.1.3 1.1.4 1.1.5 1.1.6 All 143 releases
reviewx / app / Rest / Controllers / DataSyncController.php

DataSyncController.php in ReviewX – Multi-Criteria Reviews for WooCommerce with Google Reviews & Schema trunk, at app/Rest/Controllers/DataSyncController.php

321 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ReviewX\Rest\Controllers;
4
5 \defined("ABSPATH") || exit;
6 use WP_REST_Request;
7 use ReviewX\Models\Site;
8 use ReviewX\Services\CacheServices;
9 use ReviewX\Services\CptService;
10 use ReviewX\Services\DataSyncService;
11 use ReviewX\Services\SettingService;
12 use ReviewX\Utilities\Helper;
13 use Throwable;
14 class DataSyncController
15 {
16 protected SettingService $settingService;
17 protected DataSyncService $dataSyncService;
18 protected CptService $cptService;
19 protected CacheServices $cacheServices;
20 public function __construct()
21 {
22 $this->dataSyncService = new DataSyncService();
23 $this->settingService = new SettingService();
24 $this->cptService = new CptService();
25 $this->cacheServices = new CacheServices();
26 }
27 public function dataSync()
28 {
29 $resp = $this->dataSyncService->dataSync('default');
30 if ($resp) {
31 (new \ReviewX\Services\DiscountService())->clearCouponCodesCache();
32 $this->cacheServices->refreshPendingReviewNoticeSummary();
33 return Helper::rvxApi()->success('Data Sync Success');
34 } else {
35 return Helper::rvxApi()->fails('Data Sync Failed');
36 }
37 }
38 public function dataSynComplete()
39 {
40 // Update all DB settings from API to WP DB
41 $this->updateSettingsOnSync();
42 (new \ReviewX\Services\DiscountService())->clearCouponCodesCache();
43 $this->cacheServices->refreshPendingReviewNoticeSummary();
44 return Site::where("is_saas_sync", 0)->update(['is_saas_sync' => 1]);
45 }
46 public function syncStatus()
47 {
48 \header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
49 \header("Pragma: no-cache");
50 \header("Pragma: no-cache");
51 $response = $this->dataSyncService->syncStatus();
52 if (!empty($response->getApiData()['sync_stats']) && $response->getApiData()['sync_stats'] === 1) {
53 // Update all DB settings from API to WP DB
54 $this->updateSettingsOnSync();
55 (new \ReviewX\Services\DiscountService())->clearCouponCodesCache();
56 $this->cacheServices->refreshPendingReviewNoticeSummary();
57 Site::where("is_saas_sync", 0)->update(['is_saas_sync' => 1]);
58 }
59 return Helper::saasResponse($response);
60 }
61 public function updateSettingsOnSync()
62 {
63 $local_cpt_settings = $this->getStoredCptSettings();
64 $local_widget_settings = $this->settingService->getWidgetSettings();
65 $local_review_settings = $this->getStoredReviewSettingsByPostType($local_cpt_settings);
66 $remote_cpt_settings = $this->refreshCptSettingsFromSaas();
67 $this->syncLocalCptSettingsToSaas($local_cpt_settings, $remote_cpt_settings);
68 $remote_cpt_settings = $this->refreshCptSettingsFromSaas();
69 $this->syncLocalCptStatusesToSaas($local_cpt_settings, $remote_cpt_settings);
70 $remote_cpt_settings = $this->refreshCptSettingsFromSaas();
71 $post_types = $this->resolveReviewSettingsPostTypes($local_review_settings, $remote_cpt_settings);
72 foreach ($post_types as $post_type) {
73 $this->restoreReviewSettingsAfterSync($post_type, $local_review_settings[$post_type] ?? []);
74 }
75 $this->restoreWidgetSettingsAfterSync($local_widget_settings);
76 }
77 public function dataManualSync($request)
78 {
79 try {
80 $response = $this->dataSyncService->dataManualSync($request->get_params());
81 if ($this->isSuccessfulApiResponse($response)) {
82 (new \ReviewX\Services\DiscountService())->clearCouponCodesCache();
83 $this->cacheServices->refreshPendingReviewNoticeSummary();
84 }
85 return Helper::saasResponse($response);
86 } catch (Throwable $e) {
87 return Helper::rvxApi(['error' => $e->getMessage()])->fails('General settings saved failed', $e->getCode());
88 }
89 }
90 public function syncedData(WP_REST_Request $request)
91 {
92 $post_type = $request->get_param('post_type') ?? 'product';
93 // Sanitize just in case:
94 $post_type = \sanitize_key($post_type);
95 if ($post_type === 'product') {
96 $file_name = "shop-bulk-data.jsonl";
97 } else {
98 $file_name = "{$post_type}-cpt-bulk-data.jsonl";
99 }
100 $file_path = \WP_CONTENT_DIR . '/uploads/reviewx/' . $file_name;
101 global $wp_filesystem;
102 if (empty($wp_filesystem)) {
103 require_once \ABSPATH . 'wp-admin/includes/file.php';
104 \WP_Filesystem();
105 }
106 if (!$wp_filesystem->exists($file_path)) {
107 return Helper::rvxApi()->fails('File not found for post_type: ' . $post_type, 404);
108 }
109 \header('Content-Description: File Transfer');
110 \header('Content-Type: application/jsonl');
111 \header('Content-Disposition: attachment; filename="' . \basename($file_path) . '"');
112 \header('Expires: 0');
113 \header('Cache-Control: must-revalidate');
114 \header('Pragma: public');
115 \header('Content-Length: ' . $wp_filesystem->size($file_path));
116 // Flush any buffered output, then stream the file in constant memory. The previous
117 // get_contents() loaded the whole JSONL into memory before echoing, which OOM-ed the
118 // request for large stores so SaaS received a 500 instead of the file — a major cause
119 // of failed syncs.
120 while (\ob_get_level() > 0) {
121 \ob_end_clean();
122 }
123 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- streaming a local export file to the client
124 \readfile($file_path);
125 exit;
126 }
127 private function getStoredCptSettings() : array
128 {
129 $cpt_settings = \get_option('_rvx_cpt_settings', []);
130 return \is_array($cpt_settings) ? $cpt_settings : [];
131 }
132 private function getStoredReviewSettingsByPostType(array $local_cpt_settings) : array
133 {
134 $review_settings = ['product' => $this->settingService->getReviewSettings('product')];
135 foreach ($this->mapCptEntriesByPostType($local_cpt_settings) as $post_type => $cpt_setting) {
136 $review_settings[$post_type] = $this->settingService->getReviewSettings($post_type);
137 }
138 return $review_settings;
139 }
140 private function refreshCptSettingsFromSaas() : array
141 {
142 $response = (new \ReviewX\Rest\Controllers\CptController())->cptGetOnSync();
143 $data = $response[1]['data'] ?? [];
144 if (\is_array($data)) {
145 return $data;
146 }
147 return $this->getStoredCptSettings();
148 }
149 private function syncLocalCptSettingsToSaas(array $local_cpt_settings, array $remote_cpt_settings) : void
150 {
151 $remote_cpt_map = $this->mapCptEntriesByPostType($remote_cpt_settings);
152 foreach ($this->mapCptEntriesByPostType($local_cpt_settings) as $post_type => $local_cpt) {
153 $configuration = $this->normalizeCptConfiguration($local_cpt['configuration'] ?? [], $post_type);
154 if (!$this->hasMeaningfulCptConfiguration($configuration)) {
155 continue;
156 }
157 try {
158 if (isset($remote_cpt_map[$post_type]['uid'])) {
159 $this->cptService->cptUpdate(['uid' => $remote_cpt_map[$post_type]['uid'], 'post_type' => $post_type, 'configuration' => $configuration]);
160 continue;
161 }
162 $this->cptService->cptStore(['post_type' => $post_type, 'configuration' => $configuration]);
163 } catch (Throwable $e) {
164 continue;
165 }
166 }
167 }
168 private function syncLocalCptStatusesToSaas(array $local_cpt_settings, array $remote_cpt_settings) : void
169 {
170 $remote_cpt_map = $this->mapCptEntriesByPostType($remote_cpt_settings);
171 foreach ($this->mapCptEntriesByPostType($local_cpt_settings) as $post_type => $local_cpt) {
172 if (!isset($remote_cpt_map[$post_type]['uid'])) {
173 continue;
174 }
175 $desired_status = $this->normalizeCptStatusValue($local_cpt['status'] ?? null);
176 $current_status = $this->normalizeCptStatusValue($remote_cpt_map[$post_type]['status'] ?? null);
177 if ($desired_status === null || $desired_status === $current_status) {
178 continue;
179 }
180 try {
181 $this->cptService->cptStatusChange(['uid' => $remote_cpt_map[$post_type]['uid'], 'status' => $desired_status]);
182 } catch (Throwable $e) {
183 continue;
184 }
185 }
186 }
187 private function resolveReviewSettingsPostTypes(array $local_review_settings, array $remote_cpt_settings) : array
188 {
189 $post_types = ['product' => 'product'];
190 foreach (\array_keys($local_review_settings) as $post_type) {
191 $post_types[$post_type] = $post_type;
192 }
193 foreach (\array_keys($this->mapCptEntriesByPostType($remote_cpt_settings)) as $post_type) {
194 $post_types[$post_type] = $post_type;
195 }
196 return \array_values($post_types);
197 }
198 private function restoreReviewSettingsAfterSync(string $post_type, array $local_review_settings) : void
199 {
200 $review_response = (new \ReviewX\Rest\Controllers\SettingController())->getApiReviewSettingsOnSync($post_type);
201 $remote_review_settings = $review_response['data']['review_settings'] ?? [];
202 if ($this->settingService->hasMeaningfulReviewSettings($local_review_settings)) {
203 $merged_review_settings = $this->settingService->mergeReviewSettingsForSync(\is_array($remote_review_settings) ? $remote_review_settings : [], $local_review_settings);
204 $payload = $merged_review_settings['reviews'] ?? $merged_review_settings;
205 $payload['post_type'] = $post_type;
206 try {
207 $response = $this->settingService->saveApiReviewSettings($payload);
208 if ($this->isSuccessfulApiResponse($response)) {
209 $saved_review_settings = $response->getApiData()['review_settings'] ?? [];
210 $this->settingService->syncWooCommerceOptionsFromReviewSettings($saved_review_settings, $post_type);
211 $this->settingService->updateReviewSettingsOnSync($saved_review_settings, $post_type);
212 return;
213 }
214 } catch (Throwable $e) {
215 return;
216 }
217 return;
218 }
219 if ($this->settingService->hasMeaningfulReviewSettings($remote_review_settings)) {
220 $this->settingService->syncWooCommerceOptionsFromReviewSettings($remote_review_settings, $post_type);
221 $this->settingService->updateReviewSettingsOnSync($remote_review_settings, $post_type);
222 }
223 }
224 private function restoreWidgetSettingsAfterSync(array $local_widget_settings) : void
225 {
226 $widget_response = (new \ReviewX\Rest\Controllers\SettingController())->getApiWidgetSettingsOnSync();
227 $remote_widget_settings = $widget_response['data']['widget_settings'] ?? [];
228 if ($this->settingService->hasMeaningfulWidgetSettings($local_widget_settings)) {
229 $merged_widget_settings = $this->settingService->mergeSettingsWithLocalPreference(\is_array($remote_widget_settings) ? $remote_widget_settings : [], $local_widget_settings);
230 try {
231 $response = $this->settingService->saveWidgetSettings($merged_widget_settings);
232 if ($this->isSuccessfulApiResponse($response)) {
233 $saved_widget_settings = $response->getApiData()['widget_settings'] ?? [];
234 $this->settingService->updateWidgetSettings($saved_widget_settings);
235 return;
236 }
237 } catch (Throwable $e) {
238 return;
239 }
240 return;
241 }
242 if ($this->settingService->hasMeaningfulWidgetSettings($remote_widget_settings)) {
243 $this->settingService->updateWidgetSettings($remote_widget_settings);
244 }
245 }
246 private function mapCptEntriesByPostType(array $cpt_settings) : array
247 {
248 $mapped_cpts = [];
249 $cpt_reviews = $cpt_settings['reviews'] ?? [];
250 if (!\is_array($cpt_reviews)) {
251 return $mapped_cpts;
252 }
253 foreach ($cpt_reviews as $cpt_setting) {
254 $post_type = \strtolower((string) ($cpt_setting['post_type'] ?? ''));
255 if ($post_type === '' || $post_type === 'product') {
256 continue;
257 }
258 $mapped_cpts[$post_type] = $cpt_setting;
259 }
260 return $mapped_cpts;
261 }
262 private function hasMeaningfulCptConfiguration(array $configuration) : bool
263 {
264 return !empty($configuration['post_type']) && !empty($configuration['layout_type']);
265 }
266 private function normalizeCptConfiguration($configuration, string $post_type) : array
267 {
268 $configuration = \is_array($configuration) ? $configuration : [];
269 $configuration = $this->normalizeLegacyCptMulticriteria($configuration);
270 if (empty($configuration['post_type'])) {
271 $configuration['post_type'] = $post_type;
272 }
273 if (empty($configuration['post_type_name'])) {
274 $configuration['post_type_name'] = \ucwords(\str_replace(['-', '_'], ' ', $post_type));
275 }
276 if (empty($configuration['layout_type'])) {
277 $configuration['layout_type'] = 'grid';
278 }
279 return $configuration;
280 }
281 private function normalizeLegacyCptMulticriteria(array $configuration) : array
282 {
283 if (isset($configuration['multicriteria']) || !isset($configuration['multi_criteria_reviews']) || !\is_array($configuration['multi_criteria_reviews'])) {
284 return $configuration;
285 }
286 $legacy_multicriteria = $configuration['multi_criteria_reviews'];
287 $criteria_items = $legacy_multicriteria['criteria'] ?? $legacy_multicriteria['criterias'] ?? [];
288 $criteria_keys = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
289 $criterias = [];
290 foreach ($criteria_items as $index => $criteria_item) {
291 if (\is_array($criteria_item)) {
292 $key = \sanitize_key($criteria_item['key'] ?? $criteria_keys[$index] ?? '');
293 $name = \sanitize_text_field((string) ($criteria_item['name'] ?? ''));
294 } else {
295 $key = \sanitize_key($criteria_keys[$index] ?? '');
296 $name = \sanitize_text_field((string) $criteria_item);
297 }
298 if ($key === '' || $name === '') {
299 continue;
300 }
301 $criterias[] = ['key' => $key, 'name' => $name];
302 }
303 $configuration['multicriteria'] = ['enable' => !empty($legacy_multicriteria['enabled']) || !empty($legacy_multicriteria['enable']), 'criterias' => $criterias];
304 return $configuration;
305 }
306 private function normalizeCptStatusValue($status) : ?int
307 {
308 if ($status === 1 || $status === '1' || $status === 'Enabled') {
309 return 1;
310 }
311 if ($status === 0 || $status === '0' || $status === 'Disabled') {
312 return 0;
313 }
314 return null;
315 }
316 private function isSuccessfulApiResponse($response) : bool
317 {
318 return \is_object($response) && \method_exists($response, 'getStatusCode') && $response->getStatusCode() >= 200 && $response->getStatusCode() < 300;
319 }
320 }
321