Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /**
- * Shop product reviews
- *
- * @author businesstech.fr <[email protected]> - https://www.businesstech.fr/
- * @copyright Business Tech - https://www.businesstech.fr/
- * @license see file: LICENSE.txt
- *
- * ____ _______
- * | _ \ |__ __|
- * | |_) | | |
- * | _ < | |
- * | |_) | | |
- * |____/ |_|
- */
- if (!defined('_PS_VERSION_')) {
- exit;
- }
- use SPR\Models\Callback;
- use SPR\Models\CriteriaProduct;
- use SPR\Models\Dispute;
- use SPR\Models\Reviews;
- use SPR\Models\VoucherAssoc;
- use SPR\Src\IncentiveUtils;
- use SPR\Src\ModuleUtils;
- use SPR\Src\ReviewsUtils;
- class gsnippetsreviewsReviewFormModuleFrontController extends ModuleFrontController
- {
- /**
- * @var object :
- */
- protected $customer;
- /**
- * @var int :
- */
- protected $customer_lang_id;
- /**
- * @var int :
- */
- protected $shop_lang_id;
- /**
- * @var int :
- */
- protected $shop_id;
- /**
- * @var Product :
- */
- protected $product;
- /**
- * @var bool :
- */
- protected $already_rate;
- /**
- * @var int :
- */
- protected $rating_value;
- /**
- * @var int :
- */
- protected $review_title = '';
- /**
- * @var int :
- */
- protected $review_text = '';
- /**
- * @var int :
- */
- protected $voucher_behavior = '';
- /**
- * @var bool :
- */
- protected $has_voucher = '';
- /**
- * @var bool :
- */
- protected $product_category = '';
- /**
- * @var object :
- */
- protected $order;
- /**
- * @var array :
- */
- protected $criteria = '';
- /**
- * init module front controller
- */
- public function init()
- {
- // Execute parent initialization
- parent::init();
- try {
- // Get product and customer IDs with validation
- $idProduct = (int) (!empty(\Tools::getValue('id_product')) ? \Tools::getValue('id_product') : \Tools::getValue('bt_gsr_product_id'));
- $context = \Context::getContext();
- $idCustomer = (int) (!empty($context->customer->id) ? $context->customer->id : \Tools::getValue('id_customer'));
- $orderId = (int) \Tools::getValue('order_id');
- if ($idProduct <= 0) {
- // Invalid/missing product ID: skip noisy logging and stop processing.
- \Tools::redirect('index.php');
- }
- // Initialize required objects with validation
- $this->product = new \Product($idProduct);
- if ($idProduct > 0 && !Validate::isLoadedObject($this->product)) {
- \PrestaShopLogger::addLog('Invalid product ID: ' . $idProduct, 3);
- }
- $this->customer = new \Customer($idCustomer);
- if ($idCustomer > 0 && !Validate::isLoadedObject($this->customer)) {
- \PrestaShopLogger::addLog('Invalid customer ID: ' . $idCustomer, 3);
- }
- // Set shop and language context
- $this->shop_id = (int) $context->shop->id;
- $this->shop_lang_id = (int) $context->language->id;
- // Initialize order
- $this->order = new \Order($orderId);
- if ($orderId > 0 && !Validate::isLoadedObject($this->order)) {
- \PrestaShopLogger::addLog('Invalid order ID: ' . $orderId, 3);
- }
- // Set customer language
- $this->customer_lang_id = (int) $this->customer->id_lang;
- // Check if customer already rated
- $this->already_rate = ReviewsUtils::isAlreadyRated(
- (int) $this->customer->id,
- (int) $this->product->id,
- (int) $this->order->id
- );
- // Get voucher configuration and status
- $this->voucher_behavior = \Configuration::get('BT_GSR_VOUCHER_MODE', true);
- $voucherMode = \Configuration::get('BT_GSR_VOUCHER_MODE', 'customer');
- $orderId = ($voucherMode === 'order') ? $this->order->id : null;
- $this->has_voucher = VoucherAssoc::isVoucherAssocatied(
- (int) $this->customer->id,
- $orderId,
- null
- );
- // Set product category
- $categoryId = (int) $this->product->getDefaultCategory();
- $this->product_category = new \Category($categoryId, (int) $this->shop_lang_id);
- if ($categoryId > 0 && !Validate::isLoadedObject($this->product_category)) {
- \PrestaShopLogger::addLog('Invalid category ID: ' . $categoryId, 3);
- }
- // Get product review criteria
- $this->criteria = ReviewsUtils::getProductCriteria((int) $this->product->id);
- // Set template with dynamic PS version handling
- $templateBase = 'module:gsnippetsreviews/views/templates/front/email_add_review';
- $templatePath = GSnippetsReviews::$bcomparePS9 ? $templateBase . '_ps9.tpl' : $templateBase . '.tpl';
- $this->setTemplate($templatePath);
- } catch (\Exception $e) {
- \PrestaShopLogger::addLog('Error in review form initialization: ' . $e->getMessage(), 3);
- throw $e;
- }
- }
- /**
- * Initialize module front controller content
- *
- * @return void
- */
- public function initContent()
- {
- try {
- parent::initContent();
- // Add required assets
- Context::getContext()->controller->addCSS(_MODULE_DIR_ . 'gsnippetsreviews/views/css/front/review_form.css');
- Context::getContext()->controller->addJS(_MODULE_DIR_ . 'gsnippetsreviews/views/js/front/review_form.js');
- // Initialize JS definitions
- $jsDefs = $this->getJsDefinitions();
- // Handle customer login if needed
- $this->handleCustomerLogin();
- // Build criteria IDs for JS form handling
- if (!empty($this->criteria)) {
- $jsDefs['criteria_ids'] = array_column($this->criteria, 'id_criteria');
- }
- // Add JS definitions
- \Media::addJsDef(['btSpr' => $jsDefs]);
- // Check if customer is excluded
- $isExcluded = $this->isCustomerExcluded();
- // Assign template variables
- $this->assignTemplateVariables($isExcluded);
- } catch (\Exception $e) {
- \PrestaShopLogger::addLog('Error in review form content initialization: ' . $e->getMessage(), 3);
- throw $e;
- }
- }
- /**
- * Get JS definitions for form
- *
- * @return array
- */
- private function getJsDefinitions()
- {
- return [
- 'reviewLength' => \Configuration::get('BT_GSR_MIN_RVW_LENGHT', true),
- 'reviewLengthMessage' => $this->module->l('characters minimum required', 'reviewForm'),
- 'reviewRequire' => \Configuration::get('BT_GSR_ENABLE_COMMENTS', true),
- 'has_multi' => !empty($this->criteria)
- ];
- }
- /**
- * Handle customer login if needed
- *
- * @return void
- */
- private function handleCustomerLogin()
- {
- if (!Context::getContext()->customer->isLogged()) {
- $token = ModuleUtils::makeToken($this->customer->id, $this->product->id, $this->order->id);
- if ($this->customer->id && (Tools::getValue('bt_token') === $token)) {
- if (Validate::isLoadedObject($this->customer)) {
- // Note: We do NOT call updateCustomer here to avoid overprivileged access
- // The token is only meant to allow posting a review, not to log in as the customer
- $this->customer_lang_id = $this->customer->id_lang;
- }
- }
- }
- }
- /**
- * Check if customer is excluded from reviews
- *
- * @return bool
- */
- private function isCustomerExcluded()
- {
- if (ModuleUtils::isProductExcluded((int) $this->product->id)) {
- return true;
- }
- if (empty(Context::getContext()->customer->id)) {
- return false;
- }
- $excludedCustomers = explode(',', \Configuration::get('BT_GSR_BLACKLIST'));
- $email = Context::getContext()->customer->email;
- return in_array($email, $excludedCustomers, true);
- }
- /**
- * Assign variables to template
- *
- * @param bool $isExcluded Whether customer is excluded
- * @return void
- */
- private function assignTemplateVariables($isExcluded)
- {
- $this->context->smarty->assign([
- 'customer_id' => $this->customer->id,
- 'product_id' => $this->product->id,
- 'order_id' => $this->order->id,
- 'rating_email_value' => (int) Tools::getValue('rating'),
- 'review_required' => \Configuration::get('BT_GSR_ENABLE_COMMENTS', true),
- 'product_image_link' => ModuleUtils::getProductImageUrl(
- $this->product,
- ImageType::getFormattedName('home'),
- $this->shop_lang_id
- ),
- 'product_name' => isset($this->product->name[$this->shop_lang_id]) && isset($this->product->name[1]) ? $this->product->name[$this->shop_lang_id] : (isset($this->product->name[1]) ? $this->product->name[1] : ''),
- 'product_description' => isset($this->product->description_short) && isset($this->product->description_short[$this->shop_lang_id]) ? strip_tags($this->product->description_short[$this->shop_lang_id]) : (isset($this->product->description_short[1]) ? strip_tags($this->product->description_short[1]) : ''),
- 'is_aready_rated' => $this->already_rate,
- 'incentive_data' => IncentiveUtils::getIncentiveData(),
- 'has_voucher' => $this->has_voucher,
- 'review_length' => \Configuration::get('BT_GSR_MIN_RVW_LENGHT', true),
- 'bt_is_excluded' => $isExcluded,
- 'criteria' => $this->criteria,
- 'show_description' => \Configuration::get('BT_GSR_ENABLE_PROD_DESC_REVIEW', true),
- 'allow_photo' => \Configuration::get('BT_GSR_ENABLE_PHOTO', true),
- 'bt_token' => Tools::getValue('bt_token')
- ]);
- }
- /**
- * Process review submission
- *
- * @return bool
- *
- * @throws Exception
- */
- public function postProcess()
- {
- try {
- if (!Tools::isSubmit('reviewPost')) {
- return false;
- }
- // Get and validate input parameters
- $customerId = (int) Tools::getValue('customer_id');
- $orderId = !empty(Tools::getValue('order_id')) ? (int) Tools::getValue('order_id') : 0;
- $productId = (int) Tools::getValue('product_id');
- $submittedToken = Tools::getValue('bt_token');
- $reviewSource = Tools::getValue('source_url');
- // Determine authentication method based on review source
- $isProductPageReview = ($reviewSource === 'product_page');
- if ($isProductPageReview) {
- // PRODUCT PAGE REVIEW FLOW
- // SECURITY CHECK 1: Verify user is logged in
- if (!Context::getContext()->customer->isLogged()) {
- \PrestaShopLogger::addLog(
- 'Security: Unauthenticated review attempt from product page - Product: ' . $productId .
- ', IP: ' . Tools::getRemoteAddr(),
- 3,
- null,
- 'Customer',
- $customerId
- );
- $this->errors[] = $this->module->l('You must be logged in to submit a review.', 'reviewForm');
- return false;
- }
- // SECURITY CHECK 2: Verify the logged-in customer matches the submitted customer ID
- $loggedCustomerId = (int) Context::getContext()->customer->id;
- if ($loggedCustomerId !== $customerId) {
- \PrestaShopLogger::addLog(
- 'Security: Customer ID mismatch - Logged: ' . $loggedCustomerId .
- ', Submitted: ' . $customerId . ', IP: ' . Tools::getRemoteAddr(),
- 3,
- null,
- 'Customer',
- $loggedCustomerId
- );
- $this->errors[] = $this->module->l('Invalid customer information.', 'reviewForm');
- return false;
- }
- // SECURITY CHECK 3: Verify purchase (if required by configuration)
- $whoCanReview = (int) Configuration::get('BT_GSR_COMMENTS_USER', 1);
- if ($whoCanReview === 1) { // 1 = Only registered customers who purchased
- if (!ReviewsUtils::hasPurchased($customerId, $productId)) {
- \PrestaShopLogger::addLog(
- 'Security: Review attempt without purchase - Customer: ' . $customerId .
- ', Product: ' . $productId . ', IP: ' . Tools::getRemoteAddr(),
- 2,
- null,
- 'Customer',
- $customerId
- );
- $this->errors[] = $this->module->l('You can only review products you have purchased.', 'reviewForm');
- return false;
- }
- }
- } else {
- // EMAIL LINK REVIEW FLOW
- // SECURITY CHECK 1: Validate token
- // This prevents attackers from submitting reviews without a valid email link
- $expectedToken = ModuleUtils::makeToken($customerId, $productId, $orderId);
- if (empty($submittedToken) || $submittedToken !== $expectedToken) {
- \PrestaShopLogger::addLog(
- 'Security: Invalid review token attempt - Customer: ' . $customerId .
- ', Product: ' . $productId . ', Order: ' . $orderId .
- ', IP: ' . Tools::getRemoteAddr(),
- 3,
- null,
- 'Customer',
- $customerId
- );
- $this->errors[] = $this->module->l('Invalid authentication token. Please use the link from your email.', 'reviewForm');
- return false;
- }
- // SECURITY CHECK 2: Verify order ownership
- // This prevents attackers from using someone else's customer_id with a different order_id
- if (!empty($orderId)) {
- $order = new \Order($orderId);
- if (!Validate::isLoadedObject($order) || (int) $order->id_customer !== $customerId) {
- \PrestaShopLogger::addLog(
- 'Security: Order ownership mismatch - Customer: ' . $customerId .
- ', Order: ' . $orderId . ', Order Customer: ' . (Validate::isLoadedObject($order) ? $order->id_customer : 'invalid') .
- ', IP: ' . Tools::getRemoteAddr(),
- 3,
- null,
- 'Customer',
- $customerId
- );
- $this->errors[] = $this->module->l('Invalid order information.', 'reviewForm');
- return false;
- }
- }
- // SECURITY CHECK 3: Verify purchase (if required by configuration)
- // This enforces the "only registered clients that have purchased" setting
- $whoCanReview = (int) Configuration::get('BT_GSR_COMMENTS_USER', 1);
- if ($whoCanReview === 1) { // 1 = Only registered customers who purchased
- if (!ReviewsUtils::hasPurchased($customerId, $productId)) {
- \PrestaShopLogger::addLog(
- 'Security: Review attempt without purchase - Customer: ' . $customerId .
- ', Product: ' . $productId . ', IP: ' . Tools::getRemoteAddr(),
- 2,
- null,
- 'Customer',
- $customerId
- );
- $this->errors[] = $this->module->l('You can only review products you have purchased.', 'reviewForm');
- return false;
- }
- }
- }
- // Product exclusion check (common to both flows)
- if (ModuleUtils::isProductExcluded($productId)) {
- $this->errors[] = $this->module->l('This product cannot be reviewed.', 'reviewForm');
- return false;
- }
- $criteria = ReviewsUtils::getProductCriteria($productId);
- $product = new \Product($productId, false, (int) Context::getContext()->language->id);
- $productCategory = new \Category((int) $product->getDefaultCategory(), (int) Context::getContext()->language->id);
- $productUrl = Context::getContext()->link->getProductLink(
- $product,
- null,
- Tools::strtolower($productCategory->link_rewrite),
- null,
- (int) Context::getContext()->language->id,
- (int) Context::getContext()->shop->id,
- 0,
- true
- ) . '?spr_is_posted=1';
- $review = new Reviews();
- $star = $this->getRatingValue();
- $this->setReviewData($review, $customerId, $orderId, $productId, $star);
- if (!$review->add()) {
- return false;
- }
- $this->handleReviewCreated($review, $customerId, $orderId, $productId, $star);
- if (!empty($criteria)) {
- $this->handleCriteriaReviews($criteria, $review, $productId);
- }
- ModuleUtils::storeReviewPhotos($_FILES['review_photos'], $review, $customerId, $productId, $orderId);
- if ($this->shouldCreateDispute($star)) {
- $this->createDispute($review, $star);
- }
- Tools::redirect($productUrl);
- } catch (Exception $e) {
- \PrestaShopLogger::addLog($e->getMessage(), 1, $e->getCode(), null, null, true);
- return false;
- }
- }
- /**
- * Get rating value from input
- *
- * @return int
- */
- private function getRatingValue()
- {
- $rating = (int) Tools::getValue('star');
- return !empty($rating) ? $rating : 3;
- }
- /**
- * Set review data
- *
- * @param Reviews $review
- * @param int $customerId
- * @param int $orderId
- * @param int $productId
- * @param int $rating
- * @return void
- */
- private function setReviewData($review, $customerId, $orderId, $productId, $rating)
- {
- $review->id_order = $orderId;
- $review->id_customer = $customerId;
- $review->id_product = $productId;
- $review->id_lang = (int) Context::getContext()->customer->id_lang;
- $review->title_review = Tools::getValue('review_title');
- $review->text_review = Tools::getValue('review_text');
- $review->rating_value = $rating;
- $review->review_status = !empty(Configuration::get('BT_GSR_COMMENTS_APPROVAL')) ? false : true;
- $review->id_shop = (int) $this->shop_id;
- }
- /**
- * Handle review creation tasks
- *
- * @param Reviews $review
- * @param int $customerId
- * @param int $orderId
- * @param int $productId
- * @param int $rating
- * @return void
- */
- private function handleReviewCreated($review, $customerId, $orderId, $productId, $rating)
- {
- IncentiveUtils::generateVoucher($customerId, $orderId, $productId, (int) $review->id);
- $this->updateCallback($review);
- $this->notifyMerchant($review, $rating);
- }
- /**
- * Update callback status
- *
- * @param Reviews $review
- * @return void
- */
- private function updateCallback($review)
- {
- $callback = Callback::getCallbacks((int) $review->id_customer, (int) $review->id_order, (int) $review->id_product);
- if (!empty($callback)) {
- $callbackObj = new Callback((int) $callback[0]['id_callback']);
- $callbackObj->callback_status = 3;
- $callbackObj->update();
- }
- }
- /**
- * Send merchant notification
- *
- * @param Reviews $review
- * @param int $rating
- * @return void
- */
- private function notifyMerchant($review, $rating)
- {
- if (empty(Configuration::get('BT_GSR_NOTIFY_EMAIL'))) {
- return;
- }
- $subject = $this->module->l('You have received a new product review', 'addReviewProductPage');
- $emailFolder = _PS_MODULE_DIR_ . 'gsnippetsreviews/mails';
- $vars = [
- '{message}' => $this->module->l('A customer has posted a new review', 'addReviewProductPage') . ' ' . $rating . '/5',
- '{review_title}' => $review->title_review,
- '{review_text}' => $review->text_review,
- ];
- Mail::Send(
- (int) $this->customer->id_lang,
- 'new_review',
- $subject,
- $vars,
- Configuration::get('BT_GSR_NOTIFY_EMAIL'),
- null,
- null,
- null,
- null,
- null,
- $emailFolder
- );
- }
- /**
- * Handle criteria reviews
- *
- * @param array $criteria
- * @param Reviews $review
- * @param int $productId
- * @return void
- */
- private function handleCriteriaReviews($criteria, $review, $productId)
- {
- foreach ($criteria as $data) {
- $criteriaProduct = new CriteriaProduct();
- $criteriaProduct->id_criteria = (int) $data['id_criteria'];
- $criteriaProduct->id_review = (int) $review->id;
- $criteriaProduct->id_product = $productId;
- $criteriaProduct->rating_value = (int) Tools::getValue('star_criteria_' . $data['id_criteria'], 3);
- $criteriaProduct->id_shop = (int) $this->shop_id;
- $criteriaProduct->add();
- }
- }
- /**
- * Check if dispute should be created
- *
- * @param int $rating
- * @return bool
- */
- private function shouldCreateDispute($rating)
- {
- return $rating <= Configuration::get('BT_GSR_RATING_DISPUTE_LEVEL');
- }
- /**
- * Create dispute for low rating
- *
- * @param Reviews $review
- * @param int $rating
- * @return void
- */
- private function createDispute($review, $rating)
- {
- $dispute = new Dispute();
- $dispute->id_review = (int) $review->id;
- $dispute->new_rating_value = $rating;
- $dispute->status = 0;
- $dispute->id_shop = (int) $this->shop_id;
- if (!$dispute->add() || empty(Configuration::get('BT_GSR_RATING_DISPUTE_EMAIL'))) {
- return;
- }
- $this->sendDisputeNotification($rating);
- }
- /**
- * Send dispute notification email
- *
- * @param int $rating
- * @return void
- */
- private function sendDisputeNotification($rating)
- {
- $emailFolder = _PS_MODULE_DIR_ . 'gsnippetsreviews/mails';
- $customer = new Customer((int) Tools::getValue('customer_id'));
- $subject = $this->module->l('A bad review has been sent', 'reviewForm');
- $reviewText = !empty(Tools::getValue('review_text'))
- ? Tools::getValue('review_text')
- : $this->module->l('The customer did not leave a comment.');
- $reviewTitle = !empty(Tools::getValue('review_title'))
- ? Tools::getValue('review_title')
- : $this->module->l('The customer did not give a title to his review.');
- $vars = [
- '{firstname}' => $customer->firstname,
- '{lastname}' => $customer->lastname,
- '{review_title}' => $reviewTitle,
- '{review_text}' => $reviewText,
- '{rating}' => $rating,
- ];
- Mail::Send(
- (int) $this->customer->id_lang,
- 'alert',
- $subject,
- $vars,
- Configuration::get('BT_GSR_RATING_DISPUTE_EMAIL'),
- null,
- null,
- null,
- null,
- null,
- $emailFolder
- );
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment