From df0bf77ba7af2aaa2d04090853265dec03875e52 Mon Sep 17 00:00:00 2001
From: Marco Antonio Vivas
Date: Wed, 17 Sep 2025 01:43:51 -0300
Subject: [PATCH] Initial commit
---
.gitattributes | 2 +
assets/css/admin.css | 40 +++++
assets/css/frontend.css | 345 ++++++++++++++++++++++++++++++++++++
assets/css/style.css | 152 ++++++++++++++++
assets/js/frontend.js | 11 ++
includes/admin-ui.php | 185 +++++++++++++++++++
includes/assets.php | 25 +++
includes/frontend.php | 99 +++++++++++
includes/notifications.php | 51 ++++++
includes/post-types.php | 107 +++++++++++
referencia.md | 189 ++++++++++++++++++++
simple-ticket-system.php | 49 +++++
templates/form-ticket.php | 49 +++++
templates/single-ticket.php | 66 +++++++
templates/view-ticket.php | 47 +++++
15 files changed, 1417 insertions(+)
create mode 100644 .gitattributes
create mode 100644 assets/css/admin.css
create mode 100644 assets/css/frontend.css
create mode 100644 assets/css/style.css
create mode 100644 assets/js/frontend.js
create mode 100644 includes/admin-ui.php
create mode 100644 includes/assets.php
create mode 100644 includes/frontend.php
create mode 100644 includes/notifications.php
create mode 100644 includes/post-types.php
create mode 100644 referencia.md
create mode 100644 simple-ticket-system.php
create mode 100644 templates/form-ticket.php
create mode 100644 templates/single-ticket.php
create mode 100644 templates/view-ticket.php
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/assets/css/admin.css b/assets/css/admin.css
new file mode 100644
index 0000000..4d7a982
--- /dev/null
+++ b/assets/css/admin.css
@@ -0,0 +1,40 @@
+/* Estilos para a lista de tickets no painel de administração */
+
+/* Ajuste de largura das colunas */
+.post-type-ticket .wp-list-table #ticket_id {
+ width: 8%;
+}
+
+.post-type-ticket .wp-list-table #status {
+ width: 12%;
+}
+
+.post-type-ticket .wp-list-table #ticket_type {
+ width: 15%;
+}
+
+/* Estilo base para as etiquetas de status */
+.sts-admin-status {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 15px;
+ font-weight: 600;
+ font-size: 12px;
+ color: #fff;
+ text-shadow: none;
+ white-space: nowrap;
+}
+
+/* Cores das etiquetas de status */
+.sts-admin-status.status-aberto {
+ background-color: #ffb700; /* Amarelo */
+ color: #212529; /* Preto */
+}
+
+.sts-admin-status.status-em-andamento {
+ background-color: #0582ca; /* Azul Claro */
+}
+
+.sts-admin-status.status-resolvido {
+ background-color: #028c6a; /* Verde Sucesso */
+}
\ No newline at end of file
diff --git a/assets/css/frontend.css b/assets/css/frontend.css
new file mode 100644
index 0000000..4abd807
--- /dev/null
+++ b/assets/css/frontend.css
@@ -0,0 +1,345 @@
+/* Importando as variáveis de referência */
+:root {
+ --azul-principal: #006494;
+ --azul-escuro: #003554;
+ --azul-claro: #0582ca;
+ --amarelo: #ffb700;
+ --cinza-claro: #f8f9fa;
+ --cinza: #e9ecef;
+ --preto: #212529;
+ --branco: #ffffff;
+ --verde-sucesso: #028c6a;
+ --vermelho-erro: #a94442;
+ --sombra: 0 4px 6px rgba(0, 0, 0, 0.1);
+ --borda-radius: 8px;
+ --transicao: all 0.3s ease;
+}
+
+/* Estilo do Formulário de Ticket */
+.sts-ticket-form {
+ background: var(--branco);
+ padding: 30px;
+ border-radius: var(--borda-radius);
+ box-shadow: var(--sombra);
+ max-width: 700px;
+ margin: 20px auto;
+}
+
+.sts-ticket-form p {
+ margin-bottom: 20px;
+}
+
+.sts-ticket-form label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 600;
+ color: var(--azul-escuro);
+}
+
+.sts-ticket-form input[type="text"],
+.sts-ticket-form textarea,
+.sts-ticket-form select {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid var(--cinza);
+ border-radius: var(--borda-radius);
+ transition: var(--transicao);
+ background-color: var(--cinza-claro);
+ color: var(--preto);
+}
+
+.sts-ticket-form input[type="text"]:focus,
+.sts-ticket-form textarea:focus,
+.sts-ticket-form select:focus {
+ outline: none;
+ border-color: var(--azul-principal);
+ box-shadow: 0 0 0 3px rgba(0, 100, 148, 0.15);
+}
+
+.sts-ticket-form small {
+ display: block;
+ margin-top: 5px;
+ color: #777;
+}
+
+/* Botão de Envio */
+.sts-ticket-form input[type="submit"] {
+ background: var(--azul-principal);
+ color: var(--branco);
+ padding: 12px 25px;
+ border-radius: var(--borda-radius);
+ font-weight: 600;
+ text-decoration: none;
+ transition: var(--transicao);
+ display: inline-block;
+ border: none;
+ cursor: pointer;
+ font-size: 1rem;
+}
+
+.sts-ticket-form input[type="submit"]:hover {
+ background: var(--azul-escuro);
+ box-shadow: var(--sombra);
+}
+
+/* Alertas de Sucesso/Erro */
+.sts-alert {
+ padding: 15px;
+ margin-bottom: 20px;
+ border-radius: var(--borda-radius);
+ max-width: 700px;
+ margin: 20px auto;
+ font-weight: 500;
+}
+
+.sts-alert-success {
+ background-color: #e6f7ee;
+ border-left: 5px solid var(--verde-sucesso);
+ color: var(--verde-sucesso);
+}
+
+.sts-alert-error {
+ background-color: #f2dede;
+ border-left: 5px solid var(--vermelho-erro);
+ color: var(--vermelho-erro);
+}
+
+/* Estilo customizado para o input de arquivo */
+.sts-file-input-wrapper {
+ position: relative;
+ overflow: hidden;
+ display: inline-block;
+ cursor: pointer;
+}
+
+.sts-file-input-wrapper input[type=file] {
+ position: absolute;
+ left: 0;
+ top: 0;
+ opacity: 0;
+ cursor: pointer;
+}
+
+.sts-file-input-button {
+ background: var(--cinza);
+ color: var(--preto);
+ padding: 10px 15px;
+ border-radius: var(--borda-radius);
+ border: 1px solid #ccc;
+ display: inline-block;
+}
+
+.sts-file-name {
+ margin-left: 10px;
+ font-style: italic;
+ color: #555;
+}
+
+/* Estilo da Lista de Tickets (Meus Tickets) */
+.sts-section-title {
+ color: var(--azul-escuro);
+ margin-bottom: 25px;
+ font-size: 1.8rem;
+ border-bottom: 2px solid var(--cinza);
+ padding-bottom: 10px;
+}
+
+.sts-ticket-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 25px;
+}
+
+.sts-ticket-card {
+ background: var(--branco);
+ border-radius: var(--borda-radius);
+ box-shadow: var(--sombra);
+ padding: 20px;
+ text-decoration: none;
+ color: var(--preto);
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ transition: var(--transicao);
+ position: relative;
+ border-left: 5px solid var(--cinza);
+ animation: fadeIn 0.5s ease-out forwards;
+}
+
+.sts-ticket-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
+}
+
+.sts-card-ticket-id {
+ position: absolute;
+ top: -10px;
+ left: -10px;
+ background: var(--azul-escuro);
+ color: var(--branco);
+ padding: 5px 10px;
+ font-size: 0.8rem;
+ font-weight: bold;
+ border-radius: var(--borda-radius);
+}
+
+.sts-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 15px;
+}
+
+.sts-card-title {
+ font-size: 1.2rem;
+ color: var(--azul-escuro);
+ margin: 0;
+ font-weight: 600;
+}
+
+.sts-ticket-status {
+ font-size: 0.8rem;
+ font-weight: 700;
+ padding: 4px 10px;
+ border-radius: 15px;
+ color: var(--branco);
+ white-space: nowrap;
+}
+
+.sts-card-body p {
+ margin: 0 0 10px;
+ color: #555;
+}
+
+.sts-card-footer {
+ margin-top: auto;
+ padding-top: 15px;
+ border-top: 1px solid var(--cinza);
+ font-size: 0.85rem;
+ color: #777;
+}
+
+/* Cores dos Status */
+/* Aberto: Laranja */
+.sts-ticket-card.aberto { border-left-color: var(--amarelo); }
+.sts-ticket-status.aberto { background-color: var(--amarelo); color: var(--preto); }
+
+/* Em Andamento: Azul */
+.sts-ticket-card.em-andamento { border-left-color: var(--azul-claro); }
+.sts-ticket-status.em-andamento { background-color: var(--azul-claro); color: var(--branco); }
+
+/* Resolvido: Verde */
+.sts-ticket-card.resolvido { border-left-color: var(--verde-sucesso); }
+.sts-ticket-status.resolvido { background-color: var(--verde-sucesso); color: var(--branco); }
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* Estilos para a página de um único ticket (single-ticket.php) */
+.sts-content-area {
+ max-width: 900px;
+ margin: 20px auto;
+ padding: 20px;
+}
+
+.sts-single-ticket {
+ background: var(--branco);
+ padding: 30px;
+ border-radius: var(--borda-radius);
+ box-shadow: var(--sombra);
+}
+
+.sts-title-wrapper {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.sts-single-ticket-id {
+ background: var(--cinza);
+ padding: 5px 15px;
+ border-radius: 20px;
+ font-weight: bold;
+ color: var(--azul-escuro);
+}
+
+.sts-ticket-header .sts-ticket-title {
+ font-size: 2rem;
+ color: var(--azul-escuro);
+ margin-top: 0;
+ margin-bottom: 15px;
+}
+
+.sts-ticket-meta {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 15px;
+ padding-bottom: 20px;
+ margin-bottom: 20px;
+ border-bottom: 1px solid var(--cinza);
+}
+
+.sts-ticket-meta .sts-ticket-status {
+ order: -1; /* Coloca o status primeiro */
+}
+
+.sts-ticket-content,
+.sts-ticket-attachments {
+ margin-bottom: 30px;
+}
+
+.sts-ticket-content h3,
+.sts-ticket-attachments h3 {
+ color: var(--azul-escuro);
+ border-bottom: 2px solid var(--cinza);
+ padding-bottom: 5px;
+ margin-bottom: 15px;
+}
+
+/* Estilos para a seção de respostas (comentários do WordPress) */
+.sts-ticket-responses .comments-title {
+ font-size: 1.5rem;
+ color: var(--azul-escuro);
+}
+
+.sts-ticket-responses .comment-list {
+ list-style: none;
+ padding: 0;
+}
+
+.sts-ticket-responses .comment-body {
+ background: var(--cinza-claro);
+ padding: 20px;
+ border-radius: var(--borda-radius);
+ margin-bottom: 20px;
+ border-left: 4px solid var(--azul-principal);
+}
+
+.sts-ticket-responses .comment-meta {
+ margin-bottom: 10px;
+}
+
+.sts-ticket-responses .comment-form-comment textarea {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid var(--cinza);
+ border-radius: var(--borda-radius);
+}
+
+.sts-ticket-responses .form-submit .submit {
+ background: var(--azul-principal);
+ color: var(--branco);
+ padding: 10px 20px;
+ border-radius: var(--borda-radius);
+ border: none;
+ cursor: pointer;
+ transition: var(--transicao);
+}
+
+.sts-ticket-responses .form-submit .submit:hover {
+ background: var(--azul-escuro);
+}
\ No newline at end of file
diff --git a/assets/css/style.css b/assets/css/style.css
new file mode 100644
index 0000000..98d7ac1
--- /dev/null
+++ b/assets/css/style.css
@@ -0,0 +1,152 @@
+/* Variáveis CSS Globais */
+:root {
+ --azul-principal: #006494;
+ --azul-escuro: #003554;
+ --azul-claro: #0582ca;
+ --azul-brilhante: #00a6fb;
+ --amarelo: #ffb700;
+ --cinza-claro: #f8f9fa;
+ --cinza: #e9ecef;
+ --preto: #212529;
+ --branco: #ffffff;
+ --sombra: 0 4px 6px rgba(0, 0, 0, 0.1);
+ --borda-radius: 8px;
+ --transicao: all 0.3s ease;
+}
+
+/* Tipografia Base */
+body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ color: var(--preto);
+ line-height: 1.6;
+}
+
+/* Layout Base */
+.grid-layout {
+ display: grid;
+ grid-template-columns: 250px 1fr;
+ gap: 30px;
+ padding: 20px;
+}
+
+/* Botões e Links */
+.btn-primario {
+ background: var(--branco);
+ color: var(--azul-principal);
+ padding: 12px 25px;
+ border-radius: var(--borda-radius);
+ font-weight: 600;
+ text-decoration: none;
+ transition: var(--transicao);
+ display: inline-block;
+}
+
+.btn-primario:hover {
+ background: var(--azul-principal);
+ color: var(--branco);
+ box-shadow: var(--sombra);
+}
+
+/* Cards */
+.ticket-card {
+ background: var(--branco);
+ border-radius: var(--borda-radius);
+ padding: 25px;
+ margin-bottom: 20px;
+ box-shadow: var(--sombra);
+ transition: var(--transicao);
+}
+
+.ticket-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
+}
+
+/* Formulários */
+.form-grupo {
+ margin-bottom: 20px;
+}
+
+.form-grupo label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 500;
+ color: var(--azul-escuro);
+}
+
+.form-grupo input[type="text"],
+.form-grupo input[type="email"],
+.form-grupo textarea {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid var(--cinza);
+ border-radius: var(--borda-radius);
+ transition: var(--transicao);
+}
+
+.form-grupo input[type="text"]:focus,
+.form-grupo input[type="email"]:focus,
+.form-grupo textarea:focus {
+ outline: none;
+ border-color: var(--azul-principal);
+ box-shadow: 0 0 0 2px rgba(0, 100, 148, 0.1);
+}
+
+/* Status do Ticket */
+.status-badge {
+ display: inline-block;
+ padding: 6px 12px;
+ border-radius: 20px;
+ font-size: 0.875rem;
+ font-weight: 600;
+}
+
+.status-aberto {
+ background-color: var(--azul-claro);
+ color: var(--branco);
+}
+
+.status-fechado {
+ background-color: var(--cinza);
+ color: var(--preto);
+}
+
+.status-andamento {
+ background-color: var(--amarelo);
+ color: var(--preto);
+}
+
+/* Animações */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.animate-fade-in {
+ animation: fadeIn 0.5s ease-out forwards;
+}
+
+/* Media Queries */
+@media (max-width: 900px) {
+ .grid-layout {
+ display: block;
+ }
+
+ .ticket-card {
+ margin: 15px 0;
+ }
+}
+
+@media (max-width: 600px) {
+ .btn-primario {
+ display: block;
+ text-align: center;
+ width: 100%;
+ }
+}
diff --git a/assets/js/frontend.js b/assets/js/frontend.js
new file mode 100644
index 0000000..d6981ce
--- /dev/null
+++ b/assets/js/frontend.js
@@ -0,0 +1,11 @@
+document.addEventListener('DOMContentLoaded', function() {
+ const fileInput = document.getElementById('sts_ticket_attachment');
+ if (fileInput) {
+ fileInput.addEventListener('change', function() {
+ const fileNameSpan = document.getElementById('sts-file-name');
+ if (this.files.length > 0) {
+ fileNameSpan.textContent = this.files[0].name;
+ }
+ });
+ }
+});
\ No newline at end of file
diff --git a/includes/admin-ui.php b/includes/admin-ui.php
new file mode 100644
index 0000000..f34ce8c
--- /dev/null
+++ b/includes/admin-ui.php
@@ -0,0 +1,185 @@
+ID, 'ticket_status', array('fields' => 'names'));
+ $current_status = !empty($status) ? $status[0] : 'Aberto';
+
+ $type = wp_get_object_terms($post->ID, 'ticket_type', array('fields' => 'names'));
+ $current_type = !empty($type) ? $type[0] : __('Nenhum', 'simple-ticket-system');
+
+ $author_id = $post->post_author;
+ $author = get_userdata($author_id);
+ $attachments = get_attached_media('', $post->ID);
+ $ticket_id = get_post_meta($post->ID, '_sts_ticket_id', true);
+
+ echo '' . __('ID do Ticket:', 'simple-ticket-system') . ' ' . esc_html($ticket_id) . '
';
+
+ echo '' . __('Criado por:', 'simple-ticket-system') . ' ' . $author->display_name . '
';
+ echo '' . __('Email:', 'simple-ticket-system') . ' ' . $author->user_email . '
';
+ echo '' . __('Data:', 'simple-ticket-system') . ' ' . get_the_date('d/m/Y H:i', $post->ID) . '
';
+
+ echo '';
+ echo '';
+
+ echo '' . __('Tipo de Solicitação:', 'simple-ticket-system') . ' ' . esc_html($current_type) . '
';
+
+ if (!empty($attachments)) {
+ echo '' . __('Anexos:', 'simple-ticket-system') . '
';
+ echo '';
+ } else {
+ echo '' . __('Anexos:', 'simple-ticket-system') . ' ' . __('Nenhum anexo.', 'simple-ticket-system') . '
';
+ }
+
+}
+
+// Callback para respostas do ticket
+function sts_ticket_responses_callback($post) {
+ $responses = get_comments(array(
+ 'post_id' => $post->ID,
+ 'order' => 'ASC'
+ ));
+
+ echo '';
+
+ if ($responses) {
+ foreach ($responses as $response) {
+ echo '
';
+ echo '
' . $response->comment_author . ' ' . date('d/m/Y H:i', strtotime($response->comment_date)) . '
';
+ echo '
' . wpautop($response->comment_content) . '
';
+ echo '
';
+ }
+ } else {
+ echo '
' . __('Nenhuma resposta ainda.', 'simple-ticket-system') . '
';
+ }
+
+ echo '
';
+
+ // Formulário para nova resposta
+ echo '' . __('Adicionar Resposta', 'simple-ticket-system') . '
';
+ echo '';
+ echo '' . __('Digite sua resposta acima e atualize o ticket.', 'simple-ticket-system') . '
';
+}
+
+// Salvar dados do ticket
+function sts_save_ticket_data($post_id) {
+ if (!isset($_POST['sts_ticket_nonce']) || !wp_verify_nonce($_POST['sts_ticket_nonce'], 'sts_save_ticket_data')) {
+ return;
+ }
+
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
+ return;
+ }
+
+ if (!current_user_can('edit_post', $post_id)) {
+ return;
+ }
+
+ // Salvar status
+ if (isset($_POST['ticket_status'])) {
+ $status_name = sanitize_text_field($_POST['ticket_status']);
+ $term = get_term_by('name', $status_name, 'ticket_status');
+ if ($term && !is_wp_error($term)) {
+ wp_set_object_terms($post_id, $term->term_id, 'ticket_status');
+ } else {
+ wp_set_object_terms($post_id, $status_name, 'ticket_status');
+ }
+ }
+
+ // Salvar resposta se houver
+ if (!empty($_POST['sts_response_content'])) {
+ $user = wp_get_current_user();
+
+ $comment_data = array(
+ 'comment_post_ID' => $post_id,
+ 'comment_author' => $user->display_name,
+ 'comment_author_email' => $user->user_email,
+ 'comment_content' => wp_kses_post($_POST['sts_response_content']),
+ 'comment_type' => '',
+ 'comment_parent' => 0,
+ 'user_id' => $user->ID,
+ 'comment_approved' => 1,
+ );
+
+ $comment_id = wp_insert_comment($comment_data);
+
+ if ($comment_id) {
+ // Enviar notificação por email
+ sts_notify_ticket_response($post_id, $comment_id);
+ }
+ }
+}
+add_action('save_post_ticket', 'sts_save_ticket_data', 10, 1);
+
+// Personalizar colunas na lista de tickets
+function sts_custom_ticket_columns($columns) {
+ $new_columns = array(
+ 'cb' => $columns['cb'],
+ 'ticket_id' => __('ID', 'simple-ticket-system'),
+ 'title' => $columns['title'],
+ 'author' => __('Autor', 'simple-ticket-system'),
+ 'ticket_type' => __('Tipo', 'simple-ticket-system'),
+ 'status' => __('Status', 'simple-ticket-system'),
+ 'date' => $columns['date']
+ );
+
+ return $new_columns;
+}
+add_filter('manage_ticket_posts_columns', 'sts_custom_ticket_columns');
+
+function sts_custom_ticket_column_data($column, $post_id) {
+ switch ($column) {
+ case 'status':
+ $status = wp_get_object_terms($post_id, 'ticket_status', array('fields' => 'names'));
+ if (!empty($status)) {
+ $status_class = sanitize_title($status[0]);
+ echo '' . esc_html($status[0]) . '';
+ } else {
+ echo __('Nenhum status definido', 'simple-ticket-system');
+ }
+ break;
+ case 'ticket_id':
+ $ticket_id = get_post_meta($post_id, '_sts_ticket_id', true);
+ echo $ticket_id ? '' . esc_html($ticket_id) . '' : '';
+ break;
+ }
+}
+add_action('manage_ticket_posts_custom_column', 'sts_custom_ticket_column_data', 10, 2);
\ No newline at end of file
diff --git a/includes/assets.php b/includes/assets.php
new file mode 100644
index 0000000..b9e13a9
--- /dev/null
+++ b/includes/assets.php
@@ -0,0 +1,25 @@
+post_type) && $screen->post_type == 'ticket') {
+ wp_enqueue_style('sts-admin-style', STS_URL . 'assets/css/admin.css', array(), STS_VERSION);
+ }
+}
+add_action('admin_enqueue_scripts', 'sts_enqueue_admin_assets');
\ No newline at end of file
diff --git a/includes/frontend.php b/includes/frontend.php
new file mode 100644
index 0000000..c58505d
--- /dev/null
+++ b/includes/frontend.php
@@ -0,0 +1,99 @@
+' . __('Você precisa estar logado para abrir um ticket.', 'simple-ticket-system') . '
';
+ echo wp_login_form(array('echo' => false));
+ }
+
+ return ob_get_clean();
+}
+add_shortcode('ticket_form', 'sts_ticket_form_shortcode');
+
+// Shortcode para visualização de tickets do usuário
+function sts_my_tickets_shortcode() {
+ ob_start();
+
+ if (is_user_logged_in()) {
+ include STS_PATH . 'templates/view-ticket.php';
+ } else {
+ echo '' . __('Você precisa estar logado para visualizar seus tickets.', 'simple-ticket-system') . '
';
+ echo wp_login_form(array('echo' => false));
+ }
+
+ return ob_get_clean();
+}
+add_shortcode('my_tickets', 'sts_my_tickets_shortcode');
+
+// Processar envio do formulário de ticket
+function sts_process_ticket_form() {
+ if (isset($_POST['sts_submit_ticket']) && wp_verify_nonce($_POST['sts_ticket_form_nonce'], 'sts_ticket_form')) {
+ $current_user = wp_get_current_user();
+ $title = sanitize_text_field($_POST['sts_ticket_title']);
+ $content = wp_kses_post($_POST['sts_ticket_content']);
+ $type_id = isset($_POST['sts_ticket_type']) ? (int) $_POST['sts_ticket_type'] : 0;
+
+ $post_data = array(
+ 'post_title' => $title,
+ 'post_content' => $content,
+ 'post_status' => 'publish',
+ 'post_type' => 'ticket',
+ 'post_author' => $current_user->ID
+ );
+
+ $post_id = wp_insert_post($post_data);
+
+ if (!is_wp_error($post_id)) {
+ // Gerar e salvar o ID personalizado
+ $last_id = get_option('sts_last_ticket_id', 0);
+ $new_id = $last_id + 1;
+ $ticket_id_formatted = 'DTI-' . sprintf('%03d', $new_id);
+
+ update_post_meta($post_id, '_sts_ticket_id', $ticket_id_formatted);
+ update_option('sts_last_ticket_id', $new_id);
+
+ // Definir status padrão
+ wp_set_object_terms($post_id, 'Aberto', 'ticket_status');
+
+ // Definir tipo de solicitação
+ if ($type_id > 0) {
+ wp_set_object_terms($post_id, $type_id, 'ticket_type');
+ }
+
+ // Lidar com o anexo
+ if (!empty($_FILES['sts_ticket_attachment']['name'])) {
+ require_once(ABSPATH . 'wp-admin/includes/file.php');
+ require_once(ABSPATH . 'wp-admin/includes/media.php');
+ require_once(ABSPATH . 'wp-admin/includes/image.php');
+
+ $attachment_id = media_handle_upload('sts_ticket_attachment', $post_id);
+ }
+
+ // Enviar notificação
+ sts_notify_new_ticket($post_id);
+
+ // Redireciona para a página do shortcode, não para o permalink do novo post.
+ wp_redirect(add_query_arg('ticket_submitted', 'success', wp_get_referer()));
+ exit;
+ } else {
+ wp_redirect(add_query_arg('ticket_submitted', 'error', wp_get_referer()));
+ exit;
+ }
+ }
+}
+add_action('init', 'sts_process_ticket_form');
+
+// Carregar o template para a página de um único ticket
+function sts_single_ticket_template($single_template) {
+ global $post;
+
+ if ($post->post_type == 'ticket') {
+ $single_template = STS_PATH . 'templates/single-ticket.php';
+ }
+ return $single_template;
+}
+add_filter('single_template', 'sts_single_ticket_template');
\ No newline at end of file
diff --git a/includes/notifications.php b/includes/notifications.php
new file mode 100644
index 0000000..e2ee9d2
--- /dev/null
+++ b/includes/notifications.php
@@ -0,0 +1,51 @@
+post_author);
+ $admin_email = get_option('admin_email');
+
+ $subject = __('Novo Ticket Criado', 'simple-ticket-system') . ': ' . $post->post_title;
+
+ $message = __("Um novo ticket foi criado no sistema.\n\n", 'simple-ticket-system');
+ $message .= __('Título: ', 'simple-ticket-system') . $post->post_title . "\n";
+ $message .= __('Autor: ', 'simple-ticket-system') . $author->display_name . "\n";
+ $message .= __('Email: ', 'simple-ticket-system') . $author->user_email . "\n";
+ $message .= __('Conteúdo: ', 'simple-ticket-system') . "\n" . $post->post_content . "\n\n";
+ $message .= __('Para visualizar e responder ao ticket, acesse: ', 'simple-ticket-system') . admin_url('post.php?post=' . $post_id . '&action=edit') . "\n";
+
+ wp_mail($admin_email, $subject, $message);
+}
+
+// Notificar sobre resposta ao ticket
+function sts_notify_ticket_response($post_id, $comment_id) {
+ $post = get_post($post_id);
+ $comment = get_comment($comment_id);
+ $author = get_userdata($post->post_author);
+
+ // Notificar o autor do ticket
+ $subject = __('Nova Resposta no Seu Ticket', 'simple-ticket-system') . ': ' . $post->post_title;
+
+ $message = __("Seu ticket recebeu uma nova resposta.\n\n", 'simple-ticket-system');
+ $message .= __('Ticket: ', 'simple-ticket-system') . $post->post_title . "\n";
+ $message .= __('Resposta de: ', 'simple-ticket-system') . $comment->comment_author . "\n";
+ $message .= __('Resposta: ', 'simple-ticket-system') . "\n" . $comment->comment_content . "\n\n";
+ $message .= __('Para visualizar o ticket completo, acesse: ', 'simple-ticket-system') . get_permalink($post_id) . "\n";
+
+ wp_mail($author->user_email, $subject, $message);
+
+ // Se a resposta não foi do admin, notificar o admin
+ if (!user_can($comment->user_id, 'manage_options')) {
+ $admin_email = get_option('admin_email');
+ $subject = __('Nova Resposta no Ticket', 'simple-ticket-system') . ': ' . $post->post_title;
+
+ $message = __("Um ticket recebeu uma nova resposta.\n\n", 'simple-ticket-system');
+ $message .= __('Ticket: ', 'simple-ticket-system') . $post->post_title . "\n";
+ $message .= __('Autor do Ticket: ', 'simple-ticket-system') . $author->display_name . "\n";
+ $message .= __('Resposta de: ', 'simple-ticket-system') . $comment->comment_author . "\n";
+ $message .= __('Resposta: ', 'simple-ticket-system') . "\n" . $comment->comment_content . "\n\n";
+ $message .= __('Para visualizar e responder ao ticket, acesse: ', 'simple-ticket-system') . admin_url('post.php?post=' . $post_id . '&action=edit') . "\n";
+
+ wp_mail($admin_email, $subject, $message);
+ }
+}
\ No newline at end of file
diff --git a/includes/post-types.php b/includes/post-types.php
new file mode 100644
index 0000000..3a7ae14
--- /dev/null
+++ b/includes/post-types.php
@@ -0,0 +1,107 @@
+ __('Tickets', 'simple-ticket-system'),
+ 'singular_name' => __('Ticket', 'simple-ticket-system'),
+ 'menu_name' => __('Tickets', 'simple-ticket-system'),
+ 'name_admin_bar' => __('Ticket', 'simple-ticket-system'),
+ 'add_new' => __('Novo Ticket', 'simple-ticket-system'),
+ 'add_new_item' => __('Adicionar Novo Ticket', 'simple-ticket-system'),
+ 'new_item' => __('Novo Ticket', 'simple-ticket-system'),
+ 'edit_item' => __('Editar Ticket', 'simple-ticket-system'),
+ 'view_item' => __('Ver Ticket', 'simple-ticket-system'),
+ 'all_items' => __('Todos os Tickets', 'simple-ticket-system'),
+ 'search_items' => __('Procurar Tickets', 'simple-ticket-system'),
+ 'not_found' => __('Nenhum ticket encontrado.', 'simple-ticket-system'),
+ 'not_found_in_trash' => __('Nenhum ticket na lixeira.', 'simple-ticket-system')
+ );
+
+ $args = array(
+ 'labels' => $labels,
+ 'public' => true,
+ 'has_archive' => false,
+ 'publicly_queryable' => true,
+ 'show_ui' => true,
+ 'show_in_menu' => true,
+ 'query_var' => true,
+ 'rewrite' => array('slug' => 'ticket'),
+ 'capability_type' => 'post',
+ 'capabilities' => array(
+ 'create_posts' => false, // Remover capacidade de criar diretamente
+ ),
+ 'map_meta_cap' => true,
+ 'hierarchical' => false,
+ 'menu_position' => null,
+ 'supports' => array('title', 'editor', 'author'),
+ 'menu_icon' => 'dashicons-tickets'
+ );
+
+ register_post_type('ticket', $args);
+
+ // Registrar taxonomia para status
+ $status_labels = array(
+ 'name' => __('Status', 'simple-ticket-system'),
+ 'singular_name' => __('Status', 'simple-ticket-system'),
+ 'search_items' => __('Procurar Status', 'simple-ticket-system'),
+ 'all_items' => __('Todos os Status', 'simple-ticket-system'),
+ 'edit_item' => __('Editar Status', 'simple-ticket-system'),
+ 'update_item' => __('Atualizar Status', 'simple-ticket-system'),
+ 'add_new_item' => __('Adicionar Novo Status', 'simple-ticket-system'),
+ 'new_item_name' => __('Novo Nome de Status', 'simple-ticket-system'),
+ 'menu_name' => __('Status', 'simple-ticket-system'),
+ );
+
+ $status_args = array(
+ 'hierarchical' => true,
+ 'labels' => $status_labels,
+ 'show_ui' => true,
+ 'show_admin_column' => true,
+ 'query_var' => true,
+ 'rewrite' => array('slug' => 'ticket-status'),
+ );
+
+ register_taxonomy('ticket_status', 'ticket', $status_args);
+
+ // Registrar taxonomia para Tipo de Solicitação
+ $type_labels = array(
+ 'name' => __('Tipos de Solicitação', 'simple-ticket-system'),
+ 'singular_name' => __('Tipo de Solicitação', 'simple-ticket-system'),
+ 'search_items' => __('Procurar Tipos', 'simple-ticket-system'),
+ 'all_items' => __('Todos os Tipos', 'simple-ticket-system'),
+ 'edit_item' => __('Editar Tipo', 'simple-ticket-system'),
+ 'update_item' => __('Atualizar Tipo', 'simple-ticket-system'),
+ 'add_new_item' => __('Adicionar Novo Tipo', 'simple-ticket-system'),
+ 'new_item_name' => __('Novo Nome de Tipo', 'simple-ticket-system'),
+ 'menu_name' => __('Tipos de Solicitação', 'simple-ticket-system'),
+ );
+
+ $type_args = array(
+ 'hierarchical' => true,
+ 'labels' => $type_labels,
+ 'show_ui' => true,
+ 'show_admin_column' => true,
+ 'query_var' => true,
+ 'rewrite' => array('slug' => 'ticket-type'),
+ );
+
+ register_taxonomy('ticket_type', 'ticket', $type_args);
+
+ // Adicionar status padrão
+ $default_statuses = array('Aberto', 'Em Andamento', 'Resolvido');
+
+ foreach ($default_statuses as $status) {
+ if (!term_exists($status, 'ticket_status')) {
+ wp_insert_term($status, 'ticket_status');
+ }
+ }
+
+ // Adicionar tipos padrão
+ $default_types = array('Dúvida', 'Problema Técnico', 'Sugestão');
+ foreach ($default_types as $type) {
+ if (!term_exists($type, 'ticket_type')) {
+ wp_insert_term($type, 'ticket_type');
+ }
+ }
+}
+add_action('init', 'sts_register_ticket_post_type');
\ No newline at end of file
diff --git a/referencia.md b/referencia.md
new file mode 100644
index 0000000..9a2c4cc
--- /dev/null
+++ b/referencia.md
@@ -0,0 +1,189 @@
+### 1. **Variáveis CSS (Cores, Sombra, etc.)**
+
+As variáveis são essenciais para a consistência do design e permitem que você altere rapidamente o estilo de toda a aplicação ao modificar o valor das variáveis. Algumas variáveis importantes que podem ser reutilizadas são:
+
+```css
+:root {
+ --azul-principal: #006494;
+ --azul-escuro: #003554;
+ --azul-claro: #0582ca;
+ --azul-brilhante: #00a6fb;
+ --amarelo: #ffb700;
+ --cinza-claro: #f8f9fa;
+ --cinza: #e9ecef;
+ --preto: #212529;
+ --branco: #ffffff;
+ --sombra: 0 4px 6px rgba(0, 0, 0, 0.1);
+ --borda-radius: 8px;
+ --transicao: all 0.3s ease;
+}
+```
+
+Essas cores e efeitos podem ser reutilizados para manter o design consistente.
+
+### 2. **Tipografia**
+
+Fontes e tamanhos de texto podem ser extraídos e usados em outros projetos. Por exemplo:
+
+```css
+body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+}
+```
+
+Você também tem diferentes tamanhos de fonte definidos nas regras de texto, como para o título de posts:
+
+```css
+.post-title {
+ font-size: 2.2rem;
+ color: var(--azul-escuro);
+}
+```
+
+### 3. **Layout Responsivo**
+
+Esse código já tem um design responsivo com várias quebras de mídia. Você pode utilizar os padrões de grid e as regras de `@media` para garantir que seu projeto seja adaptável a diferentes tamanhos de tela. Um exemplo é o layout de grade no `.grid-layout`:
+
+```css
+.grid-layout {
+ display: grid;
+ grid-template-columns: 250px 1fr;
+ gap: 30px;
+}
+```
+
+E a adaptação responsiva para telas menores:
+
+```css
+@media (max-width: 900px) {
+ .grid-layout {
+ display: block;
+ }
+}
+```
+
+### 4. **Estilos para Botões e Links**
+
+Estilos para botões (`btn-primario`) e links (`.nav-principal a`, `.ver-tudo`) estão bem definidos e podem ser reutilizados:
+
+```css
+.btn-primario {
+ background: var(--branco);
+ color: var(--azul-principal);
+ padding: 12px 25px;
+ border-radius: var(--borda-radius);
+ font-weight: 600;
+ text-decoration: none;
+ transition: var(--transicao);
+}
+
+.nav-principal a:hover:after {
+ width: 100%;
+}
+```
+
+Esses estilos garantem que o comportamento dos links e botões seja consistente, com efeitos de transição.
+
+### 5. **Efeitos de Sombra e Transição**
+
+As sombras e transições são definidos com variáveis e podem ser facilmente reutilizados:
+
+```css
+.sombra {
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+.sombra-hover {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+}
+```
+
+### 6. **Card Layouts (ex: Atalhos, Notícias)**
+
+O estilo de cards, como no caso dos atalhos e das notícias, é bastante modular e pode ser reutilizado em diversos projetos. Por exemplo:
+
+```css
+.atalho-card {
+ background: var(--branco);
+ border-radius: var(--borda-radius);
+ padding: 25px 15px;
+ text-align: center;
+ box-shadow: var(--sombra);
+ cursor: pointer;
+}
+
+.atalho-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
+}
+```
+
+### 7. **Estilos de Ícones**
+
+Estilos de ícones também são importantes, especialmente para se integrar com o Font Awesome:
+
+```css
+.atalho-card i {
+ font-size: 2rem;
+ color: var(--azul-principal);
+ margin-bottom: 15px;
+}
+```
+
+### 8. **Componentes de Cabeçalho e Rodapé**
+
+Esses componentes têm um estilo bem definido, com a possibilidade de facilmente aplicar em outros projetos:
+
+```css
+.header-moderno {
+ background: var(--branco);
+ box-shadow: var(--sombra);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+.footer-moderno {
+ background: var(--azul-escuro);
+ color: var(--branco);
+ padding: 50px 0 20px;
+}
+```
+
+### 9. **Animações**
+
+As animações, como o efeito `fadeIn`, são reutilizáveis e podem ser aplicadas a outros elementos:
+
+```css
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.noticia-card, .stats-card, .atalho-card {
+ animation: fadeIn 0.5s ease-out forwards;
+}
+```
+
+---
+
+### Resumo:
+
+Você pode reutilizar e adaptar os seguintes elementos para outros projetos:
+
+1. **Cores e Variáveis CSS** (cores principais, bordas, transições)
+2. **Fontes e Tipografia** (fontes, tamanhos de texto)
+3. **Layouts Responsivos** (uso de `@media` e grids)
+4. **Botões e Links** (estilos interativos e transições)
+5. **Sombreamento e Transições** (efeitos de sombra, hover)
+6. **Card Layouts** (para atalhos, notícias, etc.)
+7. **Ícones** (estilo e cores dos ícones)
+8. **Componentes de Cabeçalho e Rodapé** (design de cabeçalhos fixos e rodapés)
+9. **Animações** (efeitos de entrada, transições animadas)
+
+
diff --git a/simple-ticket-system.php b/simple-ticket-system.php
new file mode 100644
index 0000000..20fdf3c
--- /dev/null
+++ b/simple-ticket-system.php
@@ -0,0 +1,49 @@
+' . __('Ticket enviado com sucesso!', 'simple-ticket-system') . '';
+ } else {
+ echo '' . __('Ocorreu um erro ao enviar o ticket.', 'simple-ticket-system') . '
';
+ }
+}
+?>
+
+
\ No newline at end of file
diff --git a/templates/single-ticket.php b/templates/single-ticket.php
new file mode 100644
index 0000000..14536e3
--- /dev/null
+++ b/templates/single-ticket.php
@@ -0,0 +1,66 @@
+
+
+
+
+
+ 'names'));
+ $types = wp_get_object_terms(get_the_ID(), 'ticket_type', array('fields' => 'names'));
+ $attachments = get_attached_media('', get_the_ID());
+ $ticket_id = get_post_meta(get_the_ID(), '_sts_ticket_id', true);
+
+ $current_status = !empty($status) ? esc_html($status[0]) : __('Sem status', 'simple-ticket-system');
+ $status_class = !empty($status) ? sanitize_title($status[0]) : 'no-status';
+ $current_type = !empty($types) ? esc_html($types[0]) : __('Não definido', 'simple-ticket-system');
+ ?>
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/view-ticket.php b/templates/view-ticket.php
new file mode 100644
index 0000000..30aac30
--- /dev/null
+++ b/templates/view-ticket.php
@@ -0,0 +1,47 @@
+ 'ticket',
+ 'author' => $current_user->ID,
+ 'posts_per_page' => -1,
+ 'orderby' => 'date',
+ 'order' => 'DESC'
+);
+
+$tickets = get_posts($args);
+
+if ($tickets) {
+ echo '' . __('Meus Tickets', 'simple-ticket-system') . '
';
+ echo '';
+} else {
+ echo '' . __('Você não possui tickets.', 'simple-ticket-system') . '
';
+}
\ No newline at end of file