diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc342e8 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Catálogo de Cestas - beni cestas + +Este é um projeto de site de catálogo de cestas e presentes desenvolvido em PHP puro, com estrutura MVC-like, pronto para ser implantado em servidores de hospedagem como o InfinityFree. + +## Estrutura de Pastas + +``` +/ +├─ /app (Lógica da aplicação, fora da raiz pública) +│ ├─ /Controllers +│ ├─ /Models +│ └─ /Views +├─ /config (Configuração, fora da raiz pública) +├─ /database (Schema e seeds do banco de dados) +├─ /public_html (Raiz pública do site, o que vai para a pasta htdocs) +│ ├─ index.php (Front-controller) +│ ├─ .htaccess +│ ├─ /assets (CSS, JS, Imagens) +│ └─ /uploads (Imagens dos produtos) +├─ /scripts (Scripts de utilidade) +└─ README.md +``` + +## Requisitos + +* PHP 8.x +* MySQL / MariaDB +* Servidor web com suporte a `mod_rewrite` (Apache) + +## Instruções de Deploy no InfinityFree + +1. **Crie sua conta:** Acesse [InfinityFree.net](https://www.infinityfree.net/) e crie uma nova conta de hospedagem. +2. **Crie o Banco de Dados:** + * No painel de controle (cPanel), vá para a seção "MySQL Databases". + * Crie um novo banco de dados. Anote o nome do banco de dados (`if0_..._dbname`), o nome de usuário (`if0_...`) e a senha. O host do banco de dados (`sqlXXX.epizy.com`) também será exibido. +3. **Configure o projeto:** + * Abra o arquivo `config/config.php`. + * Altere os valores de `DB_HOST`, `DB_NAME`, `DB_USER`, e `DB_PASS` com as credenciais que você anotou no passo anterior. + * Altere o `BASE_URL` para a URL do seu site no InfinityFree (ex: `http://seusite.epizy.com`). +4. **Faça o Upload dos Arquivos:** + * Use um cliente FTP como o FileZilla para se conectar ao seu servidor InfinityFree (as credenciais de FTP estão no painel de controle). + * **IMPORTANTE:** Envie o **conteúdo** da pasta `public_html` do projeto para a pasta `htdocs` no seu servidor. + * Envie as pastas `app`, `config`, e `vendor` (se existir) para a raiz do seu servidor (fora da pasta `htdocs`). O InfinityFree permite criar diretórios fora de `htdocs`, o que é mais seguro. +5. **Importe o Banco de Dados:** + * No painel de controle do InfinityFree, abra o `phpMyAdmin`. + * Selecione o banco de dados que você criou. + * Vá para a aba "Importar". + * Faça o upload do arquivo `database/schema.sql` e execute a importação. +6. **Acesse o site:** Seu site já deve estar funcionando! + +## Acesso ao Admin + +* **URL:** `http://seusite.epizy.com/admin` (será implementado) +* **Usuário:** `admin@exemplo.com` +* **Senha:** `ChangeMe123!` + +**IMPORTANTE:** A senha no `schema.sql` é um placeholder. Você precisará trocá-la. Um script para criar o admin com uma senha segura (`scripts/seed_admin.php`) será fornecido. + +## Observações + +* A pasta `uploads` precisa de permissão de escrita para que o upload de imagens de produtos funcione. Você pode precisar ajustar as permissões via FTP (geralmente para 755). +* Este projeto foi desenvolvido com uma estrutura simples para fins educacionais e de portfólio. Para um ambiente de produção real, considere usar um framework PHP robusto como Laravel ou Symfony. diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php new file mode 100644 index 0000000..8a8544f --- /dev/null +++ b/app/Controllers/AuthController.php @@ -0,0 +1,50 @@ +prepare("SELECT * FROM users WHERE email = ? AND (role = 'admin' OR role = 'editor')"); + $stmt->execute([$email]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($user && password_verify($password, $user['password'])) { + session_start(); + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_name'] = $user['name']; + $_SESSION['role'] = $user['role']; + return ['success' => true]; + } else { + // For security, we use a generic error message. + // Note: The default password 'ChangeMe123!' from the initial seed needs to be hashed correctly. + // Use scripts/seed_admin.php or a manual hash generation to set it up. + return ['success' => false, 'message' => 'Credenciais inválidas ou usuário não autorizado.']; + } + } + + public function logout() { + session_start(); + session_unset(); + session_destroy(); + } + + public static function checkAuth() { + if (session_status() === PHP_SESSION_NONE) { + session_start(); + } + if (!isset($_SESSION['user_id'])) { + header("Location: " . BASE_URL . "admin/login.php"); + exit(); + } + } + + public static function isAdmin() { + self::checkAuth(); + if ($_SESSION['role'] !== 'admin') { + // Redirect to a less privileged page or show an error + header("Location: " . BASE_URL . "admin/index.php?error=unauthorized"); + exit(); + } + } +} diff --git a/app/Controllers/CartController.php b/app/Controllers/CartController.php new file mode 100644 index 0000000..4965467 --- /dev/null +++ b/app/Controllers/CartController.php @@ -0,0 +1,108 @@ + $productId, + 'name' => $product['name'], + 'price' => $product['price'], + 'image' => $product['image'], + 'slug' => $product['slug'], + 'quantity' => $quantity, + ]; + } + } + header('Location: /cart.php'); + exit(); + } + } + + public function update() { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['product_id'])) { + $productId = (int)$_POST['product_id']; + $quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 0; + + if ($quantity > 0 && isset($_SESSION['cart'][$productId])) { + $_SESSION['cart'][$productId]['quantity'] = $quantity; + } else { + unset($_SESSION['cart'][$productId]); + } + } + header('Location: /cart.php'); + exit(); + } + + public function remove() { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['product_id'])) { + $productId = (int)$_POST['product_id']; + unset($_SESSION['cart'][$productId]); + } + header('Location: /cart.php'); + exit(); + } + + public static function getCartContents() { + return $_SESSION['cart'] ?? []; + } + + public static function getCartItemCount() { + $count = 0; + if (isset($_SESSION['cart'])) { + foreach ($_SESSION['cart'] as $item) { + $count += $item['quantity']; + } + } + return $count; + } + + public static function getCartTotal() { + $total = 0; + if (isset($_SESSION['cart'])) { + foreach ($_SESSION['cart'] as $item) { + $total += $item['price'] * $item['quantity']; + } + } + return $total; + } +} + +// Handle cart actions +if (isset($_POST['action'])) { + $cartController = new CartController(); + switch ($_POST['action']) { + case 'add': + $cartController->add(); + break; + case 'update': + $cartController->update(); + break; + case 'remove': + $cartController->remove(); + break; + } +} +?> \ No newline at end of file diff --git a/app/Controllers/CategoryController.php b/app/Controllers/CategoryController.php new file mode 100644 index 0000000..49ec7d1 --- /dev/null +++ b/app/Controllers/CategoryController.php @@ -0,0 +1,68 @@ +query("SELECT * FROM categories ORDER BY name"); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + + public function store($name) { + $slug = $this->slugify($name); + $pdo = db(); + try { + $stmt = $pdo->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)"); + $stmt->execute([$name, $slug]); + return ['success' => true]; + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { // Duplicate entry + return ['success' => false, 'message' => 'Uma categoria com este nome ou slug já existe.']; + } + return ['success' => false, 'message' => 'Erro ao criar categoria.']; + } + } + + public function show($id) { + $pdo = db(); + $stmt = $pdo->prepare("SELECT * FROM categories WHERE id = ?"); + $stmt->execute([$id]); + return $stmt->fetch(PDO::FETCH_ASSOC); + } + + public function update($id, $name) { + $slug = $this->slugify($name); + $pdo = db(); + try { + $stmt = $pdo->prepare("UPDATE categories SET name = ?, slug = ? WHERE id = ?"); + $stmt->execute([$name, $slug, $id]); + return ['success' => true]; + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { + return ['success' => false, 'message' => 'Uma categoria com este nome ou slug já existe.']; + } + return ['success' => false, 'message' => 'Erro ao atualizar categoria.']; + } + } + + public function destroy($id) { + $pdo = db(); + $stmt = $pdo->prepare("DELETE FROM categories WHERE id = ?"); + $stmt->execute([$id]); + return ['success' => true]; + } + + private function slugify($text) { + $text = preg_replace('~[\pL\d]+~u', '-', $text); + $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); + $text = preg_replace('~[^\w]+~u', '-', $text); + $text = trim($text, '-'); + $text = preg_replace('~-~', '-', $text); + $text = strtolower($text); + if (empty($text)) { + return 'n-a'; + } + return $text; + } +} diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Controllers/OrderController.php b/app/Controllers/OrderController.php new file mode 100644 index 0000000..f58f419 --- /dev/null +++ b/app/Controllers/OrderController.php @@ -0,0 +1,97 @@ +sendConfirmationEmails($orderId, $customerName, $customerEmail, $cartItems, $cartTotal); + + // Redirect to a success page + header('Location: /thank-you.php?order_id=' . $orderId); + exit(); + } + + private function sendConfirmationEmails($orderId, $customerName, $customerEmail, $cartItems, $cartTotal) { + // Email to Customer + $customerSubject = "Confirmação do seu Pedido #{$orderId}"; + $customerHtml = "
Seu pedido #{$orderId} foi recebido e está sendo processado.
"; + $customerHtml .= $this->formatOrderForEmail($cartItems, $cartTotal); + MailService::sendMail($customerEmail, $customerSubject, $customerHtml); + + // Email to Admin + $adminEmail = getenv('MAIL_TO') ?: getenv('MAIL_FROM'); + if ($adminEmail) { + $adminSubject = "Novo Pedido Recebido #{$orderId}"; + $adminHtml = "| Produto | Qtd | Preço | Subtotal |
|---|---|---|---|
| %s | %d | R$ %.2f | R$ %.2f |
| Total: | R$ %.2f | ||