File manager - Edit - /home/webapp69.cm.in.th/u69319090028/Shop/app/Services/ReviewService.php
Back
<?php /** * ReviewService * Handles Business Logic for Customer Reviews, Ratings, and Image Uploads */ require_once __DIR__ . '/../Models/Review.php'; require_once __DIR__ . '/../Models/ReviewImage.php'; require_once __DIR__ . '/../Models/Report.php'; require_once __DIR__ . '/../Models/ActivityLog.php'; class ReviewService { private Review $reviewModel; private ReviewImage $reviewImageModel; private Report $reportModel; private ActivityLog $activityLog; private const MAX_IMAGES = 5; private const MAX_FILE_SIZE = 5242880; // 5MB in bytes private const ALLOWED_MIMES = ['image/jpeg', 'image/png', 'image/webp']; private const ALLOWED_EXTS = ['jpg', 'jpeg', 'png', 'webp']; public function __construct() { $this->reviewModel = new Review(); $this->reviewImageModel = new ReviewImage(); $this->reportModel = new Report(); $this->activityLog = new ActivityLog(); } /** * Check if user is eligible to review an item */ public function checkEligibility(int $userId, int $orderItemId): array { return $this->reviewModel->checkEligibility($userId, $orderItemId); } /** * Submit a new customer review */ public function submitReview( int $userId, int $orderItemId, int $rating, ?string $reviewText, array $uploadedFiles = [], bool $isAnonymous = false ): array { // 1. Check eligibility $eligibility = $this->reviewModel->checkEligibility($userId, $orderItemId); if (!$eligibility['eligible']) { return ['success' => false, 'message' => $eligibility['message']]; } $item = $eligibility['item']; // 2. Validate rating if ($rating < 1 || $rating > 5) { return ['success' => false, 'message' => 'กรุณาให้คะแนนระหว่าง 1 ถึง 5 ดาว']; } // 3. Validate text length $reviewText = trim((string)$reviewText); if (mb_strlen($reviewText) > 2000) { return ['success' => false, 'message' => 'ข้อความรีวิวต้องไม่เกิน 2,000 ตัวอักษร']; } // 4. Process and upload review images $imagePaths = []; if (!empty($uploadedFiles['name']) && is_array($uploadedFiles['name'])) { $uploadResult = $this->processImageUploads($uploadedFiles, self::MAX_IMAGES); if (!$uploadResult['success']) { return $uploadResult; } $imagePaths = $uploadResult['paths']; } // 5. Save to database try { $reviewId = $this->reviewModel->createReview([ 'order_id' => (int)$item['order_id'], 'order_item_id' => $orderItemId, 'product_id' => (int)$item['product_id'], 'store_id' => (int)$item['store_id'], 'user_id' => $userId, 'rating' => $rating, 'review_text' => !empty($reviewText) ? $reviewText : null, 'is_anonymous' => $isAnonymous ], $imagePaths); $this->activityLog->record($userId, 'review_submitted', 'review', $reviewId, "Submitted review for product ID {$item['product_id']}"); return [ 'success' => true, 'message' => 'ส่งรีวิวสินค้าเรียบร้อยแล้ว ขอบคุณสำหรับความคิดเห็นของคุณ', 'review_id' => $reviewId ]; } catch (Exception $e) { // Clean up uploaded images on error foreach ($imagePaths as $path) { $full = __DIR__ . '/../../' . ltrim($path, '/'); if (file_exists($full)) { @unlink($full); } } return ['success' => false, 'message' => 'เกิดข้อผิดพลาดในการบันทึกรีวิว: ' . $e->getMessage()]; } } /** * Update an existing review */ public function updateReview( int $userId, int $reviewId, int $rating, ?string $reviewText, array $newUploadedFiles = [], array $deleteImageIds = [], bool $isAnonymous = false ): array { $review = $this->reviewModel->findById($reviewId); if (!$review || (int)$review['user_id'] !== $userId) { return ['success' => false, 'message' => 'ไม่พบรีวิวหรือคุณไม่มีสิทธิ์แก้ไข']; } if ($review['status'] === 'removed') { return ['success' => false, 'message' => 'รีวิวนี้ถูกลบแล้ว ไม่สามารถแก้ไขได้']; } // Validate rating if ($rating < 1 || $rating > 5) { return ['success' => false, 'message' => 'กรุณาให้คะแนนระหว่าง 1 ถึง 5 ดาว']; } $reviewText = trim((string)$reviewText); if (mb_strlen($reviewText) > 2000) { return ['success' => false, 'message' => 'ข้อความรีวิวต้องไม่เกิน 2,000 ตัวอักษร']; } // Calculate allowed new images $existingImages = $this->reviewImageModel->getImagesByReview($reviewId); $remainingCount = count($existingImages) - count($deleteImageIds); $maxNew = max(0, self::MAX_IMAGES - $remainingCount); $newImagePaths = []; if (!empty($newUploadedFiles['name']) && is_array($newUploadedFiles['name']) && $maxNew > 0) { $uploadResult = $this->processImageUploads($newUploadedFiles, $maxNew); if (!$uploadResult['success']) { return $uploadResult; } $newImagePaths = $uploadResult['paths']; } try { $updated = $this->reviewModel->updateReview( $reviewId, $userId, $rating, !empty($reviewText) ? $reviewText : null, $isAnonymous, $newImagePaths, $deleteImageIds ); if ($updated) { $this->activityLog->record($userId, 'review_updated', 'review', $reviewId, "Updated review #{$reviewId}"); return ['success' => true, 'message' => 'แก้ไขรีวิวสินค้าเรียบร้อยแล้ว']; } return ['success' => false, 'message' => 'ไม่สามารถแก้ไขรีวิวได้']; } catch (Exception $e) { return ['success' => false, 'message' => 'เกิดข้อผิดพลาด: ' . $e->getMessage()]; } } /** * Delete review by user */ public function deleteReview(int $userId, int $reviewId): array { $review = $this->reviewModel->findById($reviewId); if (!$review || (int)$review['user_id'] !== $userId) { return ['success' => false, 'message' => 'ไม่พบรีวิวหรือคุณไม่มีสิทธิ์ลบ']; } $deleted = $this->reviewModel->deleteReview($reviewId, $userId); if ($deleted) { $this->activityLog->record($userId, 'review_deleted', 'review', $reviewId, "Deleted review #{$reviewId}"); return ['success' => true, 'message' => 'ลบรีวิวเรียบร้อยแล้ว']; } return ['success' => false, 'message' => 'ไม่สามารถลบรีวิวได้']; } /** * Get Product Reviews and Rating Statistics */ public function getProductReviews(int $productId, int $page = 1, int $perPage = 10, ?int $rating = null): array { $page = max(1, $page); $offset = ($page - 1) * $perPage; $reviews = $this->reviewModel->getProductReviews($productId, $perPage, $offset, $rating); $totalCount = $this->reviewModel->countProductReviews($productId, $rating); $breakdown = $this->reviewModel->getProductRatingBreakdown($productId); return [ 'reviews' => $reviews, 'breakdown' => $breakdown, 'total_count' => $totalCount, 'page' => $page, 'per_page' => $perPage, 'total_pages' => ceil($totalCount / $perPage), 'rating_filter' => $rating ]; } public function getProductRatingBreakdown(int $productId): array { return $this->reviewModel->getProductRatingBreakdown($productId); } /** * Get Store Reviews for Seller */ public function getStoreReviews(int $storeId, array $filters = [], int $page = 1, int $perPage = 20): array { $page = max(1, $page); $offset = ($page - 1) * $perPage; $reviews = $this->reviewModel->getStoreReviews($storeId, $filters, $perPage, $offset); $totalCount = $this->reviewModel->countStoreReviews($storeId, $filters); $summary = $this->reviewModel->getStoreRatingSummary($storeId); return [ 'reviews' => $reviews, 'summary' => $summary, 'total_count' => $totalCount, 'page' => $page, 'per_page' => $perPage, 'total_pages' => ceil($totalCount / $perPage), 'filters' => $filters ]; } public function getStoreRatingSummary(int $storeId): array { return $this->reviewModel->getStoreRatingSummary($storeId); } /** * Get User Reviews & Pending items */ public function getUserReviewData(int $userId): array { $myReviews = $this->reviewModel->getUserReviews($userId); $pendingItems = $this->reviewModel->getPendingReviewItemsForUser($userId); return [ 'my_reviews' => $myReviews, 'pending_items' => $pendingItems ]; } public function getReviewDetail(int $id): ?array { return $this->reviewModel->findDetailById($id); } /** * Report Inappropriate Review via Central Report System */ public function reportReview(int $userId, int $reviewId, string $reason, ?string $description = null): array { $review = $this->reviewModel->findById($reviewId); if (!$review || $review['status'] !== 'active') { return ['success' => false, 'message' => 'ไม่พบรีวิวที่ต้องการรายงาน']; } if ((int)$review['user_id'] === $userId) { return ['success' => false, 'message' => 'คุณไม่สามารถรายงานรีวิวของตนเองได้']; } $reason = trim($reason); if (empty($reason)) { return ['success' => false, 'message' => 'กรุณาระบุเหตุผลในการรายงาน']; } // Check duplicate report $existing = $this->reportModel->fetchOne( "SELECT id FROM `reports` WHERE `reporter_id` = :uid AND `report_type` = 'review' AND `target_id` = :tid AND `status` IN ('pending','reviewing') LIMIT 1", [':uid' => $userId, ':tid' => $reviewId] ); if ($existing) { return ['success' => false, 'message' => 'คุณได้ส่งรายงานสำหรับรีวิวนี้แล้ว และกำลังอยู่ระหว่างการตรวจสอบ']; } $reportId = $this->reportModel->submitReport($userId, 'review', $reviewId, $reason, $description); $this->activityLog->record($userId, 'review_reported', 'review', $reviewId, "Reported review #{$reviewId}: {$reason}"); return [ 'success' => true, 'message' => 'ส่งรายงานเรียบร้อยแล้ว ทีมงานจะดำเนินการตรวจสอบโดยเร็ว', 'report_id' => $reportId ]; } /** * Helper to process, validate, and securely save uploaded images */ private function processImageUploads(array $files, int $maxFiles): array { $uploadDir = __DIR__ . '/../../uploads/reviews/'; if (!is_dir($uploadDir)) { @mkdir($uploadDir, 0755, true); } $paths = []; $fileCount = min(count($files['name']), $maxFiles); for ($i = 0; $i < $fileCount; $i++) { if ($files['error'][$i] === UPLOAD_ERR_NO_FILE) { continue; } if ($files['error'][$i] !== UPLOAD_ERR_OK) { return ['success' => false, 'message' => 'เกิดข้อผิดพลาดในการอัปโหลดไฟล์รูปภาพ']; } if ($files['size'][$i] > self::MAX_FILE_SIZE) { return ['success' => false, 'message' => 'ขนาดไฟล์รูปภาพต้องไม่เกิน 5MB ต่อรูป']; } $tmpPath = $files['tmp_name'][$i]; $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $tmpPath); finfo_close($finfo); if (!in_array($mimeType, self::ALLOWED_MIMES, true)) { return ['success' => false, 'message' => 'รองรับเฉพาะไฟล์รูปภาพประเภท JPG, PNG และ WebP เท่านั้น']; } $ext = strtolower(pathinfo($files['name'][$i], PATHINFO_EXTENSION)); if (!in_array($ext, self::ALLOWED_EXTS, true)) { return ['success' => false, 'message' => 'นามสกุลไฟล์รูปภาพไม่ถูกต้อง']; } $safeFilename = 'rev_' . bin2hex(random_bytes(16)) . '.' . ($ext === 'jpeg' ? 'jpg' : $ext); $destination = $uploadDir . $safeFilename; if (move_uploaded_file($tmpPath, $destination)) { $paths[] = 'uploads/reviews/' . $safeFilename; } else { return ['success' => false, 'message' => 'ไม่สามารถบันทึกไฟล์รูปภาพได้']; } } return ['success' => true, 'paths' => $paths]; } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings