From 0c299335878096f3a0aa23afb475e0c8b1a0181e Mon Sep 17 00:00:00 2001 From: gustavodscruz Date: Mon, 4 Aug 2025 23:31:22 -0300 Subject: [PATCH 1/4] feat: estrutura basica --- .gitignore | 23 +++++++++ includes/main.php | 81 ++++++++++++++++++++++++++++++++ wordpress-back-end-challenge.php | 42 +++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 .gitignore create mode 100644 includes/main.php create mode 100644 wordpress-back-end-challenge.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..34519fe9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# WordPress +wp-config.php +wp-content/uploads/ +wp-content/cache/ + +# Docker +.env.local + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +/vendor/ +.env +.env.example +docker-compose.yml diff --git a/includes/main.php b/includes/main.php new file mode 100644 index 00000000..64387313 --- /dev/null +++ b/includes/main.php @@ -0,0 +1,81 @@ + + * @license GPL v2 or later + * @link https://github.com/gustavodscruz/wordpress-back-end-challenge + */ + +/** + * Class WP_Backend_Challenge + * + * Handles the backend challenge functionality for WordPress. + * + * @category Wpbackendchallenge + * + * @package WordPress_Back_End_Challenge + * + * @author Gustavo Dias + * + * @license http://opensource.org/licenses/MIT MIT + * + * @link https://github.com/gustavodiasdsc/wordpress-back-end-challenge + */ +class WP_Backend_Challenge +{ + + /** + * Instância única da classe + */ + private static $_instance = null; + + /** + * Construtor privado para implementar Singleton + */ + private function __construct() + { + $this->_initHooks(); + } + + /** + * Retorna a instância única da classe + * + * @return WP_Backend_Challenge + */ + public static function getInstance() + { + if (null === self::$_instance) { + self::$_instance = new self(); + } + return self::$_instance; + } + + /** + * Inicializa os hooks do WordPress + * + * @return void + */ + private function _initHooks() + { + + } + + +} diff --git a/wordpress-back-end-challenge.php b/wordpress-back-end-challenge.php new file mode 100644 index 00000000..a13ad2ba --- /dev/null +++ b/wordpress-back-end-challenge.php @@ -0,0 +1,42 @@ + + * + * @license http://opensource.org/licenses/MIT MIT + * + * @link https://github.com/gustavodscruz/wordpress-back-end-challenge + */ + +// Evita acesso direto +if (!defined('ABSPATH')) { + exit; +} + +declare(strict_types=1); + +// Definir constantes do plugin +define('WP_BACKEND_CHALLENGE_VERSION', '1.0.0'); +define('WP_BACKEND_CHALLENGE_PLUGIN_URL', plugin_dir_url(__FILE__)); +define('WP_BACKEND_CHALLENGE_PLUGIN_PATH', plugin_dir_path(__FILE__)); + +require_once WP_BACKEND_CHALLENGE_PLUGIN_PATH . '/includes/main.php'; + + +WP_Backend_Challenge::getInstance(); \ No newline at end of file From 13b9c7f24da255659cca4036050a9807da0d99b9 Mon Sep 17 00:00:00 2001 From: gustavodscruz Date: Tue, 5 Aug 2025 18:04:17 -0300 Subject: [PATCH 2/4] feat: atualizacao da estrutura do plugin e criacao da tabela --- .gitignore | 7 +++ includes/main.php | 92 ++++++++++++++++++++++---------- wordpress-back-end-challenge.php | 26 +++++++-- 3 files changed, 93 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 34519fe9..4117f7cb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,13 @@ Thumbs.db # Logs *.log +logs/ + +# Development +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* /vendor/ .env diff --git a/includes/main.php b/includes/main.php index 64387313..66d61115 100644 --- a/includes/main.php +++ b/includes/main.php @@ -1,28 +1,20 @@ - - * @license GPL v2 or later - * @link https://github.com/gustavodscruz/wordpress-back-end-challenge + * @package Wpbackendchallenge/includes + * @author Gustavo Dias + * @license GPL v2 or later + * @link https://github.com/gustavodscruz/wordpress-back-end-challenge */ +// Evita acesso direto +if (!defined('ABSPATH')) { + exit; +} + /** * Class WP_Backend_Challenge * @@ -40,42 +32,86 @@ */ class WP_Backend_Challenge { - + /** * Instância única da classe */ private static $_instance = null; - + private $_table_name; + /** * Construtor privado para implementar Singleton */ - private function __construct() + private function __construct() { + global $wpdb; + $this->_table_name = $wpdb->prefix . 'wp_backend_challenge'; $this->_initHooks(); } - + /** * Retorna a instância única da classe * * @return WP_Backend_Challenge */ - public static function getInstance() + public static function getInstance() { if (null === self::$_instance) { self::$_instance = new self(); } return self::$_instance; } - + /** * Inicializa os hooks do WordPress * * @return void */ - private function _initHooks() + private function _initHooks() { + add_action('admin_notices', array($this, 'displayAdminNotice')); + } + /** + * Exibe uma notificação na área de administração + * + * @return void + */ + public function displayAdminNotice() + { + $message = esc_html__( + 'Plugin WordPress Back End Challenge ativado com sucesso! 2.0!!!!! Eba!', + 'wp-backend-challenge' + ); + + echo '
+

