From 65e33092661611898230522268b668ab71546e59 Mon Sep 17 00:00:00 2001 From: Marco Antonio Vivas Date: Fri, 8 Aug 2025 22:19:01 -0300 Subject: [PATCH] Initial commit --- .gitattributes | 2 + README.md | 59 +++++++++++++ includes/api.php | 98 +++++++++++++++++++++ includes/assets/script.js | 56 ++++++++++++ includes/config.php | 6 ++ includes/templates/form.php | 128 ++++++++++++++++++++++++++++ includes/templates/tasks.php | 64 ++++++++++++++ sistema-arte.php | 160 +++++++++++++++++++++++++++++++++++ 8 files changed, 573 insertions(+) create mode 100644 .gitattributes create mode 100644 README.md create mode 100644 includes/api.php create mode 100644 includes/assets/script.js create mode 100644 includes/config.php create mode 100644 includes/templates/form.php create mode 100644 includes/templates/tasks.php create mode 100644 sistema-arte.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/README.md b/README.md new file mode 100644 index 0000000..c929d31 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# Sistema Arte + +Plugin WordPress para gerenciamento de tarefas integrado à API Vikunja. + +## Descrição +O **Sistema Arte** é um plugin para WordPress que permite a criação e acompanhamento de demandas de artes gráficas, integrando-se diretamente à API Vikunja para gerenciamento de tarefas. O plugin oferece um formulário customizado para solicitação de demandas e exibe uma lista das tarefas pendentes do projeto configurado. + +## Funcionalidades +- Formulário customizado para solicitação de artes, com campos obrigatórios: + - Título da arte + - Nome completo do solicitante + - Secretaria + - Telefone/WhatsApp + - Detalhes da solicitação + - Data de entrega (padrão: hoje + 7 dias às 17h) + - Prioridade +- Integração direta com a API Vikunja para criação e listagem de tarefas +- Exibição das demandas pendentes em tabela +- Validação de campos obrigatórios e feedback de sucesso/erro +- Interface moderna utilizando Tailwind CSS + +## Instalação +1. Faça upload da pasta `sistema-arte` para o diretório `wp-content/plugins/` do seu WordPress. +2. Ative o plugin no painel do WordPress. +3. Configure as variáveis de integração com a API Vikunja em `includes/config.php`: + - `$apiBase`: URL base da API + - `$token`: Token de acesso + - `$projectId`: ID do projeto no Vikunja + +## Uso +Adicione o shortcode `[Sistema-Arte]` em qualquer página ou post para exibir o formulário de solicitação e a lista de demandas. + +## Estrutura dos Arquivos +- `sistema-arte.php`: Arquivo principal do plugin +- `includes/config.php`: Configurações da API +- `includes/api.php`: Funções de integração com a API Vikunja +- `includes/templates/form.php`: Template do formulário de solicitação +- `includes/templates/tasks.php`: Template da lista de tarefas +- `includes/assets/script.js`: Scripts JS para manipulação do formulário + +## Dependências +- [Tailwind CSS](https://tailwindcss.com/) (via CDN) +- jQuery (WordPress padrão) + +## Segurança +- Utiliza `wp_nonce_field` para proteção contra CSRF +- Sanitização e validação de todos os campos do formulário +- Escapando de todas as saídas para evitar XSS + +## Observações +- O plugin depende de um projeto e token válidos na API Vikunja. +- O usuário do token precisa de permissão de escrita no projeto configurado. +- Em caso de erro de permissão, dados inválidos ou projeto não encontrado, mensagens detalhadas são exibidas ao usuário. + +## Autor +Marco Antonio Vivas + +--- +Plugin desenvolvido para integração com o sistema Vikunja, facilitando a gestão de demandas de artes no WordPress. \ No newline at end of file diff --git a/includes/api.php b/includes/api.php new file mode 100644 index 0000000..e9e809d --- /dev/null +++ b/includes/api.php @@ -0,0 +1,98 @@ + [ + 'Authorization' => 'Bearer ' . sanitize_text_field($token), + 'Content-Type' => 'application/json', + ], + 'method' => $method, + 'timeout' => 30, + ]; + + if ($data && in_array($method, ['PUT', 'POST'])) { + $args['body'] = wp_json_encode($data); + if ($args['body'] === false) { + error_log('Sistema Arte: Falha ao codificar JSON para a requisição API'); + return [ + 'success' => false, + 'error' => 'Erro interno: Falha ao codificar dados da requisição.', + 'http_code' => 0 + ]; + } + } + + $response = wp_remote_request($url, $args); + + if (is_wp_error($response)) { + $error_message = $response->get_error_message(); + error_log('Sistema Arte: Erro na requisição API: ' . $error_message); + return [ + 'success' => false, + 'error' => 'Erro na requisição: ' . esc_html($error_message), + 'http_code' => 0 + ]; + } + + $http_code = wp_remote_retrieve_response_code($response); + $body = wp_remote_retrieve_body($response); + + $result = json_decode($body, true); + if (json_last_error() !== JSON_ERROR_NONE) { + error_log('Sistema Arte: Erro ao decodificar JSON: ' . json_last_error_msg()); + return [ + 'success' => false, + 'error' => 'Erro ao processar resposta da API.', + 'http_code' => $http_code, + 'raw_response' => $body + ]; + } + + if ($http_code >= 200 && $http_code < 300) { + return ['success' => true, 'data' => $result, 'http_code' => $http_code]; + } else { + $error = isset($result['message']) ? $result['message'] : $body; + error_log('Sistema Arte: Erro na API (HTTP ' . $http_code . '): ' . $error); + return [ + 'success' => false, + 'error' => esc_html($error), + 'http_code' => $http_code, + 'raw_response' => $body + ]; + } +} + +// Função para listar tarefas do projeto +function listTasks($apiBase, $token, $projectId) { + $url = rtrim($apiBase, '/') . '/projects/' . absint($projectId) . '/tasks'; + $response = makeApiRequest($url, $token); + + if ($response['success']) { + return $response['data']; + } else { + error_log('Sistema Arte: Erro ao listar tarefas: ' . $response['error']); + return false; + } +} + +// Função para adicionar tarefa +function addTask($apiBase, $token, $task, $projectId) { + // Validação básica + if (empty($task['title']) || empty($task['project_id'])) { + error_log('Sistema Arte: Título ou project_id ausente na tarefa.'); + return ['success' => false, 'error' => 'Título e project_id são obrigatórios.']; + } + if ($task['project_id'] !== $projectId) { + error_log('Sistema Arte: project_id mismatch: ' . $task['project_id'] . ' != ' . $projectId); + return [ + 'success' => false, + 'error' => "project_id no corpo ({$task['project_id']}) deve corresponder ao ID do projeto no caminho ({$projectId})." + ]; + } + + $url = rtrim($apiBase, '/') . '/projects/' . absint($projectId) . '/tasks'; + $response = makeApiRequest($url, $token, 'PUT', $task); + + return $response; +} +?> \ No newline at end of file diff --git a/includes/assets/script.js b/includes/assets/script.js new file mode 100644 index 0000000..c90762c --- /dev/null +++ b/includes/assets/script.js @@ -0,0 +1,56 @@ +jQuery(document).ready(function($) { + var $form = $('#sistema-arte-form'); + if ($form.length) { + // Definir data padrão (hoje + 7 dias) no carregamento + var today = new Date(); + today.setDate(today.getDate() + 7); + today.setHours(17, 0, 0, 0); // Define para 17:00 + + // Formata para o formato esperado pelo input datetime-local (YYYY-MM-DDTHH:MM) + var formattedDate = today.toISOString().slice(0, 16); + + // Define o valor do campo se ele estiver vazio + if (!$('#due_date').val()) { + $('#due_date').val(formattedDate); + } + $form.on('submit', function(e) { + // Coleta todos os dados dos novos campos + var fullName = $('#full_name').val() || ''; + var department = $('#department').val() || ''; + var phone = $('#phone').val() || ''; + var additionalInfo = $('#additional_info').val() || ''; + + // Formata a descrição com HTML + var formattedDescription = + "
" + + "

