From 2dc8967d1eaf10d4c1997b3e5dd91557ac81ee4a Mon Sep 17 00:00:00 2001 From: Pedro Mascimo Date: Sat, 21 Mar 2026 14:14:08 -0300 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20adiciona=20plugin=20de=20favoritos?= =?UTF-8?q?=20com=20REST=20API=20e=20persist=C3=AAncia=20customizada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementa funcionalidade de favoritar posts com persistência em tabela customizada --- README.md | 48 ++++++++++++------- wp-favorites-plugin/assets/js/favorites.js | 13 +++++ wp-favorites-plugin/includes/class-api.php | 36 ++++++++++++++ .../includes/class-database.php | 36 ++++++++++++++ .../includes/class-favorites.php | 43 +++++++++++++++++ wp-favorites-plugin/includes/class-plugin.php | 17 +++++++ wp-favorites-plugin/uninstall.php | 10 ++++ wp-favorites-plugin/wp-favorites-plugin.php | 23 +++++++++ 8 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 wp-favorites-plugin/assets/js/favorites.js create mode 100644 wp-favorites-plugin/includes/class-api.php create mode 100644 wp-favorites-plugin/includes/class-database.php create mode 100644 wp-favorites-plugin/includes/class-favorites.php create mode 100644 wp-favorites-plugin/includes/class-plugin.php create mode 100644 wp-favorites-plugin/uninstall.php create mode 100644 wp-favorites-plugin/wp-favorites-plugin.php diff --git a/README.md b/README.md index a4f6c256..5df151d6 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,43 @@ -# WordPress Back-end Challenge +# WP Favorites Plugin -Desafio para os futuros programadores back-end em WordPress da Apiki. +Plugin para favoritar posts via REST API. -## Introdução +## Funcionalidades -Desenvolva um Plugin em WordPress que implemente a funcionalidade de favoritar posts para usuários logados usando a [WP REST API](https://developer.wordpress.org/rest-api/). +- Favoritar / desfavoritar posts +- Listar favoritos +- Persistência em tabela customizada +- API REST -**Especifícações**: +## Endpoints -* Possibilidade de favoritar e desfavoritar um post; -* Persistir os dados em uma [tabela a parte](https://codex.wordpress.org/Creating_Tables_with_Plugins); +### Toggle favorito +POST /wp-json/wpfav/v1/toggle -## Instruções +Body: +{ + "post_id": 123 +} -1. Efetue o fork deste repositório e crie um branch com o seu nome e sobrenome. (exemplo: fulano-dasilva) -2. Após finalizar o desafio, crie um Pull Request. -3. Aguarde algum contribuidor realizar o code review. +### Listar favoritos +GET /wp-json/wpfav/v1/list -## Pré-requisitos +## Requisitos -* PHP >= 5.6 -* Orientado a objetos +- PHP >= 7 +- WordPress >= 5.x -## Dúvidas +## Instalação -Em caso de dúvidas, crie uma issue. +1. Copie para wp-content/plugins +2. Ative o plugin +3. A tabela será criada automaticamente + +## Estrutura do banco + +Tabela: wp_favorites + +- id +- user_id +- post_id +- created_at \ No newline at end of file diff --git a/wp-favorites-plugin/assets/js/favorites.js b/wp-favorites-plugin/assets/js/favorites.js new file mode 100644 index 00000000..8852039d --- /dev/null +++ b/wp-favorites-plugin/assets/js/favorites.js @@ -0,0 +1,13 @@ +async function toggleFavorite(postId) { + const response = await fetch('/wp-json/wpfav/v1/toggle', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': wpApiSettings.nonce + }, + body: JSON.stringify({ post_id: postId }) + }); + + const data = await response.json(); + console.log(data); +} \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-api.php b/wp-favorites-plugin/includes/class-api.php new file mode 100644 index 00000000..94213537 --- /dev/null +++ b/wp-favorites-plugin/includes/class-api.php @@ -0,0 +1,36 @@ + 'POST', + 'callback' => [self::class, 'toggle'], + 'permission_callback' => function () { + return is_user_logged_in(); + } + ]); + + register_rest_route('wpfav/v1', '/list', [ + 'methods' => 'GET', + 'callback' => [self::class, 'get_favorites'], + 'permission_callback' => function () { + return is_user_logged_in(); + } + ]); + } + + public static function toggle($request) { + $user_id = get_current_user_id(); + $post_id = $request->get_param('post_id'); + + return WPFAV_Favorites::toggle_favorite($user_id, $post_id); + } + + public static function get_favorites() { + $user_id = get_current_user_id(); + + return WPFAV_Favorites::get_user_favorites($user_id); + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-database.php b/wp-favorites-plugin/includes/class-database.php new file mode 100644 index 00000000..b6461886 --- /dev/null +++ b/wp-favorites-plugin/includes/class-database.php @@ -0,0 +1,36 @@ +prefix . 'favorites'; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table_name ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + post_id BIGINT UNSIGNED NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY user_post (user_id, post_id) + ) $charset_collate;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta($sql); + + add_option('wpfav_db_version', WPFAV_VERSION); + } + + public static function maybe_update() { + $installed_version = get_option('wpfav_db_version'); + + if ($installed_version !== WPFAV_VERSION) { + + self::create_table(); + + update_option('wpfav_db_version', WPFAV_VERSION); + } + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-favorites.php b/wp-favorites-plugin/includes/class-favorites.php new file mode 100644 index 00000000..8e53f7bb --- /dev/null +++ b/wp-favorites-plugin/includes/class-favorites.php @@ -0,0 +1,43 @@ +prefix . 'favorites'; + + $exists = $wpdb->get_var($wpdb->prepare( + "SELECT id FROM $table WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + )); + + if ($exists) { + $wpdb->delete($table, [ + 'user_id' => $user_id, + 'post_id' => $post_id + ]); + + return ['status' => 'removed']; + } + + $wpdb->insert($table, [ + 'user_id' => $user_id, + 'post_id' => $post_id + ]); + + return ['status' => 'added']; + } + + public static function get_user_favorites($user_id) { + global $wpdb; + + $table = $wpdb->prefix . 'favorites'; + + return $wpdb->get_results($wpdb->prepare( + "SELECT post_id FROM $table WHERE user_id = %d", + $user_id + )); + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-plugin.php b/wp-favorites-plugin/includes/class-plugin.php new file mode 100644 index 00000000..9762ce84 --- /dev/null +++ b/wp-favorites-plugin/includes/class-plugin.php @@ -0,0 +1,17 @@ +load_dependencies(); + + add_action('plugins_loaded', ['WPFAV_Database', 'maybe_update']); + + add_action('rest_api_init', ['WPFAV_API', 'register_routes']); + } + + private function load_dependencies() { + require_once WPFAV_PLUGIN_PATH . 'includes/class-api.php'; + require_once WPFAV_PLUGIN_PATH . 'includes/class-favorites.php'; + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/uninstall.php b/wp-favorites-plugin/uninstall.php new file mode 100644 index 00000000..efd0b30c --- /dev/null +++ b/wp-favorites-plugin/uninstall.php @@ -0,0 +1,10 @@ +prefix . 'favorites'; +$wpdb->query("DROP TABLE IF EXISTS $table"); + +delete_option('wpfav_db_version'); \ No newline at end of file diff --git a/wp-favorites-plugin/wp-favorites-plugin.php b/wp-favorites-plugin/wp-favorites-plugin.php new file mode 100644 index 00000000..e2e62228 --- /dev/null +++ b/wp-favorites-plugin/wp-favorites-plugin.php @@ -0,0 +1,23 @@ +run(); +} + +wpfav_init(); \ No newline at end of file From afecd6298c539721d26b33a124b04174c4a3d5ba Mon Sep 17 00:00:00 2001 From: Pedro Mascimo Date: Sun, 22 Mar 2026 11:45:25 -0300 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20adiciona=20valida=C3=A7=C3=B5es,=20s?= =?UTF-8?q?anitiza=C3=A7=C3=A3o=20e=20melhorias=20na=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Melhora validação de parâmetros, sanitização de dados e retorno da API --- README.md | 162 +++++++++++++++--- wp-favorites-plugin/assets/js/favorites.js | 122 +++++++++++-- wp-favorites-plugin/includes/class-api.php | 104 ++++++++--- .../includes/class-database.php | 66 ++++--- .../includes/class-favorites.php | 155 ++++++++++++++--- wp-favorites-plugin/includes/class-plugin.php | 63 ++++++- wp-favorites-plugin/uninstall.php | 13 +- wp-favorites-plugin/wp-favorites-plugin.php | 24 ++- 8 files changed, 590 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index 5df151d6..4d015d3d 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,163 @@ # WP Favorites Plugin -Plugin para favoritar posts via REST API. +Plugin WordPress para favoritar posts via REST API, com persistência em tabela customizada. ## Funcionalidades -- Favoritar / desfavoritar posts -- Listar favoritos -- Persistência em tabela customizada -- API REST +- Favoritar e desfavoritar posts (toggle) +- Listar favoritos do usuário logado +- Persistência em tabela própria (`wp_favorites`) +- Criação automática da tabela ao ativar o plugin +- Sistema de versionamento da tabela para futuras atualizações +- Script front-end com feedback visual e tratamento de erros -## Endpoints +## Requisitos + +- PHP >= 7.4 +- WordPress >= 5.0 + +## Instalação + +1. Copie a pasta `wp-favorites-plugin` para `wp-content/plugins/` +2. Ative o plugin no painel do WordPress +3. A tabela `wp_favorites` será criada automaticamente + +## Estrutura do projeto + +``` +wp-favorites-plugin/ +├── wp-favorites-plugin.php # Bootstrap: constantes, hooks de ativação +├── uninstall.php # Remove tabela e opções ao desinstalar +├── includes/ +│ ├── class-database.php # Criação e atualização da tabela +│ ├── class-plugin.php # Registro de hooks e enqueue de scripts +│ ├── class-api.php # Endpoints REST (registro e callbacks) +│ └── class-favorites.php # Regras de negócio (toggle, list) +└── assets/ + └── js/ + └── favorites.js # Front-end: requisições e feedback visual +``` + +## Endpoints REST + +Todos os endpoints exigem autenticação (usuário logado + nonce). ### Toggle favorito + +``` POST /wp-json/wpfav/v1/toggle +``` + +**Body (JSON):** +```json +{ "post_id": 123 } +``` + +**Resposta — adicionado (201):** +```json +{ + "status": "added", + "post_id": 123, + "message": "Post adicionado aos favoritos." +} +``` + +**Resposta — removido (200):** +```json +{ + "status": "removed", + "post_id": 123, + "message": "Post removido dos favoritos." +} +``` -Body: +**Resposta — post não encontrado (404):** +```json { - "post_id": 123 + "code": "invalid_post", + "message": "O post informado não existe ou não é válido.", + "data": { "status": 404 } } +``` + +**Resposta — não autenticado (401):** +```json +{ + "code": "rest_forbidden", + "message": "Você precisa estar logado para usar esta funcionalidade.", + "data": { "status": 401 } +} +``` ### Listar favoritos + +``` GET /wp-json/wpfav/v1/list +``` -## Requisitos +**Resposta (200):** +```json +{ + "total": 2, + "favorites": [ + { "post_id": "123", "created_at": "2026-03-21 10:00:00" }, + { "post_id": "45", "created_at": "2026-03-20 08:30:00" } + ] +} +``` -- PHP >= 7 -- WordPress >= 5.x +## Uso no tema (botão de favoritar) -## Instalação +O plugin registra automaticamente o script `favorites.js` para usuários logados. +Adicione o atributo `data-wpfav-post-id` a qualquer botão no seu tema: + +```php + + + +``` + +O script escuta cliques via delegação de evento no `document`, portanto funciona +com conteúdo carregado dinamicamente (infinite scroll, AJAX, etc.). + +### Exemplo de estilo CSS básico + +```css +.wpfav-btn { cursor: pointer; } +.wpfav-btn.wpfav--active { color: red; } +.wpfav-btn[disabled] { opacity: 0.5; } +``` + +### Ouvindo o evento customizado + +```js +document.addEventListener( 'wpfav:toggled', function ( event ) { + console.log( 'Post', event.detail.postId, '→', event.detail.status ); +} ); +``` + +## Estrutura do banco de dados + +Tabela: `wp_favorites` -1. Copie para wp-content/plugins -2. Ative o plugin -3. A tabela será criada automaticamente +| Coluna | Tipo | Descrição | +|--------------|---------------------|-------------------------------| +| `id` | BIGINT UNSIGNED PK | Identificador único | +| `user_id` | BIGINT UNSIGNED | ID do usuário (FK wp_users) | +| `post_id` | BIGINT UNSIGNED | ID do post (FK wp_posts) | +| `created_at` | DATETIME | Data/hora de criação | -## Estrutura do banco +Índices: `PRIMARY KEY (id)`, `UNIQUE KEY user_post (user_id, post_id)`, `KEY idx_user_id (user_id)` -Tabela: wp_favorites +## Versionamento da tabela -- id -- user_id -- post_id -- created_at \ No newline at end of file +A versão da tabela é armazenada na opção `wpfav_db_version`. +Ao atualizar o plugin, incremente `WPFAV_VERSION` em `wp-favorites-plugin.php`. +O método `WPFAV_Database::maybe_update()` (hook `plugins_loaded`) detecta a diferença +de versão e chama `create_table()` via `dbDelta()` automaticamente. diff --git a/wp-favorites-plugin/assets/js/favorites.js b/wp-favorites-plugin/assets/js/favorites.js index 8852039d..a641456e 100644 --- a/wp-favorites-plugin/assets/js/favorites.js +++ b/wp-favorites-plugin/assets/js/favorites.js @@ -1,13 +1,109 @@ -async function toggleFavorite(postId) { - const response = await fetch('/wp-json/wpfav/v1/toggle', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-WP-Nonce': wpApiSettings.nonce - }, - body: JSON.stringify({ post_id: postId }) - }); - - const data = await response.json(); - console.log(data); -} \ No newline at end of file +/** + * WP Favorites Plugin — front-end + * + * Usa wpFavSettings (definido via wp_localize_script) para obter: + * wpFavSettings.apiUrl — URL base da REST API do plugin + * wpFavSettings.nonce — nonce gerado pelo WordPress para autenticação REST + */ + +( function () { + 'use strict'; + + /** + * Alterna o estado de favorito de um post. + * + * @param {number} postId - ID do post. + * @param {HTMLElement} [btn] - Botão que disparou a ação (opcional, para feedback visual). + * @returns {Promise} + */ + async function toggleFavorite( postId, btn ) { + if ( ! postId || postId <= 0 ) { + console.error( 'wpfav: post_id inválido.', postId ); + return; + } + + // Feedback visual imediato: desabilita o botão durante a requisição. + if ( btn ) { + btn.disabled = true; + btn.setAttribute( 'aria-busy', 'true' ); + } + + try { + const response = await fetch( wpFavSettings.apiUrl + '/toggle', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': wpFavSettings.nonce, + }, + body: JSON.stringify( { post_id: parseInt( postId, 10 ) } ), + } ); + + // Trata erros HTTP (401, 404, 500, etc.) + if ( ! response.ok ) { + const errorData = await response.json().catch( () => ( {} ) ); + const message = errorData.message || `Erro HTTP ${ response.status }`; + console.error( 'wpfav toggle error:', message ); + + if ( btn ) { + btn.setAttribute( 'data-wpfav-error', message ); + } + + return; + } + + const data = await response.json(); + + // Atualiza o estado visual do botão conforme a resposta da API. + if ( btn ) { + const isFavorited = data.status === 'added'; + btn.classList.toggle( 'wpfav--active', isFavorited ); + btn.setAttribute( 'aria-pressed', String( isFavorited ) ); + btn.setAttribute( 'title', isFavorited ? 'Remover dos favoritos' : 'Adicionar aos favoritos' ); + btn.removeAttribute( 'data-wpfav-error' ); + } + + // Dispara um evento customizado para que o tema/outros scripts possam reagir. + document.dispatchEvent( + new CustomEvent( 'wpfav:toggled', { + detail: { postId: data.post_id, status: data.status }, + } ) + ); + } catch ( error ) { + // Trata falhas de rede (sem conexão, timeout, etc.) + console.error( 'wpfav: falha na requisição.', error ); + + if ( btn ) { + btn.setAttribute( 'data-wpfav-error', 'Falha na conexão. Tente novamente.' ); + } + } finally { + // Reabilita o botão independentemente do resultado. + if ( btn ) { + btn.disabled = false; + btn.removeAttribute( 'aria-busy' ); + } + } + } + + /** + * Delega o clique em botões com data-wpfav-post-id dentro do document. + * Compatível com conteúdo carregado dinamicamente (infinite scroll, etc.). + * + * Uso no tema: + * + */ + document.addEventListener( 'click', function ( event ) { + const btn = event.target.closest( '[data-wpfav-post-id]' ); + + if ( ! btn ) { + return; + } + + const postId = parseInt( btn.dataset.wpfavPostId, 10 ); + toggleFavorite( postId, btn ); + } ); + + // Expõe a função globalmente para uso programático se necessário. + window.wpFavToggle = toggleFavorite; +} )(); diff --git a/wp-favorites-plugin/includes/class-api.php b/wp-favorites-plugin/includes/class-api.php index 94213537..e716bdc7 100644 --- a/wp-favorites-plugin/includes/class-api.php +++ b/wp-favorites-plugin/includes/class-api.php @@ -1,36 +1,94 @@ 'POST', - 'callback' => [self::class, 'toggle'], - 'permission_callback' => function () { - return is_user_logged_in(); - } - ]); - - register_rest_route('wpfav/v1', '/list', [ - 'methods' => 'GET', - 'callback' => [self::class, 'get_favorites'], - 'permission_callback' => function () { - return is_user_logged_in(); - } - ]); + /** + * Registra os endpoints da REST API. + * + * @return void + */ + public static function register_routes(): void { + register_rest_route( + 'wpfav/v1', + '/toggle', + [ + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => [ self::class, 'toggle' ], + 'permission_callback' => [ self::class, 'user_is_logged_in' ], + 'args' => [ + 'post_id' => [ + 'required' => true, + 'type' => 'integer', + 'minimum' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => static function ( $value ) { + return is_numeric( $value ) && (int) $value > 0; + }, + 'description' => __( 'ID do post a favoritar/desfavoritar.', 'wp-favorites-plugin' ), + ], + ], + ] + ); + + register_rest_route( + 'wpfav/v1', + '/list', + [ + 'methods' => WP_REST_Server::READABLE, // 'GET' + 'callback' => [ self::class, 'get_favorites' ], + 'permission_callback' => [ self::class, 'user_is_logged_in' ], + ] + ); } - public static function toggle($request) { + /** + * Callback do endpoint POST /toggle. + * + * @param WP_REST_Request $request Objeto da requisição. + * + * @return WP_REST_Response|WP_Error + */ + public static function toggle( WP_REST_Request $request ) { $user_id = get_current_user_id(); - $post_id = $request->get_param('post_id'); + $post_id = (int) $request->get_param( 'post_id' ); - return WPFAV_Favorites::toggle_favorite($user_id, $post_id); + return WPFAV_Favorites::toggle_favorite( $user_id, $post_id ); } - public static function get_favorites() { + /** + * Callback do endpoint GET /list. + * + * @return WP_REST_Response + */ + public static function get_favorites(): WP_REST_Response { $user_id = get_current_user_id(); - return WPFAV_Favorites::get_user_favorites($user_id); + return WPFAV_Favorites::get_user_favorites( $user_id ); + } + + /** + * Permission callback reutilizável: verifica se o usuário está logado. + * + * @return bool|WP_Error + */ + public static function user_is_logged_in() { + if ( is_user_logged_in() ) { + return true; + } + + return new WP_Error( + 'rest_forbidden', + __( 'Você precisa estar logado para usar esta funcionalidade.', 'wp-favorites-plugin' ), + [ 'status' => 401 ] + ); } -} \ No newline at end of file +} diff --git a/wp-favorites-plugin/includes/class-database.php b/wp-favorites-plugin/includes/class-database.php index b6461886..88f10e27 100644 --- a/wp-favorites-plugin/includes/class-database.php +++ b/wp-favorites-plugin/includes/class-database.php @@ -1,36 +1,62 @@ prefix . 'favorites'; + $table_name = $wpdb->prefix . 'favorites'; $charset_collate = $wpdb->get_charset_collate(); - $sql = "CREATE TABLE $table_name ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - user_id BIGINT UNSIGNED NOT NULL, - post_id BIGINT UNSIGNED NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY user_post (user_id, post_id) - ) $charset_collate;"; + /* + * IMPORTANTE: dbDelta() é sensível ao formato do SQL. + * Referência: https://developer.wordpress.org/plugins/creating-tables-with-plugins/ + */ + $sql = "CREATE TABLE {$table_name} ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT(20) UNSIGNED NOT NULL, + post_id BIGINT(20) UNSIGNED NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY user_post (user_id, post_id), + KEY idx_user_id (user_id) + ) {$charset_collate};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; - dbDelta($sql); + dbDelta( $sql ); - add_option('wpfav_db_version', WPFAV_VERSION); + update_option( self::DB_VERSION_OPTION, WPFAV_VERSION ); } - public static function maybe_update() { - $installed_version = get_option('wpfav_db_version'); - - if ($installed_version !== WPFAV_VERSION) { - + /** + * Verifica se a versão instalada da tabela está desatualizada + * e executa create_table() caso necessário. + * Chamada no hook plugins_loaded para suportar atualizações. + * + * @return void + */ + public static function maybe_update(): void { + $installed_version = get_option( self::DB_VERSION_OPTION, '0.0.0' ); + + if ( version_compare( $installed_version, WPFAV_VERSION, '<' ) ) { self::create_table(); - - update_option('wpfav_db_version', WPFAV_VERSION); } } -} \ No newline at end of file +} diff --git a/wp-favorites-plugin/includes/class-favorites.php b/wp-favorites-plugin/includes/class-favorites.php index 8e53f7bb..f50fba52 100644 --- a/wp-favorites-plugin/includes/class-favorites.php +++ b/wp-favorites-plugin/includes/class-favorites.php @@ -1,43 +1,148 @@ 404 ] + ); + } + + global $wpdb; $table = $wpdb->prefix . 'favorites'; - $exists = $wpdb->get_var($wpdb->prepare( - "SELECT id FROM $table WHERE user_id = %d AND post_id = %d", - $user_id, - $post_id - )); + // Verifica se o favorito já existe. + $exists = $wpdb->get_var( + $wpdb->prepare( + "SELECT id FROM {$table} WHERE user_id = %d AND post_id = %d LIMIT 1", + $user_id, + $post_id + ) + ); - if ($exists) { - $wpdb->delete($table, [ - 'user_id' => $user_id, - 'post_id' => $post_id - ]); + if ( $exists ) { + $deleted = $wpdb->delete( + $table, + [ + 'user_id' => $user_id, + 'post_id' => $post_id, + ], + [ '%d', '%d' ] + ); + + if ( false === $deleted ) { + return new WP_Error( + 'db_error', + __( 'Erro ao remover o favorito.', 'wp-favorites-plugin' ), + [ 'status' => 500 ] + ); + } - return ['status' => 'removed']; + return new WP_REST_Response( + [ + 'status' => 'removed', + 'post_id' => $post_id, + 'message' => __( 'Post removido dos favoritos.', 'wp-favorites-plugin' ), + ], + 200 + ); } - $wpdb->insert($table, [ - 'user_id' => $user_id, - 'post_id' => $post_id - ]); + // Insere o novo favorito declarando os formatos de cada coluna. + $inserted = $wpdb->insert( + $table, + [ + 'user_id' => $user_id, + 'post_id' => $post_id, + ], + [ '%d', '%d' ] + ); + + if ( false === $inserted ) { + return new WP_Error( + 'db_error', + __( 'Erro ao salvar o favorito.', 'wp-favorites-plugin' ), + [ 'status' => 500 ] + ); + } - return ['status' => 'added']; + return new WP_REST_Response( + [ + 'status' => 'added', + 'post_id' => $post_id, + 'message' => __( 'Post adicionado aos favoritos.', 'wp-favorites-plugin' ), + ], + 201 + ); } - public static function get_user_favorites($user_id) { - global $wpdb; + /** + * Retorna todos os posts favoritados por um usuário. + * + * @param int $user_id ID do usuário. + * + * @return WP_REST_Response + */ + public static function get_user_favorites( int $user_id ): WP_REST_Response { + $user_id = absint( $user_id ); + global $wpdb; $table = $wpdb->prefix . 'favorites'; - return $wpdb->get_results($wpdb->prepare( - "SELECT post_id FROM $table WHERE user_id = %d", - $user_id - )); + $results = $wpdb->get_results( + $wpdb->prepare( + "SELECT post_id, created_at FROM {$table} WHERE user_id = %d ORDER BY created_at DESC", + $user_id + ) + ); + + return new WP_REST_Response( + [ + 'total' => count( $results ), + 'favorites' => $results, + ], + 200 + ); + } + + /** + * Verifica se um post_id é válido: existe, é do tipo 'post' e está publicado. + * + * @param int $post_id ID do post. + * + * @return bool + */ + private static function post_is_valid( int $post_id ): bool { + if ( $post_id <= 0 ) { + return false; + } + + $post = get_post( $post_id ); + + return ( $post instanceof WP_Post && 'post' === $post->post_type && 'publish' === $post->post_status ); } -} \ No newline at end of file +} diff --git a/wp-favorites-plugin/includes/class-plugin.php b/wp-favorites-plugin/includes/class-plugin.php index 9762ce84..7812fedb 100644 --- a/wp-favorites-plugin/includes/class-plugin.php +++ b/wp-favorites-plugin/includes/class-plugin.php @@ -1,17 +1,68 @@ load_dependencies(); - add_action('plugins_loaded', ['WPFAV_Database', 'maybe_update']); - - add_action('rest_api_init', ['WPFAV_API', 'register_routes']); + + add_action( 'plugins_loaded', [ 'WPFAV_Database', 'maybe_update' ] ); + add_action( 'rest_api_init', [ 'WPFAV_API', 'register_routes' ] ); + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] ); } - private function load_dependencies() { + /** + * Carrega os arquivos de classes necessários. + * + * @return void + */ + private function load_dependencies(): void { require_once WPFAV_PLUGIN_PATH . 'includes/class-api.php'; require_once WPFAV_PLUGIN_PATH . 'includes/class-favorites.php'; } -} \ No newline at end of file + + /** + * Registra e localiza o script JS do plugin. + * + * @return void + */ + public function enqueue_scripts(): void { + if ( ! is_user_logged_in() ) { + return; + } + + wp_enqueue_script( + 'wpfav-favorites', + WPFAV_PLUGIN_URL . 'assets/js/favorites.js', + [], + WPFAV_VERSION, + true + ); + + /* + * wp_localize_script: passa dados do PHP para o JS de forma segura. + */ + wp_localize_script( + 'wpfav-favorites', + 'wpFavSettings', + [ + 'apiUrl' => esc_url_raw( rest_url( 'wpfav/v1' ) ), + 'nonce' => wp_create_nonce( 'wp_rest' ), + ] + ); + } +} diff --git a/wp-favorites-plugin/uninstall.php b/wp-favorites-plugin/uninstall.php index efd0b30c..4e60ab8c 100644 --- a/wp-favorites-plugin/uninstall.php +++ b/wp-favorites-plugin/uninstall.php @@ -1,10 +1,17 @@ prefix . 'favorites'; -$wpdb->query("DROP TABLE IF EXISTS $table"); -delete_option('wpfav_db_version'); \ No newline at end of file +$wpdb->query( 'DROP TABLE IF EXISTS `' . esc_sql( $table ) . '`' ); + +delete_option( 'wpfav_db_version' ); diff --git a/wp-favorites-plugin/wp-favorites-plugin.php b/wp-favorites-plugin/wp-favorites-plugin.php index e2e62228..f68a33c2 100644 --- a/wp-favorites-plugin/wp-favorites-plugin.php +++ b/wp-favorites-plugin/wp-favorites-plugin.php @@ -1,23 +1,31 @@ run(); } -wpfav_init(); \ No newline at end of file +wpfav_init();