' . $message . '

+
'; } - + + /** + * Cria tabela no mysql + * + * @return void + */ + public static function createTable() + { + global $wpdb; + $table_name = $wpdb->prefix . 'wp_backend_challenge'; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table_name ( + id int NOT NULL AUTO_INCREMENT, + user_id bigint(20) UNSIGNED NOT NULL, + post_id bigint(20) UNSIGNED NOT NULL, + date_favorited datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY (user_id, post_id) + ) $charset_collate;"; + + include_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta($sql); + } + } diff --git a/wordpress-back-end-challenge.php b/wordpress-back-end-challenge.php index a13ad2ba..cd98cbdf 100644 --- a/wordpress-back-end-challenge.php +++ b/wordpress-back-end-challenge.php @@ -1,4 +1,5 @@ Date: Tue, 5 Aug 2025 19:20:18 -0300 Subject: [PATCH 3/4] feat: funcionalidade de botao de favoritos e verificacao de favoritado --- includes/main.php | 57 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/includes/main.php b/includes/main.php index 66d61115..dfd94ff4 100644 --- a/includes/main.php +++ b/includes/main.php @@ -113,5 +113,60 @@ public static function createTable() dbDelta($sql); } - + /** + * Adiciona botão de favorito + * + * @param string $content Conteúdo em html + * + * @return string $content Conteúdo em html com o botão + */ + public function addFavoriteButton(string $content) + { + if (!(is_singular('post') && is_user_logged_in())) { + return $content; + } + + $user_id = get_current_user_id(); + $post_id = get_the_ID(); + + $is_favorited = $this->_isPostFavorited($user_id, $post_id); + $button_text = $is_favorited + ? __('Desfavoritar', 'wp_backend_challenge') + : __('Favoritar', 'wp_backend_challenge'); + $button_class = $is_favorited ? 'favorited' : ''; + $button_html = '

' + . $button_text + . '

'; + return $content . $button_html; + } + + /** + * Função que verifica pelos ids se o post está favoritado ou não + * + * @param string|integer $user_id Id do usuário + * @param string|integer $post_id Id do post + * + * @return bool retorna verdadeiro se o post estiver favoritado + */ + private function _isPostFavorited(string|int $user_id, string|int $post_id) + { + /** + * Banco de dados do wordpress + * + * @var wpdb $wpdb + */ + global $wpdb; + $count = $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT (*) FROM {$this->_table_name} WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + ) + ); + return $count > 0; + } } From f2f3df26443b209de1dd5d8457ec8ed3e9b153c2 Mon Sep 17 00:00:00 2001 From: gustavodscruz Date: Thu, 7 Aug 2025 18:24:52 -0300 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20implementa=20funcionalidade=20de=20?= =?UTF-8?q?favoritos=20com=20AJAX=20e=20p=C3=A1gina=20de=20administra?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/favorites-page.php | 202 ++++++++++++++++++ assets/css/favorites.css | 283 +++++++++++++++++++++++++ assets/js/favorites.js | 104 ++++++++++ includes/main.php | 344 ++++++++++++++++++++++++++++++- wordpress-back-end-challenge.php | 21 +- 5 files changed, 944 insertions(+), 10 deletions(-) create mode 100644 admin/favorites-page.php create mode 100644 assets/css/favorites.css create mode 100644 assets/js/favorites.js diff --git a/admin/favorites-page.php b/admin/favorites-page.php new file mode 100644 index 00000000..dbd3d62b --- /dev/null +++ b/admin/favorites-page.php @@ -0,0 +1,202 @@ + + * @license GPL v2 or later + * @link https://github.com/gustavodscruz/wordpress-back-end-challenge + */ + +// Evita acesso direto +if (!defined('ABSPATH')) { + exit; +} +?> + +
+

