Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
84.08% |
206 / 245 |
|
52.00% |
13 / 25 |
CRAP | |
0.00% |
0 / 1 |
AdminController | |
84.02% |
205 / 244 |
|
52.00% |
13 / 25 |
85.33 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDashboardData | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
getTotalPendings | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
getTotalCompleted | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
getOrdersCount | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getProductsCount | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getUsersCount | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getAdminsCount | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getTotalAccounts | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getMessagesCount | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
handleDatabaseError | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addProduct | |
76.47% |
13 / 17 |
|
0.00% |
0 / 1 |
5.33 | |||
deleteProduct | |
76.92% |
10 / 13 |
|
0.00% |
0 / 1 |
5.31 | |||
validateImageName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSecureImagePath | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
6.02 | |||
handleImageDelete | |
64.71% |
11 / 17 |
|
0.00% |
0 / 1 |
10.81 | |||
updateProduct | |
85.37% |
35 / 41 |
|
0.00% |
0 / 1 |
10.31 | |||
getAllProducts | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getAllOrders | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
updateOrderStatus | |
62.50% |
5 / 8 |
|
0.00% |
0 / 1 |
2.21 | |||
deleteOrder | |
57.14% |
4 / 7 |
|
0.00% |
0 / 1 |
2.31 | |||
getAllUsers | |
78.57% |
11 / 14 |
|
0.00% |
0 / 1 |
3.09 | |||
deleteUser | |
57.14% |
4 / 7 |
|
0.00% |
0 / 1 |
2.31 | |||
getAllMessages | |
80.00% |
12 / 15 |
|
0.00% |
0 / 1 |
3.07 | |||
deleteMessage | |
50.00% |
3 / 6 |
|
0.00% |
0 / 1 |
2.50 |
1 | <?php |
2 | namespace Controllers; |
3 | |
4 | require_once __DIR__ . '/../autoload.php'; |
5 | |
6 | use Models\Product; |
7 | use Models\Order; |
8 | use Models\User; |
9 | use Models\Message; |
10 | |
11 | class 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 | } |