Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.08% covered (warning)
84.08%
206 / 245
52.00% covered (warning)
52.00%
13 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
AdminController
84.02% covered (warning)
84.02%
205 / 244
52.00% covered (warning)
52.00%
13 / 25
85.33
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDashboardData
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getTotalPendings
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 getTotalCompleted
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 getOrdersCount
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getProductsCount
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getUsersCount
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getAdminsCount
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getTotalAccounts
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getMessagesCount
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 handleDatabaseError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addProduct
76.47% covered (warning)
76.47%
13 / 17
0.00% covered (danger)
0.00%
0 / 1
5.33
 deleteProduct
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
5.31
 validateImageName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSecureImagePath
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
6.02
 handleImageDelete
64.71% covered (warning)
64.71%
11 / 17
0.00% covered (danger)
0.00%
0 / 1
10.81
 updateProduct
85.37% covered (warning)
85.37%
35 / 41
0.00% covered (danger)
0.00%
0 / 1
10.31
 getAllProducts
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getAllOrders
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 updateOrderStatus
62.50% covered (warning)
62.50%
5 / 8
0.00% covered (danger)
0.00%
0 / 1
2.21
 deleteOrder
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
2.31
 getAllUsers
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
3.09
 deleteUser
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
2.31
 getAllMessages
80.00% covered (warning)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
3.07
 deleteMessage
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
1<?php
2namespace Controllers;
3
4require_once __DIR__ . '/../autoload.php';
5
6use Models\Product;
7use Models\Order;
8use Models\User;
9use Models\Message;
10
11class AdminController {
12    private const UPLOAD_PATH = '../../uploaded_img/';
13    private const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png'];
14    private $conn;
15
16    public function __construct($conn) {
17        $this->conn = $conn;
18    }
19
20    public function getDashboardData() {
21        $data = [];
22        
23        // Obtener total pendientes
24        $data['total_pendings'] = $this->getTotalPendings();
25        $data['total_completed'] = $this->getTotalCompleted();
26        $data['orders_count'] = $this->getOrdersCount();
27        $data['products_count'] = $this->getProductsCount();
28        $data['users_count'] = $this->getUsersCount();
29        $data['admins_count'] = $this->getAdminsCount();
30        $data['total_accounts'] = $this->getTotalAccounts();
31        $data['messages_count'] = $this->getMessagesCount();
32
33        return $data;
34    }
35
36    public function getTotalPendings() {
37        try {
38            $stmt = $this->conn->prepare("SELECT * FROM `orders` WHERE payment_status = 'pendiente'");
39            $stmt->execute();
40            $total = 0;
41            foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
42                $order = new Order();
43                $order->setId($row['id']);
44                $order->setTotalPrice($row['total_price']);
45                $total += $order->getTotalPrice();
46            }
47            return $total;
48        } catch (\Exception $e) {
49            $this->handleDatabaseError($e);
50        }
51    }
52
53    private function getTotalCompleted() {
54        $stmt = $this->conn->prepare("SELECT * FROM `orders` WHERE payment_status = 'completado'");
55        $stmt->execute();
56        $total = 0;
57        foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
58            $order = new Order();
59            $order->setId($row['id']);
60            $order->setTotalPrice($row['total_price']);
61            $total += $order->getTotalPrice();
62        }
63        return $total;
64    }
65
66    private function getOrdersCount() {
67        $query = "SELECT COUNT(*) as count FROM `orders`";
68        $stmt = $this->conn->prepare($query);
69        $stmt->execute();
70        return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
71    }
72
73    private function getProductsCount() {
74        $query = "SELECT COUNT(*) as count FROM `products`";
75        $stmt = $this->conn->prepare($query);
76        $stmt->execute();
77        return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
78    }
79
80    private function getUsersCount() {
81        $query = "SELECT COUNT(*) as count FROM `users` WHERE user_type = 'user'";
82        $stmt = $this->conn->prepare($query);
83        $stmt->execute();
84        return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
85    }
86
87    private function getAdminsCount() {
88        $query = "SELECT COUNT(*) as count FROM `users` WHERE user_type = 'admin'";
89        $stmt = $this->conn->prepare($query);
90        $stmt->execute();
91        return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
92    }
93
94    private function getTotalAccounts() {
95        $query = "SELECT COUNT(*) as count FROM `users`";
96        $stmt = $this->conn->prepare($query);
97        $stmt->execute();
98        return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
99    }
100
101    private function getMessagesCount() {
102        $query = "SELECT COUNT(*) as count FROM `message`";
103        $stmt = $this->conn->prepare($query);
104        $stmt->execute();
105        return $stmt->fetch(\PDO::FETCH_ASSOC)['count'];
106    }
107
108    private function handleDatabaseError($e) {
109        error_log("Error en la base de datos: " . $e->getMessage());
110        throw new \Exception("Error al procesar la solicitud");
111    }
112
113    public function addProduct($postData, $files) {
114        try {
115            $product = new Product();
116            $product->setName($postData['name']);
117            $product->setPrice($postData['price']);
118            $product->setImage($files['image']['name']);
119
120            // Verificar si el producto ya existe
121            $stmt = $this->conn->prepare("SELECT name FROM `products` WHERE name = ?");
122            $stmt->execute([$product->getName()]);
123            
124            if($stmt->rowCount() > 0) {
125                return ['success' => false, 'message' => 'El producto ya existe'];
126            }
127
128            if($files['image']['size'] > 2000000) {
129                return ['success' => false, 'message' => 'El tamaño de la imagen es demasiado grande'];
130            }
131
132            $stmt = $this->conn->prepare("INSERT INTO `products`(name, price, image) VALUES(?, ?, ?)");
133            if($stmt->execute([$product->getName(), $product->getPrice(), $product->getImage()])) {
134                move_uploaded_file($files['image']['tmp_name'], self::UPLOAD_PATH . $product->getImage());
135                return ['success' => true, 'message' => '¡Producto añadido exitosamente!'];
136            }
137        } catch (\Exception $e) {
138            $this->handleDatabaseError($e);
139            return ['success' => false, 'message' => 'Error al añadir el producto'];
140        }
141    }
142
143    public function deleteProduct($id) {
144        try {
145            // Obtener información de la imagen
146            $stmt = $this->conn->prepare("SELECT image FROM `products` WHERE id = ?");
147            $stmt->execute([$id]);
148            $image_data = $stmt->fetch(\PDO::FETCH_ASSOC);
149            
150            if($image_data) {
151                $imagePath = self::UPLOAD_PATH . $image_data['image'];
152                if(file_exists($imagePath)) {
153                    unlink($imagePath);
154                }
155            }
156            
157            $stmt = $this->conn->prepare("DELETE FROM `products` WHERE id = ?");
158            if($stmt->execute([$id])) {
159                return ['success' => true, 'message' => 'Producto eliminado'];
160            }
161            
162            return ['success' => false, 'message' => 'Error al eliminar el producto'];
163        } catch (\Exception $e) {
164            return ['success' => false, 'message' => 'Error al eliminar el producto: ' . $e->getMessage()];
165        }
166    }
167
168    private function validateImageName($imageName) {
169        // Solo permitir letras, números, guiones y puntos
170        return preg_match('/^[a-zA-Z0-9_.-]+$/', $imageName);
171    }
172
173    private function getSecureImagePath($imageName) {
174        try {
175            // Validar el nombre del archivo
176            if (empty($imageName) || !is_string($imageName)) {
177                throw new \Exception('Nombre de archivo inválido');
178            }
179
180            // Obtener y validar la extensión
181            $extension = strtolower(pathinfo($imageName, PATHINFO_EXTENSION));
182            if (!in_array($extension, self::ALLOWED_EXTENSIONS)) {
183                throw new \Exception('Tipo de archivo no permitido');
184            }
185
186            // Verificar que el archivo existe en la base de datos
187            $stmt = $this->conn->prepare("SELECT image FROM products WHERE image = ?");
188            $stmt->execute([$imageName]);
189            if (!$stmt->fetch()) {
190                throw new \Exception('Archivo no encontrado en la base de datos');
191            }
192
193            return self::UPLOAD_PATH . $imageName;
194        } catch (\Exception $e) {
195            error_log("Error en getSecureImagePath: " . $e->getMessage());
196            return false;
197        }
198    }
199
200    private function handleImageDelete($imageName) {
201        try {
202            // Validar el nombre del archivo
203            if (empty($imageName) || !is_string($imageName)) {
204                return false;
205            }
206
207            // Verificar en la base de datos
208            $stmt = $this->conn->prepare("SELECT image FROM products WHERE image = ? LIMIT 1");
209            $stmt->execute([$imageName]);
210            $result = $stmt->fetch(\PDO::FETCH_ASSOC);
211
212            if (!$result) {
213                return false;
214            }
215
216            // Construir y validar la ruta
217            $fullPath = realpath(self::UPLOAD_PATH . $result['image']);
218            $uploadDir = realpath(self::UPLOAD_PATH);
219
220            // Verificar que el archivo está dentro del directorio permitido
221            if ($fullPath === false || strpos($fullPath, $uploadDir) !== 0) {
222                return false;
223            }
224
225            // Eliminar el archivo si existe
226            if (file_exists($fullPath)) {
227                return unlink($fullPath);
228            }
229
230            return false;
231        } catch (\Exception $e) {
232            error_log("Error al eliminar imagen: " . $e->getMessage());
233            return false;
234        }
235    }
236
237    public function updateProduct($postData, $files) {
238        try {
239            // Validar que los datos no estén vacíos
240            if (empty($postData['update_p_id']) || 
241                empty($postData['update_name']) || 
242                empty($postData['update_price'])) {
243                return [
244                    'success' => false, 
245                    'message' => 'Datos del producto inválidos'
246                ];
247            }
248
249            // Validar que el precio sea numérico
250            if (!is_numeric($postData['update_price'])) {
251                return [
252                    'success' => false, 
253                    'message' => 'Precio inválido'
254                ];
255            }
256
257            $product = new Product();
258            $product->setId($postData['update_p_id']);
259            $product->setName($postData['update_name']);
260            $product->setPrice($postData['update_price']);
261            
262            if(!empty($files['update_image']['name'])) {
263                if($files['update_image']['size'] > 2000000) {
264                    return ['success' => false, 'message' => 'El tamaño de la imagen es demasiado grande'];
265                }
266
267                // Eliminar imagen anterior de forma segura
268                if (!empty($postData['update_old_image'])) {
269                    $this->handleImageDelete($postData['update_old_image']);
270                }
271
272                // Generar nombre Ãºnico para la nueva imagen
273                $extension = strtolower(pathinfo($files['update_image']['name'], PATHINFO_EXTENSION));
274                $newImageName = uniqid() . '.' . $extension;
275                $product->setImage($newImageName);
276                
277                // Subir nueva imagen
278                move_uploaded_file(
279                    $files['update_image']['tmp_name'], 
280                    self::UPLOAD_PATH . $newImageName
281                );
282                
283                $stmt = $this->conn->prepare("UPDATE `products` SET name = ?, price = ?, image = ? WHERE id = ?");
284                $params = [$product->getName(), $product->getPrice(), $product->getImage(), $product->getId()];
285            } else {
286                $stmt = $this->conn->prepare("UPDATE `products` SET name = ?, price = ? WHERE id = ?");
287                $params = [$product->getName(), $product->getPrice(), $product->getId()];
288            }
289            
290            if($stmt->execute($params)) {
291                return ['success' => true, 'message' => 'Producto actualizado exitosamente'];
292            }
293            
294            return ['success' => false, 'message' => 'Error al actualizar el producto'];
295        } catch (\Exception $e) {
296            error_log("Error al actualizar producto: " . $e->getMessage());
297            return [
298                'success' => false,
299                'message' => 'Error al actualizar el producto'
300            ];
301        }
302    }
303
304    public function getAllProducts() {
305        $stmt = $this->conn->query("SELECT * FROM `products`");
306        $products = [];
307        foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
308            $product = new Product();
309            $product->setId($row['id']);
310            $product->setName($row['name']);
311            $product->setPrice($row['price']);
312            $product->setImage($row['image']);
313            $products[] = $product;
314        }
315        return $products;
316    }
317
318    public function getAllOrders() {
319        $stmt = $this->conn->prepare("SELECT * FROM `orders`");
320        $stmt->execute();
321        $orders = [];
322        foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
323            $order = new Order();
324            $order->setId($row['id']);
325            $order->setUserId($row['user_id']);
326            $order->setPlacedOn($row['placed_on']);
327            $order->setName($row['name']);
328            $order->setNumber($row['number']);
329            $order->setEmail($row['email']);
330            $order->setAddress($row['address']);
331            $order->setTotalProducts($row['total_products']);
332            $order->setTotalPrice($row['total_price']);
333            $order->setMethod($row['method']);
334            $order->setPaymentStatus($row['payment_status']);
335            $orders[] = $order;
336        }
337        return $orders;
338    }
339
340    public function updateOrderStatus($orderId, $status) {
341        try {
342            $order = new Order();
343            $order->setId($orderId);
344            $order->setPaymentStatus($status);
345            
346            $stmt = $this->conn->prepare("UPDATE `orders` SET payment_status = ? WHERE id = ?");
347            return $stmt->execute([$order->getPaymentStatus(), $order->getId()]);
348        } catch (\PDOException $e) {
349            $this->handleDatabaseError($e);
350            return false;
351        }
352    }
353
354    public function deleteOrder($orderId) {
355        try {
356            $order = new Order();
357            $order->setId($orderId);
358            
359            $stmt = $this->conn->prepare("DELETE FROM `orders` WHERE id = ?");
360            return $stmt->execute([$order->getId()]);
361        } catch (\PDOException $e) {
362            $this->handleDatabaseError($e);
363            return false;
364        }
365    }
366
367    public function getAllUsers() {
368        try {
369            $stmt = $this->conn->prepare("SELECT * FROM `users`");
370            $stmt->execute();
371            $users = [];
372            
373            foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
374                $user = new User();
375                $user->setId($row['id']);
376                $user->setName($row['name']);
377                $user->setEmail($row['email']);
378                $user->setUserType($row['user_type']);
379                $users[] = $user;
380            }
381            return $users;
382        } catch (\PDOException $e) {
383            $this->handleDatabaseError($e);
384            return [];
385        }
386    }
387
388    public function deleteUser($userId) {
389        try {
390            $user = new User();
391            $user->setId($userId);
392            
393            $stmt = $this->conn->prepare("DELETE FROM `users` WHERE id = ?");
394            return $stmt->execute([$user->getId()]);
395        } catch (\PDOException $e) {
396            $this->handleDatabaseError($e);
397            return false;
398        }
399    }
400
401    public function getAllMessages() {
402        try {
403            $stmt = $this->conn->query("SELECT * FROM `message`");
404            $messages = [];
405            foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
406                $message = new Message();
407                $message->setId($row['id']);
408                $message->setUserId($row['user_id']);
409                $message->setMessage($row['message']);
410                $message->setName($row['name']);
411                $message->setEmail($row['email']);
412                $message->setNumber($row['number']);
413                $messages[] = $message;
414            }
415            return $messages;
416        } catch (\PDOException $e) {
417            $this->handleDatabaseError($e);
418            return [];
419        }
420    }
421
422    public function deleteMessage($messageId) {
423        try {
424            $query = "DELETE FROM `message` WHERE id = ?";
425            $stmt = $this->conn->prepare($query);
426            return $stmt->execute([$messageId]);
427        } catch (\PDOException $e) {
428            $this->handleDatabaseError($e);
429            return false;
430        }
431    }
432}