SOLICITAÇÃO

" + + "" + + "

DETALHES

" + + "

" + additionalInfo.replace(//g, '>') + "

" + + "
"; + + // Atribui ao campo de descrição que será enviado ao backend + $('#description').val(formattedDescription); + + // Define valores padrão para os campos originais se necessário + if (!$('#due_date').val()) { + var today = new Date(); + today.setDate(today.getDate() + 7); + today.setHours(17, 0); + $('#due_date').val(today.toISOString().slice(0, 16)); + } + + if (!$('#priority').val()) { + $('#priority').val(3); // Prioridade média por padrão + } + + return true; + }); + } else { + console.warn('Sistema Arte: Formulário #sistema-arte-form não encontrado.'); + } +}); \ No newline at end of file diff --git a/includes/config.php b/includes/config.php new file mode 100644 index 0000000..b266e7e --- /dev/null +++ b/includes/config.php @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/includes/templates/form.php b/includes/templates/form.php new file mode 100644 index 0000000..489e176 --- /dev/null +++ b/includes/templates/form.php @@ -0,0 +1,128 @@ +add(new DateInterval('P7D')); + $date->setTime(17, 0); // Define para 17:00 + $default_due_date = $date->format('Y-m-d\TH:i'); + } catch (Exception $e) { + error_log('Erro ao calcular data padrão: ' . $e->getMessage()); + } + + // Verificar se já existe um valor POST para manter na reexibição do formulário + $due_date_value = isset($_POST['due_date']) ? esc_attr(wp_unslash($_POST['due_date'])) : $default_due_date; + ?> + +
+