+ +
+
+

+

+
+ +
+

+ post_id])) { + $post_counts[$favorite->post_id] = array( + 'title' => $favorite->post_title, + 'count' => 0 + ); + } + $post_counts[$favorite->post_id]['count']++; + } + + // Ordenar por contagem + uasort( + $post_counts, + function ($a, $b) { + return $b['count'] - $a['count']; + } + ); + + $top_posts = array_slice($post_counts, 0, 3, true); + ?> + + +
    + $data): ?> +
  • + + + + + () + +
  • + +
+ +

+ +

+ +
+
+ + +
+

+
+ +

+ + + + + + + + + + + + + + + + + + + + +
+ + + display_name); ?> + + +
+ ID: user_id; ?> +
+ + + post_title); ?> + + +
+ + + + + +
+ date_favorited) + ); ?> + + + + +
+ +
+ + \ No newline at end of file diff --git a/assets/css/favorites.css b/assets/css/favorites.css new file mode 100644 index 00000000..f7829249 --- /dev/null +++ b/assets/css/favorites.css @@ -0,0 +1,283 @@ +/* WordPress Backend Challenge - Favorites CSS */ + +/* Botão de favorito */ +.wpb-favorite-button { + display: inline-block; + background: #0073aa; + color: white; + padding: 10px 15px; + text-decoration: none; + border-radius: 4px; + border: none; + cursor: pointer; + font-size: 14px; + transition: all 0.3s ease; + margin: 10px 0; +} + +.wpb-favorite-button:hover { + background: #005a87; + color: white; + text-decoration: none; +} + +.wpb-favorite-button.favorited { + background: #dc3232; +} + +.wpb-favorite-button.favorited:hover { + background: #a00; +} + +.wpb-favorite-button.loading { + opacity: 0.6; + cursor: not-allowed; + pointer-events: none; +} + +/* Ícones para os botões */ +.wpb-favorite-button:before { + content: "♡"; + margin-right: 5px; + font-size: 16px; +} + +.wpb-favorite-button.favorited:before { + content: "♥"; +} + +/* Feedback de ações */ +.wpb-feedback { + position: fixed; + top: 32px; + right: 20px; + z-index: 9999; + padding: 12px 20px; + border-radius: 4px; + font-weight: bold; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + display: none; +} + +.wpb-feedback-success { + background: #46b450; + color: white; +} + +.wpb-feedback-error { + background: #dc3232; + color: white; +} + +/* Contador de favoritos */ +.wpb-favorites-count { + display: inline-block; + background: #f1f1f1; + padding: 5px 10px; + border-radius: 3px; + font-size: 12px; + color: #666; + margin-left: 10px; +} + +.wpb-favorites-count:before { + content: "♥ "; + color: #dc3232; +} + +/* Lista de favoritos (para página de admin) */ +.wpb-favorites-list { + margin-top: 20px; +} + +.wpb-favorite-item { + display: flex; + align-items: center; + padding: 15px; + border: 1px solid #ddd; + margin-bottom: 10px; + border-radius: 4px; + background: white; +} + +.wpb-favorite-item .user-info { + flex: 1; + margin-right: 15px; +} + +.wpb-favorite-item .post-info { + flex: 2; + margin-right: 15px; +} + +.wpb-favorite-item .date-info { + flex: 1; + text-align: right; + color: #666; + font-size: 12px; +} + +.wpb-favorite-item .user-info strong { + color: #0073aa; +} + +.wpb-favorite-item .post-info a { + text-decoration: none; + color: #333; +} + +.wpb-favorite-item .post-info a:hover { + color: #0073aa; +} + +/* Página de favoritos do usuário */ +.wpb-user-favorites { + margin: 20px 0; +} + +.wpb-user-favorites h3 { + margin-bottom: 15px; + color: #333; +} + +.wpb-user-favorites .no-favorites { + color: #666; + font-style: italic; + padding: 20px; + text-align: center; + background: #f9f9f9; + border-radius: 4px; +} + +/* Responsividade */ +@media (max-width: 768px) { + .wpb-favorite-button { + display: block; + text-align: center; + margin: 15px 0; + } + + .wpb-feedback { + right: 10px; + left: 10px; + top: 46px; + } + + .wpb-favorite-item { + flex-direction: column; + align-items: flex-start; + } + + .wpb-favorite-item .user-info, + .wpb-favorite-item .post-info, + .wpb-favorite-item .date-info { + flex: none; + width: 100%; + margin-right: 0; + margin-bottom: 10px; + } + + .wpb-favorite-item .date-info { + text-align: left; + } +} + +/* Integração com temas */ +.entry-content .wpb-favorite-button { + margin-top: 20px; +} + +/* Estados hover e focus melhorados */ +.wpb-favorite-button:focus { + outline: 2px solid #0073aa; + outline-offset: 2px; +} + +.wpb-favorite-button:active { + transform: translateY(1px); +} + +/* Animações */ +@keyframes wpb-pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.05); } + 100% { transform: scale(1); } +} + +.wpb-favorite-button.favorited { + animation: wpb-pulse 0.3s ease-in-out; +} + +/* Shortcode de favoritos do usuário */ +.wpb-user-favorites { + margin: 20px 0; +} + +.wpb-favorites-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +.wpb-favorite-post { + background: white; + border: 1px solid #ddd; + border-radius: 8px; + padding: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.wpb-favorite-post:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.wpb-favorite-post h4 { + margin-top: 0; + margin-bottom: 10px; +} + +.wpb-favorite-post h4 a { + color: #333; + text-decoration: none; +} + +.wpb-favorite-post h4 a:hover { + color: #0073aa; +} + +.wpb-favorite-post .post-date { + color: #666; + font-size: 14px; + margin-bottom: 10px; +} + +.wpb-favorite-post .post-excerpt { + color: #555; + line-height: 1.6; + margin-bottom: 15px; +} + +.wpb-favorite-post .read-more { + color: #0073aa; + text-decoration: none; + font-weight: bold; +} + +.wpb-favorite-post .read-more:hover { + text-decoration: underline; +} + +/* Responsividade para shortcode */ +@media (max-width: 768px) { + .wpb-favorites-grid { + grid-template-columns: 1fr; + gap: 15px; + } + + .wpb-favorite-post { + padding: 15px; + } +} diff --git a/assets/js/favorites.js b/assets/js/favorites.js new file mode 100644 index 00000000..0409cc06 --- /dev/null +++ b/assets/js/favorites.js @@ -0,0 +1,104 @@ +/** + * WordPress Backend Challenge - Favorites JavaScript + */ + +jQuery(document).ready(function($) { + 'use strict'; + + /** + * Manipula o clique no botão de favorito + */ + $('.wpb-favorite-button').on('click', function(e) { + e.preventDefault(); + + var button = $(this); + var postId = button.data('post-id'); + + // Previne cliques múltiplos + if (button.hasClass('loading')) { + return; + } + + // Adiciona estado de loading + button.addClass('loading').text('Carregando...'); + + // Faz a requisição AJAX + $.ajax({ + url: wpbFavorites.ajax_url, + type: 'POST', + data: { + action: 'wpb_toggle_favorite', + post_id: postId, + nonce: wpbFavorites.nonce + }, + dataType: 'json', + success: function(response) { + if (response.success) { + // Atualiza o botão + button.text(response.data.button_text); + + // Atualiza as classes + if (response.data.favorited) { + button.addClass('favorited'); + } else { + button.removeClass('favorited'); + } + + // Mostra feedback visual + showFeedback( + response.data.favorited ? 'Post favoritado!' : 'Post removido dos favoritos!', + 'success' + ); + } else { + showFeedback('Erro: ' + response.data.message, 'error'); + } + }, + error: function() { + showFeedback('Erro de conexão. Tente novamente.', 'error'); + }, + complete: function() { + // Remove estado de loading + button.removeClass('loading'); + } + }); + }); + + /** + * Mostra feedback visual para o usuário + * + * @param {string} message Mensagem a ser exibida + * @param {string} type Tipo da mensagem (success|error) + */ + function showFeedback(message, type) { + // Remove feedbacks anteriores + $('.wpb-feedback').remove(); + + // Cria novo feedback + var feedback = $('
' + message + '
'); + + // Adiciona à página + $('body').prepend(feedback); + + // Anima entrada + feedback.slideDown(300); + + // Remove após 3 segundos + setTimeout(function() { + feedback.slideUp(300, function() { + feedback.remove(); + }); + }, 3000); + } + + /** + * Atualiza contador de favoritos se existir + */ + function updateFavoritesCount(postId, increment) { + var counter = $('.wpb-favorites-count[data-post-id="' + postId + '"]'); + if (counter.length) { + var currentCount = parseInt(counter.text()) || 0; + var newCount = increment ? currentCount + 1 : currentCount - 1; + counter.text(Math.max(0, newCount)); + } + } +}); diff --git a/includes/main.php b/includes/main.php index dfd94ff4..5a7729ac 100644 --- a/includes/main.php +++ b/includes/main.php @@ -2,6 +2,7 @@ /** * Classe principal do plugin WordPress Back End Challenge + * PHP version 8.1 * * @category Wpbackendchallenge * @package Wpbackendchallenge/includes @@ -16,10 +17,8 @@ } /** - * Class WP_Backend_Challenge + * Classe principal do plugin WordPress Back End Challenge * - * Handles the backend challenge functionality for WordPress. - * * @category Wpbackendchallenge * * @package WordPress_Back_End_Challenge @@ -70,6 +69,16 @@ public static function getInstance() private function _initHooks() { add_action('admin_notices', array($this, 'displayAdminNotice')); + add_filter('the_content', array($this, 'addFavoriteButton')); + add_action('wp_ajax_wpb_toggle_favorite', array($this, 'toggleFavorite')); + add_action( + 'wp_ajax_nopriv_wpb_toggle_favorite', + array($this, 'toggleFavorite') + ); + add_action('wp_enqueue_scripts', array($this, 'enqueueScripts')); + add_action('admin_menu', array($this, 'addAdminMenu')); + add_action('admin_init', array($this, 'handleAdminActions')); + add_shortcode('wpb_user_favorites', array($this, 'userFavoritesShortcode')); } /** @@ -79,8 +88,16 @@ private function _initHooks() */ public function displayAdminNotice() { + $show_notice = get_transient('wpb_activation_notice'); + + if (!$show_notice) { + return; + } + + delete_transient('wpb_activation_notive'); + $message = esc_html__( - 'Plugin WordPress Back End Challenge ativado com sucesso! 2.0!!!!! Eba!', + 'Plugin WordPress Back End Challenge ativado com sucesso!', 'wp-backend-challenge' ); @@ -144,6 +161,71 @@ public function addFavoriteButton(string $content) return $content . $button_html; } + /** + * Função AJAX para favoritar ou desfavoritar. + * + * @return void + */ + public function toggleFavorite() + { + check_ajax_referer('wpb_favorite_nonce', 'nonce'); + + if (!is_user_logged_in()) { + wp_send_json_error(['message' => 'Você precisa estar logado']); + } + + $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; + $user_id = get_current_user_id(); + + if ($post_id === 0) { + wp_send_json_error(['message' => 'ID do post inválido']); + } + + global $wpdb; + /** + * Objeto wpdb; + * + * @var wpdb $wpdb + * */ + + $is_favorited = $this->_isPostFavorited($user_id, $post_id); + + if ($is_favorited) { + $result = $wpdb->delete( + $this->_table_name, + [ + 'user_id' => $user_id, + 'post_id' => $post_id + ] + ); + } else { + $result = $wpdb->insert( + $this->_table_name, + [ + 'user_id' => $user_id, + 'post_id' => $post_id, + 'date_favorited' => current_time('mysql') + ] + ); + } + + if ($result !== false) { + $new_status = !$is_favorited; + $button_text = $new_status + ? __('Desfavoritar', 'wp_backend_challenge') + : __('Favoritar', 'wp_backend_challenge'); + + wp_send_json_success( + [ + 'favorited' => $new_status, + 'button_text' => $button_text + ] + ); + } else { + wp_send_json_error(['message' => 'Erro ao atualizar favorito']); + } + } + /** * Função que verifica pelos ids se o post está favoritado ou não * @@ -162,11 +244,263 @@ private function _isPostFavorited(string|int $user_id, string|int $post_id) global $wpdb; $count = $wpdb->get_var( $wpdb->prepare( - "SELECT COUNT (*) FROM {$this->_table_name} WHERE user_id = %d AND post_id = %d", + "SELECT COUNT(*) FROM {$this->_table_name} WHERE user_id = %d AND post_id = %d", $user_id, $post_id ) ); return $count > 0; } + + /** + * Enfileira scripts e estilos + * + * @return void + */ + public function enqueueScripts() + { + if (!is_singular('post')) { + return; + } + + wp_enqueue_script( + 'wpb-favorites', + WP_BACKEND_CHALLENGE_PLUGIN_URL . 'assets/js/favorites.js', + array('jquery'), + WP_BACKEND_CHALLENGE_VERSION, + true + ); + + wp_localize_script( + 'wpb-favorites', + 'wpbFavorites', + array( + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('wpb_favorite_nonce') + ) + ); + + wp_enqueue_style( + 'wpb-favorites-style', + WP_BACKEND_CHALLENGE_PLUGIN_URL . 'assets/css/favorites.css', + array(), + WP_BACKEND_CHALLENGE_VERSION + ); + } + + /** + * Adiciona menu de administração + * + * @return void + */ + public function addAdminMenu() + { + add_menu_page( + __('Favoritos', 'wp_backend_challenge'), + __('Favoritos', 'wp_backend_challenge'), + 'manage_options', + 'wpb-favorites', + array($this, 'adminPage'), + 'dashicons-heart', + 30 + ); + } + + /** + * Página de administração dos favoritos + * + * @return void + */ + public function adminPage() + { + global $wpdb; + /** + * Objeto wpdb; + * + * @var wpdb $wpdb + * */ + + $favorites = $wpdb->get_results( + "SELECT f.*, u.display_name, p.post_title + FROM {$this->_table_name} f + LEFT JOIN {$wpdb->users} u ON f.user_id = u.ID + LEFT JOIN {$wpdb->posts} p ON f.post_id = p.ID + ORDER BY f.date_favorited DESC" + ); + + include WP_BACKEND_CHALLENGE_PLUGIN_PATH . 'admin/favorites-page.php'; + } + + /** + * Retorna posts favoritos de um usuário + * + * @param integer $user_id ID do usuário + * + * @return array Array com os posts favoritos + */ + public function getUserFavorites(int $user_id) + { + global $wpdb; + /** + * Objeto wpdb; + * + * @var wpdb $wpdb + * */ + + return $wpdb->get_results( + $wpdb->prepare( + "SELECT p.* FROM {$wpdb->posts} p + INNER JOIN {$this->_table_name} f ON p.ID = f.post_id + WHERE f.user_id = %d AND p.post_status = 'publish' + ORDER BY f.date_favorited DESC", + $user_id + ) + ); + } + + /** + * Conta total de favoritos de um post + * + * @param integer $post_id ID do post + * + * @return integer Número de favoritos + */ + public function getPostFavoritesCount(int $post_id) + { + global $wpdb; + /** + * Objeto wpdb; + * + * @var wpdb $wpdb + * */ + + return (int) $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(*) FROM {$this->_table_name} WHERE post_id = %d", + $post_id + ) + ); + } + + /** + * Manipula ações administrativas + * + * @return void + */ + public function handleAdminActions() + { + if (!is_admin() || !current_user_can('manage_options')) { + return; + } + + $action = isset($_GET['action']) ? $_GET['action'] : ''; + $page = isset($_GET['page']) ? $_GET['page'] : ''; + + if ($page !== 'wpb-favorites' || $action !== 'delete') { + return; + } + + $id = isset($_GET['id']) ? intval($_GET['id']) : 0; + if ($id === 0) { + return; + } + + if (!wp_verify_nonce($_GET['_wpnonce'], 'delete_favorite_' . $id)) { + wp_die(__('Erro de segurança.', 'wp_backend_challenge')); + } + + global $wpdb; + /** + * Banco de dados WordPress + * + * @var wpdb $wpdb + */ + + $result = $wpdb->delete($this->_table_name, array('id' => $id)); + + if ($result !== false) { + $redirect_url = add_query_arg( + 'deleted', + '1', + admin_url('admin.php?page=wpb-favorites') + ); + } else { + $redirect_url = add_query_arg( + 'error', + '1', + admin_url('admin.php?page=wpb-favorites') + ); + } + + wp_redirect($redirect_url); + exit; + } + + /** + * Shortcode para exibir favoritos do usuário atual + * + * @param array $atts Atributos do shortcode + * + * @return string HTML do shortcode + */ + public function userFavoritesShortcode($atts) + { + $atts = shortcode_atts( + array( + 'limit' => 10, + 'show_excerpt' => 'true', + 'show_date' => 'true' + ), + $atts, + 'wpb_user_favorites' + ); + + if (!is_user_logged_in()) { + return '

' . __('Você precisa estar logado para ver seus favoritos.', 'wp_backend_challenge') . '

'; + } + + $user_id = get_current_user_id(); + $favorites = $this->getUserFavorites($user_id); + + if (empty($favorites)) { + return '
+

' . __('Meus Favoritos', 'wp_backend_challenge') . '

+

' . __('Você ainda não tem posts favoritos.', 'wp_backend_challenge') . '

+
'; + } + + $html = '
'; + $html .= '

' . __('Meus Favoritos', 'wp_backend_challenge') . '

'; + $html .= '
'; + + $limit = intval($atts['limit']); + $count = 0; + + foreach ($favorites as $post) { + if ($count >= $limit) { + break; + } + + $html .= '
'; + $html .= '

' . esc_html($post->post_title) . '

'; + + if ($atts['show_date'] === 'true') { + $html .= ''; + } + + if ($atts['show_excerpt'] === 'true') { + $excerpt = wp_trim_words($post->post_content, 20); + $html .= '

' . esc_html($excerpt) . '

'; + } + + $html .= '

' . __('Ler mais', 'wp_backend_challenge') . '

'; + $html .= '
'; + + $count++; + } + + $html .= '
'; + + return $html; + } } diff --git a/wordpress-back-end-challenge.php b/wordpress-back-end-challenge.php index cd98cbdf..5709d4bb 100644 --- a/wordpress-back-end-challenge.php +++ b/wordpress-back-end-challenge.php @@ -37,24 +37,35 @@ // Incluir arquivos apenas quando o WordPress estiver carregado add_action( - 'plugins_loaded', function () { + 'plugins_loaded', + function () { include_once WP_BACKEND_CHALLENGE_PLUGIN_PATH . 'includes/main.php'; WP_Backend_Challenge::getInstance(); } ); - +// Hooks de ativação e desativação +register_activation_hook(__FILE__, 'wpbActivatePlugin'); +register_deactivation_hook(__FILE__, 'wpbDeactivatePlugin'); /** - * Chama a criação de tabela de usuários + * Ativa o plugin e cria as tabelas necessárias * * @return void */ -function wpfActivate() +function wpbActivatePlugin() { include_once WP_BACKEND_CHALLENGE_PLUGIN_PATH . 'includes/main.php'; WP_Backend_Challenge::createTable(); } -register_activation_hook(__FILE__, 'wpfActivate'); +/** + * Desativa o plugin + * + * @return void + */ +function wpbDeactivatPplugin() +{ + // Código de desativação se necessário +}