Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/reviewx/app/Api/BaseApi.php
+++ b/reviewx/app/Api/BaseApi.php
@@ -35,6 +35,6 @@
*/
public function getDefaultHeaders() : array
{
- return ['Authorization' => 'Bearer ' . Helper::getAuthToken(), 'X-Auth-Token' => 'Bearer ' . Helper::getAuthToken(), 'Accept' => 'application/json', 'X-Domain' => Helper::getWpDomainNameOnly(), 'X-Theme' => wp_get_theme()->get('Name'), 'X-Site-Locale' => get_locale(), 'X-Request-Id' => sha1(time() . Client::getUid()), 'X-Wp-Version' => get_bloginfo("version"), 'X-Reviewx-Version' => RVX_VERSION, 'X-Environment' => Helper::plugin()->isProduction() ? 'production' : 'development'];
+ return ['Accept' => 'application/json', 'Authorization' => 'Bearer ' . Helper::getAuthToken(), 'X-Domain' => Helper::getWpDomainNameOnly(), 'X-Theme' => wp_get_theme()->get('Name'), 'X-Site-Locale' => get_locale(), 'X-Request-Id' => sha1(time() . Client::getUid()), 'X-Wp-Version' => get_bloginfo("version"), 'X-Reviewx-Version' => RVX_VERSION, 'X-Environment' => Helper::plugin()->isProduction() ? 'production' : 'development'];
}
}
--- a/reviewx/app/Api/WpApi.php
+++ b/reviewx/app/Api/WpApi.php
@@ -72,7 +72,6 @@
$headers = ['Content-Type' => 'application/json'];
if ($this->token) {
$headers['Authorization'] = 'Bearer ' . $this->token;
- $headers['X-Auth-Token'] = 'Bearer ' . $this->token;
}
return $headers;
}
--- a/reviewx/app/CPT/CommentsRatingColumn.php
+++ b/reviewx/app/CPT/CommentsRatingColumn.php
@@ -2,12 +2,13 @@
namespace RvxCPT;
+use RvxCPTCptHelper;
class CommentsRatingColumn
{
protected $cptHelper;
public function __construct()
{
- $this->cptHelper = new RvxCPTCptHelper();
+ $this->cptHelper = new CptHelper();
}
/**
* Add a new column to the comments page for the review rating.
--- a/reviewx/app/CPT/CptAverageRating.php
+++ b/reviewx/app/CPT/CptAverageRating.php
@@ -2,6 +2,8 @@
namespace RvxCPT;
+use WP_Post;
+use RvxCPTCptHelper;
class CptAverageRating
{
/**
@@ -49,7 +51,7 @@
{
// Check the post type.
$post_type = get_post_type($post_id);
- $enabled_post_types = (new RvxCPTCptHelper())->usedCPT('used');
+ $enabled_post_types = (new CptHelper())->usedCPT('used');
unset($enabled_post_types['product']);
// Unset Product
if (!isset($enabled_post_types[$post_type])) {
@@ -67,6 +69,7 @@
$average_rating = round(array_sum($ratings) / count($ratings), 2);
// Store the average rating as post meta, ensuring it's a float.
update_post_meta($post_id, 'rvx_avg_rating', (float) $average_rating);
+ update_post_meta($post_id, 'rating', (float) $average_rating);
} else {
// No ratings found, set the meta key to 0.00.
update_post_meta($post_id, 'rvx_avg_rating', (float) $average_rating);
@@ -76,7 +79,7 @@
* Hook into the post save action to add the rvx_avg_rating meta key.
*
* @param int $post_id The post ID.
- * @param WP_Post $post The post object.
+ * @param WP_Post $post The post object.
*/
public static function rvx_avg_rating_on_save($post_id, $post)
{
@@ -86,7 +89,7 @@
}
// Check the post type.
$post_type = get_post_type($post_id);
- $enabled_post_types = (new RvxCPTCptHelper())->usedCPT('used');
+ $enabled_post_types = (new CptHelper())->usedCPT('used');
unset($enabled_post_types['product']);
// Unset Product
if (!isset($enabled_post_types[$post_type])) {
@@ -96,6 +99,7 @@
if (!get_post_meta($post_id, 'rvx_avg_rating', true)) {
// Add the rvx_avg_rating meta key with an initial value (0.00)
update_post_meta($post_id, 'rvx_avg_rating', (float) 0.0);
+ update_post_meta($post_id, 'rating', (float) 0.0);
}
}
}
--- a/reviewx/app/CPT/CptCommentsLinkMeta.php
+++ b/reviewx/app/CPT/CptCommentsLinkMeta.php
@@ -2,12 +2,13 @@
namespace RvxCPT;
+use RvxCPTCptHelper;
class CptCommentsLinkMeta
{
protected $cptHelper;
public function __construct()
{
- $this->cptHelper = new RvxCPTCptHelper();
+ $this->cptHelper = new CptHelper();
}
/**
* Modify the comment count output with custom review logic
--- a/reviewx/app/CPT/CptReviewAddReply.php
+++ b/reviewx/app/CPT/CptReviewAddReply.php
@@ -1,27 +0,0 @@
-<?php
-
-namespace RvxCPT;
-
-use RvxServicesReviewService;
-use RvxUtilitiesAuthClient;
-class CptReviewAddReply
-{
- public function __invoke($id, $data)
- {
- $updatedData = $this->prepareData($id, $data);
- $reviewService = new ReviewService();
- $response = $reviewService->updateReview($updatedData, $data);
- }
- public function prepareData($id, $data)
- {
- $post_id = $data['comment_post_ID'];
- $post_type = get_post_type($post_id);
- // Retrieve the rating.
- $rating = get_comment_meta($id, 'rating', true);
- if ($post_type !== 'product') {
- // For other post types (non-product), round to two decimal places.
- $rating = (float) round($rating, 2);
- }
- return ['wp_id' => $id, 'wp_post_id' => $post_id, 'comment_approved' => $data['comment_approved'], 'rating' => $rating, 'reviewer_email' => $data['comment_author_email'], 'reviewer_name' => $data['comment_author'], 'feedback' => $data['comment_content'], 'date' => current_time('mysql', true), 'customer_id' => $data['user_id'], 'wp_unique_id' => Client::getUid() . '-' . $id, 'woocommerce_update' => false];
- }
-}
--- a/reviewx/app/Handlers/IsAlreadySyncSucess.php
+++ b/reviewx/app/Handlers/IsAlreadySyncSucess.php
@@ -4,17 +4,15 @@
class IsAlreadySyncSucess
{
- public function __construct()
+ public function resetSyncFlag()
{
add_action('admin_footer', function () {
- if (get_option('rvx_reset_sync_flag')) {
- ?>
- <script>
- localStorage.setItem('isAlreadySyncSuccess', 'false');
- </script>
- <?php
- delete_option('rvx_reset_sync_flag');
- }
+ ?>
+ <script>
+ localStorage.setItem('isAlreadySyncSuccess', 'false');
+ </script>
+ <?php
});
+ delete_transient('rvx_reset_sync_flag');
}
}
--- a/reviewx/app/Handlers/PluginActivatedHandler.php
+++ b/reviewx/app/Handlers/PluginActivatedHandler.php
@@ -6,56 +6,66 @@
use RvxCPTCptHelper;
use RvxServicesApiLoginService;
use RvxServicesDataSyncService;
-use RvxUtilitiesHelper;
use RvxWPDrillContractsInvokableContract;
use RvxWPDrillDBMigrationMigrator;
use RvxServicesCacheServices;
+use Exception;
class PluginActivatedHandler implements InvokableContract
{
private Migrator $migrator;
private DataSyncService $dataSyncService;
private CacheServices $cacheServices;
+ private LoginService $loginService;
public function __construct(Migrator $migrator)
{
$this->migrator = $migrator;
$this->dataSyncService = new DataSyncService();
$this->cacheServices = new CacheServices();
+ $this->loginService = new LoginService();
}
public function __invoke()
{
- $this->migrator->run();
- global $wpdb;
- $rvxSites = $wpdb->prefix . 'rvx_sites';
- $uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
- if ($uid) {
- // Change rvx_sites table is_saas_sync to 0
- $wpdb->update(
- $rvxSites,
- ['is_saas_sync' => 0],
- ['uid' => $uid],
- ['%d'],
- // format for is_saas_sync (integer)
- ['%s']
- );
- // Set the localStorage isAlreadySyncSuccess to false
- update_option('rvx_reset_sync_flag', true);
- (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 1, 'plugin_version' => RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
- $dataResponse = $this->dataSyncService->dataSync('login', 'product');
- if (!$dataResponse) {
- return Helper::rvxApi(['error' => 'Data sync fails'])->fails('Data sync fails', $dataResponse->getStatusCode());
+ add_action('activated_plugin', function ($plugin) {
+ if (RVX_DIR_NAME . '/reviewx.php' !== $plugin) {
+ return;
}
- // Sleep for 1 seconds
- sleep(1);
- // Upload CPT data to Saas
- $enabled_post_types = (new CptHelper())->usedCPTOnSync('used');
- unset($enabled_post_types['product']);
- // Exclude 'product' post type
- // Loop through each post type and hook into the actions/filters dynamically
- foreach ($enabled_post_types as $post_type) {
- $this->dataSyncService->dataSync('login', $post_type);
+ global $wpdb;
+ // Initialize tables and reset sync flag
+ (new RvxHandlersRvxInitLoadReviewxCreateSiteTable())->init();
+ (new RvxHandlersIsAlreadySyncSucess())->resetSyncFlag();
+ $this->migrator->run();
+ $rvxSites = $wpdb->prefix . 'rvx_sites';
+ $uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
+ if ($uid) {
+ // Mark as not synced initially
+ $wpdb->update($rvxSites, ['is_saas_sync' => 0], ['uid' => $uid], ['%d'], ['%s']);
+ set_transient('rvx_reset_sync_flag', true);
+ try {
+ // Attempt SaaS activation
+ (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 1, 'plugin_version' => defined('RVX_VERSION') ? RVX_VERSION : 'unknown', 'wp_version' => get_bloginfo('version')]);
+ // Start initial sync
+ $dataResponse = $this->dataSyncService->dataSync('login', 'product');
+ if ($dataResponse) {
+ sleep(1);
+ $enabled_post_types = (new CptHelper())->usedCPTOnSync('used');
+ unset($enabled_post_types['product']);
+ foreach ($enabled_post_types as $post_type) {
+ $this->dataSyncService->dataSync('login', $post_type);
+ }
+ }
+ } catch (Exception $e) {
+ // Clean up if AuthApi fails
+ $wpdb->query("TRUNCATE TABLE {$rvxSites}");
+ if (defined('WP_DEBUG') && WP_DEBUG) {
+ error_log('[ReviewX] Activation warning: AuthApi connection failed - ' . $e->getMessage());
+ }
+ }
}
+ // Always clean cache and redirect, even if UID or API failed
$this->cacheServices->removeCache();
- (new LoginService())->resetPostMeta();
- }
+ $this->loginService->resetPostMeta();
+ wp_safe_redirect(admin_url('admin.php?page=reviewx'));
+ exit;
+ }, 15, 1);
}
}
--- a/reviewx/app/Handlers/PluginDeactivatedHandler.php
+++ b/reviewx/app/Handlers/PluginDeactivatedHandler.php
@@ -4,26 +4,34 @@
use RvxApiAuthApi;
use RvxWPDrillContractsInvokableContract;
+use Exception;
class PluginDeactivatedHandler implements InvokableContract
{
- public function __invoke()
+ public function __invoke() : void
{
global $wpdb;
$rvxSites = $wpdb->prefix . 'rvx_sites';
$uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
if ($uid) {
// Change rvx_sites table is_saas_sync to 0
- $wpdb->update(
- $rvxSites,
- ['is_saas_sync' => 0],
- ['uid' => $uid],
- ['%d'],
- // format for is_saas_sync (integer)
- ['%s']
- );
- // Set the localStorage isAlreadySyncSuccess to false
- update_option('rvx_reset_sync_flag', true);
- (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 0, 'plugin_version' => $plugin_version ?? RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
+ $wpdb->update($rvxSites, ['is_saas_sync' => 0], ['uid' => $uid], ['%d'], ['%s']);
+ // Mark sync flag locally
+ set_transient('rvx_reset_sync_flag', true);
+ try {
+ // Attempt API call — skip gracefully on failure
+ (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 0, 'plugin_version' => defined('RVX_VERSION') ? RVX_VERSION : 'unknown', 'wp_version' => get_bloginfo('version')]);
+ } catch (Exception $e) {
+ // Log quietly instead of breaking plugin disable process
+ if (defined('WP_DEBUG') && WP_DEBUG) {
+ error_log('[ReviewX] PluginDeactivatedHandler: API call failed - ' . $e->getMessage());
+ }
+ // continue silently
+ }
}
+ // Always clean up local data, regardless of API status
+ (new RvxServicesCacheServices())->removeCache();
+ (new RvxServicesApiLoginService())->resetPostMeta();
+ // Continue plugin deactivation
+ return;
}
}
--- a/reviewx/app/Handlers/PluginUninstallerHandler.php
+++ b/reviewx/app/Handlers/PluginUninstallerHandler.php
@@ -3,6 +3,7 @@
namespace RvxHandlers;
use RvxApiAuthApi;
+use Exception;
class PluginUninstallerHandler
{
public static function handleUninstall()
@@ -11,8 +12,22 @@
$rvxSites = $wpdb->prefix . 'rvx_sites';
$uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
if ($uid) {
- (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 2, 'plugin_version' => $plugin_version ?? RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
+ try {
+ // Try to inform the API about uninstall
+ (new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 2, 'plugin_version' => defined('RVX_VERSION') ? RVX_VERSION : 'unknown', 'wp_version' => get_bloginfo('version')]);
+ } catch (Exception $e) {
+ // If API fails, do not interrupt uninstall
+ if (defined('WP_DEBUG') && WP_DEBUG) {
+ error_log('[ReviewX] PluginUninstallerHandler: API call failed - ' . $e->getMessage());
+ }
+ // continue silently
+ }
}
+ // Always clean up local data, regardless of API status
$wpdb->query("TRUNCATE TABLE {$rvxSites}");
+ (new RvxServicesCacheServices())->removeCache();
+ (new RvxServicesApiLoginService())->resetPostMeta();
+ // Continue uninstall cleanly
+ return true;
}
}
--- a/reviewx/app/Handlers/RichSchema/WoocommerceRichSchemaHandler.php
+++ b/reviewx/app/Handlers/RichSchema/WoocommerceRichSchemaHandler.php
@@ -70,7 +70,7 @@
}
}
if ($reviewCount > 0 && $averageRating > 0) {
- $trueAverage = round($averageRating / $reviewCount, 1);
+ $trueAverage = round($averageRating / $reviewCount, 2);
$markup['aggregateRating'] = ['@type' => 'AggregateRating', 'ratingValue' => $trueAverage, 'reviewCount' => $reviewCount];
$markup['review'] = $reviewItems;
}
--- a/reviewx/app/Handlers/RvxInit/LoadReviewxCreateSiteTable.php
+++ b/reviewx/app/Handlers/RvxInit/LoadReviewxCreateSiteTable.php
@@ -7,7 +7,7 @@
/**
* Invokes the process to ensure the table exists.
*/
- public function __invoke()
+ public function init()
{
if (!$this->is_table_exists()) {
$this->create_table();
--- a/reviewx/app/Handlers/RvxInit/RedirectReviewxHandler.php
+++ b/reviewx/app/Handlers/RvxInit/RedirectReviewxHandler.php
@@ -1,14 +0,0 @@
-<?php
-
-namespace RvxHandlersRvxInit;
-
-class RedirectReviewxHandler
-{
- public function __invoke($plugin)
- {
- if (RVX_DIR_NAME . '/reviewx.php' === $plugin) {
- wp_safe_redirect(admin_url() . 'admin.php?page=reviewx');
- exit;
- }
- }
-}
--- a/reviewx/app/Handlers/WooReviewTableHandler.php
+++ b/reviewx/app/Handlers/WooReviewTableHandler.php
@@ -6,6 +6,7 @@
use RvxApiReviewsApi;
use RvxUtilitiesAuthClient;
use RvxServicesCacheServices;
+use WP_Screen;
class WooReviewTableHandler
{
protected $cacheServices;
@@ -15,15 +16,17 @@
}
public function __invoke($new_status, $old_status, $comment)
{
- $screen = get_current_screen();
- if ($screen instanceof RvxHandlersWP_Screen || $screen->id == 'edit-comments') {
- $comment_id = $comment->comment_ID;
- $this->handleAction($new_status, $old_status, $comment_id);
- }
- if (wp_doing_ajax()) {
- $comment_id = $comment->comment_ID;
- $this->handleAction($new_status, $old_status, $comment_id);
- }
+ add_action('init', function () use($new_status, $old_status, $comment) {
+ $screen = get_current_screen();
+ if ($screen instanceof WP_Screen || $screen->id == 'edit-comments') {
+ $comment_id = $comment->comment_ID;
+ $this->handleAction($new_status, $old_status, $comment_id);
+ }
+ if (wp_doing_ajax()) {
+ $comment_id = $comment->comment_ID;
+ $this->handleAction($new_status, $old_status, $comment_id);
+ }
+ });
}
public function handleAction($new_status, $old_status, $comment_id)
{
--- a/reviewx/app/Import/Judgeme/JudgemeReviewsImport.php
+++ b/reviewx/app/Import/Judgeme/JudgemeReviewsImport.php
@@ -48,58 +48,83 @@
}
public function judgemeCSVdownload($request) : array
{
- $cookie_file = tempnam($this->tmpDir, 'judgeme_cookie_');
- $csvPath = $this->getCsvFilePath();
- if (file_exists($csvPath)) {
- if (file_exists($cookie_file)) {
- @unlink($cookie_file);
- }
- return ['success' => true, 'message' => 'Judgeme Export CSV file already exists.'];
- }
- if (empty($this->judgemeDomain) || empty($this->judgemeToken)) {
- return ['success' => false, 'message' => 'Judge.me credentials are missing.'];
- }
- $login_url = $this->generateJudgemeLoginURL();
- $export_url = 'https://app.judge.me/reviews/export?export_mode=published&from_settings=true';
- if (empty($login_url)) {
- return ['success' => false, 'message' => 'Login URL could not be generated.'];
- }
- try {
- // Step 1: Authenticate (gets and stores session cookies)
- $ch = curl_init($login_url);
- curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_COOKIEJAR => $cookie_file, CURLOPT_COOKIEFILE => $cookie_file]);
- curl_exec($ch);
- // hits login URL, stores auth cookies in $cookie_file
- curl_close($ch);
- // Step 2: Download CSV using the same cookies
- $ch = curl_init($export_url);
- curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_COOKIEJAR => $cookie_file, CURLOPT_COOKIEFILE => $cookie_file, CURLOPT_USERAGENT => 'Mozilla/5.0']);
- $csv_data = curl_exec($ch);
- $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $curl_error = curl_error($ch);
- curl_close($ch);
- if ($http_code !== 200 || empty($csv_data)) {
- return ['success' => false, 'message' => 'Failed to download CSV.'];
- }
- // Normalize line endings & strip UTF-8 BOM
- $csv_data = str_replace(["rn", "r"], "n", $csv_data);
- $csv_data = preg_replace('/^\xEF\xBB\xBF/', '', $csv_data);
- // Check if CSV has only header (no data rows)
- $lines = explode("n", $csv_data);
- if (count($lines) <= 1) {
- // means only header or empty
- return ['success' => false, 'message' => 'CSV file contains only headers and no reviews.'];
- }
- // Directly save CSV to file
- if (file_put_contents($this->csvFilePath, $csv_data) === false) {
- return ['success' => false, 'message' => 'Failed to save downloaded CSV file.'];
- }
- return ['success' => true, 'message' => 'Judgeme Export CSV file downloaded successfully.'];
- } finally {
- if (file_exists($cookie_file)) {
- @unlink($cookie_file);
- }
- }
+ return ['success' => false, 'message' => 'Judgeme CSV download can't be automatted anymore! Please try uploading CSV manually.'];
+ // $cookie_file = tempnam($this->tmpDir, 'judgeme_cookie_');
+ // $csvPath = $this->getCsvFilePath();
+ // if (file_exists($csvPath)) {
+ // if (file_exists($cookie_file)) {
+ // @unlink($cookie_file);
+ // }
+ // return [
+ // 'success' => true,
+ // 'message' => 'Judgeme Export CSV file already exists.',
+ // ];
+ // }
+ // if (empty($this->judgemeDomain) || empty($this->judgemeToken)) {
+ // return ['success' => false, 'message' => 'Judge.me credentials are missing.'];
+ // }
+ // $login_url = $this->generateJudgemeLoginURL();
+ // $export_url = 'https://app.judge.me/reviews/export?export_mode=published&from_settings=true';
+ // if (empty($login_url)) {
+ // return ['success' => false, 'message' => 'Login URL could not be generated.'];
+ // }
+ // try {
+ // // Step 1: Authenticate (gets and stores session cookies)
+ // $ch = curl_init($login_url);
+ // curl_setopt_array($ch, [
+ // CURLOPT_RETURNTRANSFER => true,
+ // CURLOPT_FOLLOWLOCATION => true,
+ // CURLOPT_COOKIEJAR => $cookie_file,
+ // CURLOPT_COOKIEFILE => $cookie_file,
+ // ]);
+ // curl_exec($ch); // hits login URL, stores auth cookies in $cookie_file
+ // curl_close($ch);
+ // // Step 2: Download CSV using the same cookies
+ // $ch = curl_init($export_url);
+ // curl_setopt_array($ch, [
+ // CURLOPT_RETURNTRANSFER => true,
+ // CURLOPT_FOLLOWLOCATION => true,
+ // CURLOPT_COOKIEJAR => $cookie_file,
+ // CURLOPT_COOKIEFILE => $cookie_file,
+ // CURLOPT_USERAGENT => 'Mozilla/5.0'
+ // ]);
+ // $csv_data = curl_exec($ch);
+ // $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ // $curl_error = curl_error($ch);
+ // curl_close($ch);
+ // if ($http_code !== 200 || empty($csv_data)) {
+ // return [
+ // 'success' => false,
+ // 'message' => 'Failed to download CSV.'
+ // ];
+ // }
+ // // Normalize line endings & strip UTF-8 BOM
+ // $csv_data = str_replace(["rn", "r"], "n", $csv_data);
+ // $csv_data = preg_replace('/^xEFxBBxBF/', '', $csv_data);
+ // // Check if CSV has only header (no data rows)
+ // $lines = explode("n", $csv_data);
+ // if (count($lines) <= 1) { // means only header or empty
+ // return [
+ // 'success' => false,
+ // 'message' => 'CSV file contains only headers and no reviews.'
+ // ];
+ // }
+ // // Directly save CSV to file
+ // if (file_put_contents($this->csvFilePath, $csv_data) === false) {
+ // return [
+ // 'success' => false,
+ // 'message' => 'Failed to save downloaded CSV file.'
+ // ];
+ // }
+ // return [
+ // 'success' => true,
+ // 'message' => 'Judgeme Export CSV file downloaded successfully.'
+ // ];
+ // } finally {
+ // if (file_exists($cookie_file)) {
+ // @unlink($cookie_file);
+ // }
+ // }
}
public function judgemeCSVUpload($request) : array
{
@@ -223,7 +248,8 @@
$uid = $wpdb->get_var("SELECT uid FROM {$rvxSites} ORDER BY id DESC LIMIT 1");
if ($uid) {
$wpdb->update($rvxSites, ['is_saas_sync' => 0], ['uid' => $uid], ['%d'], ['%s']);
- update_option('rvx_reset_sync_flag', true);
+ // Set the localStorage isAlreadySyncSuccess
+ set_transient('rvx_reset_sync_flag', true);
(new AuthApi())->changePluginStatus(['site_uid' => $uid, 'status' => 1, 'plugin_version' => RVX_VERSION, 'wp_version' => get_bloginfo('version')]);
$dataResponse = $this->dataSyncService->dataSync('default', 'product');
if (!$dataResponse) {
--- a/reviewx/app/Providers/PluginServiceProvider.php
+++ b/reviewx/app/Providers/PluginServiceProvider.php
@@ -32,7 +32,6 @@
use RvxHandlersReplyCommentHandler;
use RvxHandlersRichSchemaWoocommerceRichSchemaHandler;
use RvxHandlersRvxInitPageBuilderHandler;
-use RvxHandlersRvxInitRedirectReviewxHandler;
use RvxHandlersRvxInitResetProductMetaHandler;
use RvxHandlersRvxInitReviewXoldPluginDeactivateHandler;
use RvxHandlersRvxInitUpgradeReviewxDeactiveProHandler;
@@ -68,13 +67,19 @@
}
public function boot() : void
{
+ add_action('plugins_loaded', function () {
+ if (get_transient('rvx_reset_sync_flag')) {
+ // LocalStorage is reset when the sync flag is set true
+ (new RvxHandlersIsAlreadySyncSucess())->resetSyncFlag();
+ }
+ require_once ABSPATH . 'wp-admin/includes/image.php';
+ }, 15, 1);
add_action('rest_api_init', function () {
WpUser::setLoggedInStatus(is_user_logged_in());
WpUser::setAbility(is_user_logged_in() && (current_user_can('manage_options') || current_user_can('edit_others_posts') || current_user_can('manage_woocommerce')) ? true : false);
- });
+ }, 5);
add_action('init', new ReviewXoldPluginDeactivateHandler(), 10);
- add_action('activated_plugin', new RedirectReviewxHandler(), 15, 1);
- add_action('plugins_loaded', new PageBuilderHandler(), 20);
+ add_action('init', new PageBuilderHandler(), 20);
// add_action('upgrader_process_complete', new ResetProductMetaHandler(), 5, 2);
add_action('upgrader_process_complete', new UpgradeReviewxDeactiveProHandler(), 10, 2);
// add_action('admin_notices', [new ReviewxAdminNoticeHandler(), 'adminNoticeHandler']);
@@ -140,21 +145,26 @@
* CPT comments / reviews
*/
add_action('wp_insert_comment', function ($comment_id, $comment) {
- if ($comment) {
+ if ($comment && $comment->comment_post_ID) {
CptAverageRating::update_average_rating($comment->comment_post_ID);
}
- }, 10, 2);
- add_action('comment_post', [CptAverageRating::class, 'handle_comment_rating'], 10, 2);
- add_action('comment_post', [CptAverageRating::class, 'handle_comment_rating'], 10, 3);
- add_action('get_comments_number', [new CptCommentsLinkMeta(), 'replace_total_comments_count'], 10, 2);
+ }, 999, 2);
+ add_action('comment_post', [CptAverageRating::class, 'handle_comment_rating'], 999, 3);
+ add_action('get_comments_number', [new CptCommentsLinkMeta(), 'replace_total_comments_count'], 999, 2);
add_action('edit_comment', function ($comment_id) {
$comment = get_comment($comment_id);
- if ($comment) {
+ if ($comment && $comment->comment_post_ID) {
CptAverageRating::update_average_rating($comment->comment_post_ID);
}
- });
- add_action('wp_set_comment_status', [CptAverageRating::class, 'handle_comment_status_change'], 10, 2);
- add_action('save_post', [CptAverageRating::class, 'update_average_rating'], 10, 2);
+ }, 999);
+ add_action('wp_set_comment_status', [CptAverageRating::class, 'handle_comment_status_change'], 999, 2);
+ add_action('save_post', [CptAverageRating::class, 'update_average_rating'], 999, 3);
+ add_action('deleted_comment', function ($comment_id) {
+ $comment = get_comment($comment_id);
+ if ($comment && $comment->comment_post_ID) {
+ CptAverageRating::update_average_rating($comment->comment_post_ID);
+ }
+ }, 999);
/**
* Rich Schema
*/
@@ -196,6 +206,6 @@
// Load plugin textdomain
add_action('init', function () {
load_plugin_textdomain('reviewx', false, dirname(plugin_basename(__FILE__)) . '/languages');
- });
+ }, 15, 5);
}
}
--- a/reviewx/app/Rest/Controllers/AuthController.php
+++ b/reviewx/app/Rest/Controllers/AuthController.php
@@ -76,8 +76,8 @@
}
$this->cacheServices->removeCache();
$this->loginService->resetPostMeta();
- // Set the localStorage isAlreadySyncSuccess to false
- update_option('rvx_reset_sync_flag', true);
+ // Set the localStorage isAlreadySyncSuccess
+ set_transient('rvx_reset_sync_flag', true);
return Helper::saasResponse($response);
} catch (Exception $e) {
$errorCode = $e->getCode() === 0 ? Response::HTTP_INTERNAL_SERVER_ERROR : $e->getCode();
@@ -125,8 +125,8 @@
}
$this->cacheServices->removeCache();
$this->loginService->resetPostMeta();
- // Set the localStorage isAlreadySyncSuccess to false
- update_option('rvx_reset_sync_flag', true);
+ // Set the localStorage isAlreadySyncSuccess
+ set_transient('rvx_reset_sync_flag', true);
return Helper::saasResponse($response);
} catch (Exception $e) {
$errorCode = $e->getCode() === 0 ? Response::HTTP_INTERNAL_SERVER_ERROR : $e->getCode();
@@ -214,8 +214,8 @@
$this->cacheServices->removeCache();
$this->loginService->resetPostMeta();
$this->removeUserSettingsFormLocal();
- // Set the localStorage isAlreadySyncSuccess to false
- update_option('rvx_reset_sync_flag', true);
+ // Set the localStorage isAlreadySyncSuccess
+ set_transient('rvx_reset_sync_flag', true);
return Helper::saasResponse($response);
} catch (Exception $e) {
$errorCode = $e->getCode() === 0 ? Response::HTTP_INTERNAL_SERVER_ERROR : $e->getCode();
--- a/reviewx/app/Rest/Controllers/DashboardController.php
+++ b/reviewx/app/Rest/Controllers/DashboardController.php
@@ -4,6 +4,7 @@
use RvxServicesDashboardServices;
use RvxUtilitiesHelper;
+use Throwable;
use RvxWPDrillContractsInvokableContract;
use RvxWPDrillResponse;
class DashboardController implements InvokableContract
@@ -39,6 +40,18 @@
return Helper::getApiResponse($resp);
}
/**
+ * @return WPDrillResponse
+ */
+ public function requestUserData()
+ {
+ try {
+ $response = $this->dashboardServices->requestUserData();
+ return Helper::rvxApi($response)->success('Site data Retrieved Successfully');
+ } catch (Throwable $e) {
+ return Helper::rvxApi(['error' => $e->getMessage()])->fails('Site data Retrieval Failed', $e->getCode());
+ }
+ }
+ /**
* @param $request
* @return Response
*/
--- a/reviewx/app/Rest/Controllers/DataSyncController.php
+++ b/reviewx/app/Rest/Controllers/DataSyncController.php
@@ -2,6 +2,7 @@
namespace RvxRestControllers;
+use WP_REST_Request;
use RvxCPTCptHelper;
use RvxModelsSite;
use RvxServicesDataSyncService;
@@ -36,6 +37,7 @@
{
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
+ header("Pragma: no-cache");
$response = $this->dataSyncService->syncStatus();
if (!empty($response->getApiData()['sync_stats']) && $response->getApiData()['sync_stats'] === 1) {
// Update all DB settings from API to WP DB
@@ -74,7 +76,7 @@
return Helper::rvxApi(['error' => $e->getMessage()])->fails('General settings saved failed', $e->getCode());
}
}
- public function syncedData(WP_REST_Request $request)
+ public function syncedData(WP_REST_Request $request)
{
$post_type = $request->get_param('post_type') ?? 'product';
// Sanitize just in case:
--- a/reviewx/app/Rest/Controllers/PingController.php
+++ b/reviewx/app/Rest/Controllers/PingController.php
@@ -3,6 +3,7 @@
namespace RvxRestControllers;
use Exception;
+use WP_REST_Response;
use RvxServicesPingService;
use RvxUtilitiesHelper;
class PingController
@@ -13,14 +14,14 @@
$this->pingService = new PingService();
}
/**
- * Ping from sass and (cached for 7 days) return site info.
+ * Ping from sass and (cached for 2 hours) return site info.
*
- * @return WP_REST_Response
+ * @return WP_REST_Response
*/
- public function ping() : WP_REST_Response
+ public function ping() : WP_REST_Response
{
- // Cache time-to-live: 7 days
- $cache_duration = 86400 * 7;
+ // Cache time-to-live: 2 hours
+ $cache_duration = 3600 * 2;
try {
// Try to get cache
$data = get_transient('rvx_ping_cache');
--- a/reviewx/app/Rest/Controllers/ReviewController.php
+++ b/reviewx/app/Rest/Controllers/ReviewController.php
@@ -29,7 +29,7 @@
*/
public function index($request)
{
- $aggregation = get_transient('reviewx_aggregation');
+ $aggregation = get_transient('rvx_admin_aggregation');
if (empty($request->get_params()) && $aggregation) {
$response = ['aggregations' => $aggregation['aggregations'], 'count' => $aggregation['count'], 'reviews' => $aggregation['reviews'], 'meta' => $aggregation['meta']];
return Helper::rest($response)->success("Success");
@@ -43,8 +43,8 @@
}
public function aggregationDataStore($data)
{
- delete_transient('reviewx_aggregation');
- set_transient('reviewx_aggregation', $data, 86400);
+ delete_transient('rvx_admin_aggregation');
+ set_transient('rvx_admin_aggregation', $data, 3600);
}
public function adminAllReviewSaasCall($data)
{
@@ -61,7 +61,7 @@
$differentReview = $this->cacheServices->makeSaaSCallDecision();
$this->visibilityPaginationSaasCall($request, $differentReview);
$isVisible = $request->get_params()['isVisible'] ?? '';
- $transientKeys = ['published' => 'review_approve_data', 'pending' => 'review_pending_data', 'spam' => 'review_spam_data', 'trash' => 'review_trash_data'];
+ $transientKeys = ['published' => 'rvx_review_approve_data', 'pending' => 'rvx_review_pending_data', 'spam' => 'rvx_review_spam_data', 'trash' => 'rvx_review_trash_data'];
if (array_key_exists($isVisible, $transientKeys)) {
$approve = get_transient($transientKeys[$isVisible]);
$params = $request->get_params();
@@ -77,7 +77,7 @@
}
}
if (empty($request->get_params())) {
- $data = get_transient('reviews_data_list');
+ $data = get_transient('rvx_reviews_data_list');
if ($data) {
$response = ['count' => $data['count'], 'reviews' => $data['reviews'], 'meta' => $data['meta']];
return Helper::rest($response)->success("Success");
@@ -104,26 +104,26 @@
}
public function reviewListStoreInDB($reviewData)
{
- delete_transient('reviews_data_list');
- set_transient('reviews_data_list', $reviewData, 86400);
+ delete_transient('rvx_reviews_data_list');
+ set_transient('rvx_reviews_data_list', $reviewData, 3600);
}
public function storeVisibilityReview($data, $visibility)
{
if ($visibility === 'published') {
- delete_transient('review_approve_data');
- set_transient('review_approve_data', $data, 86400);
+ delete_transient('rvx_review_approve_data');
+ set_transient('rvx_review_approve_data', $data, 3600);
}
if ($visibility === 'pending') {
- delete_transient('review_pending_data');
- set_transient('review_pending_data', $data, 86400);
+ delete_transient('rvx_review_pending_data');
+ set_transient('rvx_review_pending_data', $data, 3600);
}
if ($visibility === 'spam') {
- delete_transient('review_spam_data');
- set_transient('review_spam_data', $data, 86400);
+ delete_transient('rvx_review_spam_data');
+ set_transient('rvx_review_spam_data', $data, 3600);
}
if ($visibility === 'trash') {
- delete_transient('review_trash_data');
- set_transient('review_trash_data', $data, 86400);
+ delete_transient('rvx_review_trash_data');
+ set_transient('rvx_review_trash_data', $data, 3600);
}
}
/**
--- a/reviewx/app/Rest/Controllers/SettingController.php
+++ b/reviewx/app/Rest/Controllers/SettingController.php
@@ -148,13 +148,22 @@
return Helper::rvxApi(['error' => $e->getMessage()])->fails('General settings saved failed', $e->getCode());
}
}
- public function removeCredentials()
+ public function removeCredentials($request)
{
try {
- $response = $this->settingService->removeCredentials();
+ $response = $this->settingService->removeCredentials($request->get_headers());
return $response;
} catch (Throwable $e) {
- return Helper::rvxApi(['error' => $e->getMessage()])->fails('General settings saved failed', $e->getCode());
+ return Helper::rvxApi(['error' => $e->getMessage()])->fails('Sites Table Data deletion failed', $e->getCode());
+ }
+ }
+ public function updateSiteData($request)
+ {
+ try {
+ $response = $this->settingService->updateSiteData($request->get_headers());
+ return $response;
+ } catch (Throwable $e) {
+ return Helper::rvxApi(['error' => $e->getMessage()])->fails('Sites Table Data update failed', $e->getCode());
}
}
}
--- a/reviewx/app/Rest/Controllers/StoreFrontReviewController.php
+++ b/reviewx/app/Rest/Controllers/StoreFrontReviewController.php
@@ -358,9 +358,9 @@
try {
$defferentIds = $this->cacheService->clearShortcodesCache(get_option('_rvx_reviews_ids'), $request->get_params());
if ($defferentIds == false) {
- delete_transient('_rvx_shortcode_transient');
+ delete_transient('rvx_shortcode_transient');
}
- $transient_key = '_rvx_shortcode_transient';
+ $transient_key = 'rvx_shortcode_transient';
$resp = get_transient($transient_key);
if ($resp !== false) {
$data = ['reviews' => $resp['reviews'], 'meta' => $resp['meta']];
--- a/reviewx/app/Rest/Middleware/AuthMiddleware.php
+++ b/reviewx/app/Rest/Middleware/AuthMiddleware.php
@@ -2,8 +2,12 @@
namespace RvxRestMiddleware;
+use Exception;
use WP_REST_Request;
use RvxUtilitiesAuthWpUser;
+use RvxUtilitiesAuthClient;
+use RvxFirebaseJWTJWT;
+use RvxFirebaseJWTKey;
class AuthMiddleware
{
/**
@@ -11,15 +15,43 @@
*
* Authorization is granted if:
* User is logged in AND has sufficient capabilities.
+ * Or if user has a valid JWT Bearer token
*
* @param WP_REST_Request $request
* @return bool
*/
public function handle(WP_REST_Request $request) : bool
{
- if (!WpUser::isLoggedIn()) {
+ // Validate by WP User
+ if (WpUser::isLoggedIn() && WpUser::can()) {
+ // error_log('AuthMiddleware: Loggedin PASS');
+ return true;
+ }
+ // error_log('AuthMiddleware: User is not logged in or does not have sufficient capabilities.');
+ // Validate by Bearer Token
+ $clientUid = Client::getUid();
+ $secret = Client::getSecret();
+ if (empty($clientUid) || empty($secret)) {
+ // error_log('AuthMiddleware: Missing client UID or secret.');
+ return false;
+ }
+ $headers = $request->get_headers();
+ $authHeader = isset($headers['authorization'][0]) ? trim($headers['authorization'][0]) : '';
+ if (!$authHeader || !preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
+ // error_log('AuthMiddleware: Missing or invalid Bearer header');
+ return false;
+ }
+ $token = $matches[1];
+ try {
+ $decoded = JWT::decode($token, new Key($secret, 'HS256'));
+ // Check UID binding
+ if (empty($decoded->uid) || $decoded->uid !== $clientUid) {
+ return false;
+ }
+ return true;
+ } catch (Exception $e) {
+ // error_log('AuthMiddleware: Invalid token - ' . $e->getMessage());
return false;
}
- return WpUser::can();
}
}
--- a/reviewx/app/Rest/Middleware/AuthSaasMiddleware.php
+++ b/reviewx/app/Rest/Middleware/AuthSaasMiddleware.php
@@ -2,13 +2,42 @@
namespace RvxRestMiddleware;
+use Exception;
use WP_REST_Request;
use RvxUtilitiesAuthClient;
+use RvxFirebaseJWTJWT;
+use RvxFirebaseJWTKey;
class AuthSaasMiddleware
{
+ /**
+ * Handle incoming SaaS-authenticated requests
+ */
public function handle(WP_REST_Request $request) : bool
{
- // ReviewX Used ID check
- return Client::getUid() ? true : false;
+ $clientUid = Client::getUid();
+ $secret = Client::getSecret();
+ if (empty($clientUid) || empty($secret)) {
+ // error_log('AuthSaasMiddleware: Missing client UID or secret.');
+ return false;
+ }
+ $headers = $request->get_headers();
+ $authHeader = isset($headers['authorization'][0]) ? trim($headers['authorization'][0]) : '';
+ if (!$authHeader || !preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
+ // error_log('AuthSaasMiddleware: Missing or invalid Bearer header');
+ return false;
+ }
+ $token = $matches[1];
+ try {
+ $decoded = JWT::decode($token, new Key($secret, 'HS256'));
+ // Check UID binding
+ if (empty($decoded->uid) || $decoded->uid !== $clientUid) {
+ return false;
+ }
+ // error_log('AuthSaasMiddleware: Token PASS');
+ return true;
+ } catch (Exception $e) {
+ // error_log('AuthSaasMiddleware: Invalid token - ' . $e->getMessage());
+ return false;
+ }
}
}
--- a/reviewx/app/Services/CacheServices.php
+++ b/reviewx/app/Services/CacheServices.php
@@ -19,7 +19,7 @@
}
public function saasStatusReviewCount()
{
- $data = get_transient('reviews_data_list');
+ $data = get_transient('rvx_reviews_data_list');
if (is_array($data)) {
return $data['count'];
}
@@ -41,14 +41,14 @@
}
public function removeCache()
{
- delete_transient('reviews_data_list');
- delete_transient('review_approve_data');
- delete_transient('review_pending_data');
- delete_transient('review_spam_data');
- delete_transient('review_trash_data');
- delete_transient('reviewx_aggregation');
- delete_transient('review_shortcode');
- delete_transient('_rvx_shortcode_transient');
+ delete_transient('rvx_reviews_data_list');
+ delete_transient('rvx_review_approve_data');
+ delete_transient('rvx_review_pending_data');
+ delete_transient('rvx_review_spam_data');
+ delete_transient('rvx_review_trash_data');
+ delete_transient('rvx_admin_aggregation');
+ delete_transient('rvx_review_shortcode');
+ delete_transient('rvx_shortcode_transient');
delete_transient('rvx_shortcode_all_reviews');
}
public function clearShortcodesCache($arrayFirst, $arraySecond)
--- a/reviewx/app/Services/DashboardServices.php
+++ b/reviewx/app/Services/DashboardServices.php
@@ -13,6 +13,17 @@
{
return (new DashboardApi())->requestEmail();
}
+ public function requestUserData()
+ {
+ global $wpdb;
+ $table_name = $wpdb->prefix . 'rvx_sites';
+ $site_data = $wpdb->get_row("SELECT * FROM {$table_name} WHERE id = 1");
+ // error_log('Site Data: ' . print_r($site_data, true));
+ if (!$site_data) {
+ return ['success' => false, 'message' => 'No site data found', 'data' => null];
+ }
+ return ['success' => true, 'message' => 'Site user data fetched successfully', 'site_id' => $site_data->site_id, 'user_name' => $site_data->name, 'user_email' => $site_data->email, 'site_domain' => $site_data->domain, 'is_saas_synced' => $site_data->is_saas_sync];
+ }
public function chart($request)
{
$time = $request['view'];
--- a/reviewx/app/Services/DataSyncService.php
+++ b/reviewx/app/Services/DataSyncService.php
@@ -12,13 +12,13 @@
use RvxServicesUserSyncService;
use RvxServicesProductSyncService;
use RvxServicesReviewSyncService;
-use RvxServicesCategorySyncService;
+// use RvxServicesCategorySyncService;
use RvxUtilitiesHelper;
class DataSyncService extends Service
{
protected DataSyncHandler $dataSyncHandler;
protected UserSyncService $userSyncService;
- protected CategorySyncService $categorySyncService;
+ // protected CategorySyncService $categorySyncService;
protected ProductSyncService $productSyncService;
protected ReviewSyncService $reviewSyncService;
protected OrderService $orderService;
@@ -27,7 +27,7 @@
{
$this->dataSyncHandler = new DataSyncHandler();
$this->userSyncService = new UserSyncService();
- $this->categorySyncService = new CategorySyncService();
+ // $this->categorySyncService = new CategorySyncService();
$this->productSyncService = new ProductSyncService();
$this->reviewSyncService = new ReviewSyncService();
$this->orderService = new OrderService();
@@ -56,8 +56,8 @@
// Product Sync Data for *Login or Register*
$total_objects += $this->userSyncService->syncUser($file);
if (class_exists('WooCommerce') || $this->dataSyncHandler->wc_data_exists_in_db()) {
- $syncedCaterories = new CategorySyncService();
- $total_objects += $syncedCaterories->syncCategory($file);
+ // $syncedCaterories = new CategorySyncService();
+ // $total_objects += $syncedCaterories->syncCategory($file);
$total_objects += $this->productSyncService->processProductForSync($file, $post_type);
$total_objects += $this->reviewSyncService->processReviewForSync($file, $post_type);
$total_objects += $this->orderItemSyncService->syncOrder($file);
@@ -107,17 +107,17 @@
}
if ("categories" === $data['action']) {
if (class_exists('WooCommerce') || $this->dataSyncHandler->wc_data_exists_in_db()) {
- $syncedCaterories = new CategorySyncService();
- $totalLines += $syncedCaterories->syncCategory($file);
- $processProduct = new ProductSyncService($syncedCaterories);
- $totalLines += $processProduct->processProductForSync($file);
+ // $syncedCaterories = new CategorySyncService();
+ // $totalLines += $syncedCaterories->syncCategory($file);
+ $processProduct = new ProductSyncService();
+ $totalLines += $processProduct->processProductForSync($file, 'product');
update_option('rvx_sync_number', $totalLines);
}
}
if ("reviews" === $data['action']) {
if (class_exists('WooCommerce') || $this->dataSyncHandler->wc_data_exists_in_db()) {
$totalLines = get_option('rvx_sync_number');
- $totalLines += (new ReviewSyncService())->processReviewForSync($file);
+ $totalLines += (new ReviewSyncService())->processReviewForSync($file, 'product');
update_option('rvx_sync_number', $totalLines);
}
}
--- a/reviewx/app/Services/ProductSyncService.php
+++ b/reviewx/app/Services/ProductSyncService.php
@@ -11,6 +11,8 @@
protected $postMetaPriceRelation;
protected $productCount = 0;
protected $postMetaAverageRatingRelation;
+ protected $postMetaRatingCountPercentageRelation;
+ protected $postMetaReviewsCountRelation;
protected $postMetaSalePriceRelation;
protected $postMetaThumbnaiRelation;
protected $postMetaAttachmentsRelation;
@@ -25,29 +27,41 @@
}
public function processProductForSync($file, $post_type) : int
{
- $this->syncProductsMeta();
+ $this->syncProductsMeta($post_type);
return $this->syncProducts($file, $post_type);
}
public function getProductAttachementRalation()
{
return $this->postAttachmentRelation;
}
- public function syncProductsMeta()
+ public function syncProductsMeta($post_type)
{
+ // Base meta keys
+ $dbTableKeys = ['rvx_avg_rating', 'rating', '_thumbnail_id'];
+ // Add product-specific keys
+ if ($post_type === 'product') {
+ $dbTableKeys = array_merge($dbTableKeys, ['_price', '_sale_price', '_wc_review_count', '_wc_average_rating', '_wc_rating_count']);
+ }
+ // Define relation targets by meta key
+ $relationMap = ['_price' => 'postMetaPriceRelation', '_sale_price' => 'postMetaSalePriceRelation', '_wc_review_count' => 'postMetaReviewsCountRelation', '_wc_rating_count' => 'postMetaRatingCountPercentageRelation', '_thumbnail_id' => 'postMetaThumbnaiRelation'];
try {
- DB::table('postmeta')->whereIn('meta_key', ['_price', '_sale_price', '_wc_average_rating', '_thumbnail_id'])->chunk(100, function ($allPostMeta) {
- foreach ($allPostMeta as $postMetas) {
- if ($postMetas->meta_key === '_price') {
- $this->postMetaPriceRelation[$postMetas->post_id] = $postMetas->meta_value;
- }
- if ($postMetas->meta_key === '_sale_price') {
- $this->postMetaSalePriceRelation[$postMetas->post_id] = $postMetas->meta_value;
- }
- if ($postMetas->meta_key === '_wc_average_rating') {
- $this->postMetaAverageRatingRelation[$postMetas->post_id] = $postMetas->meta_value;
+ DB::table('postmeta')->whereIn('meta_key', $dbTableKeys)->chunk(100, function ($allPostMeta) use($relationMap) {
+ foreach ($allPostMeta as $meta) {
+ $key = $meta->meta_key;
+ $pid = $meta->post_id;
+ $value = $meta->meta_value;
+ // Direct assignment using map
+ if (isset($relationMap[$key])) {
+ $this->{$relationMap[$key]}[$pid] = $value;
+ continue;
}
- if ($postMetas->meta_key === '_thumbnail_id') {
- $this->postMetaThumbnaiRelation[$postMetas->post_id] = $postMetas->meta_value;
+ // --- Rating Prioritization Logic ---
+ // Priority: _wc_average_rating > rvx_avg_rating > rating
+ if ($key === '_wc_average_rating' || $key === 'rvx_avg_rating' || $key === 'rating') {
+ // Assign only if not already assigned by a higher-priority field
+ if (!isset($this->postMetaAverageRatingRelation[$pid])) {
+ $this->postMetaAverageRatingRelation[$pid] = $value;
+ }
}
}
});
@@ -60,10 +74,13 @@
$productCount = 0;
$attachmentRelation = [];
$this->postMetaAttachmentsRelation = [];
- DB::table('posts')->select(['ID', 'post_type', 'post_title', 'post_name', 'post_excerpt', 'post_status', 'guid', 'post_modified'])->orderBy('ID')->whereIn('post_type', [$post_type])->chunk(100, function ($products) use(&$attachmentRelation, &$file, &$productCount) {
+ DB::table('posts')->select(['ID', 'post_type', 'post_title', 'post_name', 'post_excerpt', 'post_status', 'guid', 'post_modified', 'comment_count'])->orderBy('ID')->whereIn('post_type', [$post_type])->chunk(100, function ($products) use(&$attachmentRelation, &$file, &$productCount) {
foreach ($products as $product) {
$this->productids[] = $product->ID;
$productImage = get_the_post_thumbnail_url($product->ID, 'full') ? get_the_post_thumbnail_url($product->ID, 'full') : null;
+ if ($product->post_type !== 'product') {
+ $this->postMetaReviewsCountRelation[$product->ID] = $product->comment_count;
+ }
$formatedProduct = $this->processProduct($product, $productImage);
if ($formatedProduct['post_type'] !== 'attachment') {
Helper::appendToJsonl($file, $formatedProduct);
@@ -81,7 +98,53 @@
}
public function processProduct($product, $productImage) : array
{
- return ['rid' => 'rid://Product/' . (int) $product->ID, "post_type" => $product->post_type ?? null, "wp_id" => (int) ($product->ID ?? 0), "title" => isset($product->post_title) ? htmlspecialchars($product->post_title, ENT_QUOTES, 'UTF-8') : null, "url" => $product->guid ?? '', "description" => $product->post_excerpt ?? null, "price" => isset($this->postMetaPriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaPriceRelation[$product->ID]) : 0, "discounted_price" => isset($this->postMetaSalePriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaSalePriceRelation[$product->ID]) : 0, "slug" => $product->post_name ?? '', "status" => $this->productStatus($product->post_status ?? ''), "total_reviews" => 0, "avg_rating" => isset($this->postMetaAverageRatingRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaAverageRatingRelation[$product->ID]) : 0, "stars" => ["one" => 0, "two" => 0, "three" => 0, "four" => 0, "five" => 0], "one_stars" => 0, "two_stars" => 0, "three_stars" => 0, "four_stars" => 0, "five_stars" => 0, "modified_date" => Helper::validateReturnDate($product->post_modified) ?? null, "image" => $productImage, "category_ids" => isset($this->postTermRelation[(int) $product->ID]) && is_array($this->postTermRelation[(int) $product->ID]) ? array_map('intval', $this->postTermRelation[(int) $product->ID]) : []];
+ $reviewsCount = isset($this->postMetaReviewsCountRelation[$product->ID]) ? (int) $this->postMetaReviewsCountRelation[$product->ID] : 0;
+ $ratingCount = $this->postMetaRatingCountPercentageRelation[$product->ID] ?? [];
+ // Ensure WooCommerce serialized rating count is converted to array
+ if (is_string($ratingCount)) {
+ $decoded = @unserialize($ratingCount);
+ $ratingCount = is_array($decoded) ? $decoded : [];
+ }
+ $ratingCounts = $this->ratingCountsConverter($ratingCount);
+ return ['rid' => 'rid://Product/' . (int) $product->ID, "post_type" => $product->post_type ?? null, "wp_id" => (int) ($product->ID ?? 0), "title" => isset($product->post_title) ? htmlspecialchars($product->post_title, ENT_QUOTES, 'UTF-8') : null, "url" => $product->guid ?? '', "description" => $product->post_excerpt ?? null, "price" => isset($this->postMetaPriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaPriceRelation[$product->ID]) : 0, "discounted_price" => isset($this->postMetaSalePriceRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaSalePriceRelation[$product->ID]) : 0, "slug" => $product->post_name ?? '', "status" => $this->productStatus($product->post_status ?? ''), "total_reviews" => $reviewsCount, "avg_rating" => isset($this->postMetaAverageRatingRelation[$product->ID]) ? Helper::formatToTwoDecimalPlaces($this->postMetaAverageRatingRelation[$product->ID]) : 0, "stars" => ["one" => $ratingCounts["one"], "two" => $ratingCounts["two"], "three" => $ratingCounts["three"], "four" => $ratingCounts["four"], "five" => $ratingCounts["five"]], "one_stars" => $ratingCounts["one"], "two_stars" => $ratingCounts["two"], "three_stars" => $ratingCounts["three"], "four_stars" => $ratingCounts["four"], "five_stars" => $ratingCounts["five"], "modified_date" => Helper::validateReturnDate($product->post_modified) ?? null, "image" => $productImage, "category_ids" => isset($this->postTermRelation[(int) $product->ID]) && is_array($this->postTermRelation[(int) $product->ID]) ? array_map('intval', $this->postTermRelation[(int) $product->ID]) : []];
+ }
+ private function ratingCountsConverter(array $ratingCount) : array
+ {
+ // Final output initialized to 0
+ $stars = ["one" => 0, "two" => 0, "three" => 0, "four" => 0, "five" => 0];
+ // Empty OR invalid input → return default immediately
+ if (empty($ratingCount)) {
+ return $stars;
+ }
+ foreach ($ratingCount as $rawKey => $value) {
+ // Convert numeric strings
+ $value = is_numeric($value) ? (float) $value : 0;
+ // Convert key to float (handles "3.5", 5, "4", etc.)
+ $key = is_numeric($rawKey) ? (float) $rawKey : null;
+ if ($key === null) {
+ continue;
+ }
+ // Round decimals down (3.5 => 3)
+ $bucket = (int) floor($key);
+ switch ($bucket) {
+ case 1:
+ $stars["one"] += $value;
+ break;
+ case 2:
+ $stars["two"] += $value;
+ break;
+ case 3:
+ $stars["three"] += $value;
+ break;
+ case 4:
+ $stars["four"] += $value;
+ break;
+ case 5:
+ $stars["five"] += $value;
+ break;
+ }
+ }
+ return $stars;
}
publi