Adicionar uma nova demanda

+ + +
+ +
+ + +
+ + + +
+ + +
+ + +
+ +
+ + +
+ + +
+ + +
+
+ + +
+ + +
+ + + + + +
+ + +
+ + +
+ +
+ + +
+ + +
+ + +
+
+ + + +
+
+ \ No newline at end of file diff --git a/includes/templates/tasks.php b/includes/templates/tasks.php new file mode 100644 index 0000000..8197970 --- /dev/null +++ b/includes/templates/tasks.php @@ -0,0 +1,64 @@ + + +
+

Demandas Pendentes

+ +
+ + + + + + + + + + + + + + + + + + + +
IDTítuloVencimentoPrioridade
+ format('d/m/Y H:i')); + } catch (Exception $e) { + echo '-'; + } + } else { + echo '-'; + } + ?> + + + + + + + - + +
+
+ +
+ + + +

Nenhuma tarefa encontrada ou erro ao carregar.

+
+ +
+ \ No newline at end of file diff --git a/sistema-arte.php b/sistema-arte.php new file mode 100644 index 0000000..d888cdb --- /dev/null +++ b/sistema-arte.php @@ -0,0 +1,160 @@ + admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('sistema_arte_nonce') + ]); +} +add_action('wp_enqueue_scripts', 'sistema_arte_enqueue_assets'); + +// Register shortcode +function sistema_arte_shortcode($atts) { + // Start output buffering + ob_start(); + + // Load config variables + global $apiBase, $token, $projectId; + + // Process form submission + $message = ''; + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['sistema_arte_nonce']) && wp_verify_nonce($_POST['sistema_arte_nonce'], 'sistema_arte_nonce')) { + $title = isset($_POST['title']) ? sanitize_text_field(trim($_POST['title'])) : ''; + $full_name = isset($_POST['full_name']) ? sanitize_text_field(trim($_POST['full_name'])) : ''; + $department = isset($_POST['department']) ? sanitize_text_field(trim($_POST['department'])) : ''; + $phone = isset($_POST['phone']) ? sanitize_text_field(trim($_POST['phone'])) : ''; + $additional_info = isset($_POST['additional_info']) ? sanitize_textarea_field(trim($_POST['additional_info'])) : ''; + $due_date = isset($_POST['due_date']) ? sanitize_text_field(trim($_POST['due_date'])) : ''; + $priority = isset($_POST['priority']) ? absint($_POST['priority']) : null; + + // Validações + $errors = []; + if (empty($title)) { + $errors[] = 'O título é obrigatório.'; + } + if (empty($full_name)) { + $errors[] = 'O nome completo é obrigatório.'; + } + if (empty($department)) { + $errors[] = 'A secretaria é obrigatória.'; + } + if (empty($phone)) { + $errors[] = 'O telefone/WhatsApp é obrigatório.'; + } + if (empty($additional_info)) { + $errors[] = 'Os detalhes da solicitação são obrigatórios.'; + } + if ($due_date) { + try { + $date = new DateTime($due_date); + $due_date = $date->format('c'); // Formato ISO 8601 + } catch (Exception $e) { + $errors[] = 'Data de vencimento inválida.'; + } + } + if ($priority !== null && ($priority < 1 || $priority > 5)) { + $errors[] = 'Prioridade deve ser entre 1 e 5.'; + } + + if (empty($errors)) { + // Format description + $description = "
" . + "

SOLICITAÇÃO

" . + "" . + "

DETALHES

" . + "

" . esc_html($additional_info) . "

" . + "
"; + + $task = [ + 'title' => $title, + 'project_id' => $projectId, + 'description' => $description, + ]; + if ($due_date) { + $task['due_date'] = $due_date; + } + if ($priority !== null) { + $task['priority'] = $priority; + } + + $result = addTask($apiBase, $token, $task, $projectId); + if ($result['success']) { + $message = "Tarefa criada com sucesso! ID: {$result['data']['id']}"; + } else { + $message = "Erro ao criar tarefa: {$result['error']} (HTTP {$result['http_code']})"; + if ($result['http_code'] == 403) { + $message .= "
Permissão negada. Verifique se o usuário do token tem acesso de escrita no projeto."; + } elseif ($result['http_code'] == 400) { + $message .= "
Dados inválidos. Verifique o formato dos campos da tarefa."; + } elseif ($result['http_code'] == 404) { + $message .= "
Projeto não encontrado. Confirme se o project_id é válido."; + } + } + } else { + $message = 'Erros no formulário:'; + } + } + + // List tasks + $tasks = listTasks($apiBase, $token, $projectId); + + // Load templates + ?> +

+

Sistema de gerenciamento de artes

+
+ +
+
+ \ No newline at end of file