From 3a7abb7e02e1d0acbd502377ec35952088d4a718 Mon Sep 17 00:00:00 2001 From: Lucas Sens Date: Fri, 25 Jul 2025 14:17:12 -0300 Subject: [PATCH 1/6] Initial commit: Create organized WordPress plugin structure --- .../assets/css/wp-favorites.css | 117 +++++++++ wp-favorites-plugin/assets/js/wp-favorites.js | 194 ++++++++++++++ wp-favorites-plugin/config.php | 62 +++++ .../includes/class-wp-favorites-assets.php | 106 ++++++++ .../includes/class-wp-favorites-helpers.php | 124 +++++++++ .../includes/class-wp-favorites-plugin.php | 240 ++++++++++++++++++ .../includes/class-wp-favorites-template.php | 148 +++++++++++ .../templates/favorite-button.php | 55 ++++ wp-favorites-plugin/uninstall.php | 23 ++ wp-favorites-plugin/wp-favorites-plugin.php | 48 ++++ 10 files changed, 1117 insertions(+) create mode 100644 wp-favorites-plugin/assets/css/wp-favorites.css create mode 100644 wp-favorites-plugin/assets/js/wp-favorites.js create mode 100644 wp-favorites-plugin/config.php create mode 100644 wp-favorites-plugin/includes/class-wp-favorites-assets.php create mode 100644 wp-favorites-plugin/includes/class-wp-favorites-helpers.php create mode 100644 wp-favorites-plugin/includes/class-wp-favorites-plugin.php create mode 100644 wp-favorites-plugin/includes/class-wp-favorites-template.php create mode 100644 wp-favorites-plugin/templates/favorite-button.php create mode 100644 wp-favorites-plugin/uninstall.php create mode 100644 wp-favorites-plugin/wp-favorites-plugin.php diff --git a/wp-favorites-plugin/assets/css/wp-favorites.css b/wp-favorites-plugin/assets/css/wp-favorites.css new file mode 100644 index 00000000..4e00fb50 --- /dev/null +++ b/wp-favorites-plugin/assets/css/wp-favorites.css @@ -0,0 +1,117 @@ +/** + * WP Favorites Plugin Styles + */ + +.wp-favorites-button { + display: inline-flex; + align-items: center; + gap: 5px; + background: #0073aa; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; + font-size: 14px; + text-decoration: none; + line-height: 1.4; +} + +.wp-favorites-button:hover { + background: #005a87; + color: white; + text-decoration: none; +} + +.wp-favorites-button.favorited { + background: #d63638; +} + +.wp-favorites-button.favorited:hover { + background: #b32d2e; +} + +.wp-favorites-button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.wp-favorites-icon { + font-size: 16px; + line-height: 1; +} + +.wp-favorites-count { + background: rgba(255, 255, 255, 0.2); + padding: 2px 6px; + border-radius: 10px; + font-size: 12px; + margin-left: 5px; +} + +.wp-favorites-message { + position: fixed; + top: 20px; + right: 20px; + padding: 12px 20px; + border-radius: 4px; + color: white; + z-index: 9999; + font-size: 14px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + animation: wp-favorites-slide-in 0.3s ease; +} + +.wp-favorites-message.success { + background: #46b450; +} + +.wp-favorites-message.error { + background: #dc3232; +} + +@keyframes wp-favorites-slide-in { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Loading state */ +.wp-favorites-button.loading { + opacity: 0.7; + pointer-events: none; +} + +.wp-favorites-button.loading .wp-favorites-icon { + animation: wp-favorites-spin 1s linear infinite; +} + +@keyframes wp-favorites-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* Responsive */ +@media (max-width: 768px) { + .wp-favorites-button { + padding: 6px 12px; + font-size: 13px; + } + + .wp-favorites-message { + top: 10px; + right: 10px; + left: 10px; + font-size: 13px; + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/assets/js/wp-favorites.js b/wp-favorites-plugin/assets/js/wp-favorites.js new file mode 100644 index 00000000..0f6ebbdb --- /dev/null +++ b/wp-favorites-plugin/assets/js/wp-favorites.js @@ -0,0 +1,194 @@ +/** + * WP Favorites Plugin JavaScript + */ + +(function($) { + 'use strict'; + + // WP Favorites Plugin namespace + window.WPFavorites = window.WPFavorites || {}; + + // Configuration + WPFavorites.config = { + restUrl: wpFavorites.restUrl || '/wp-json/wp-favorites/v1', + nonce: wpFavorites.nonce || '', + messages: { + favorited: 'Post added to favorites!', + unfavorited: 'Post removed from favorites!', + error: 'An error occurred. Please try again.', + loginRequired: 'Please log in to favorite posts.' + } + }; + + // Main class + WPFavorites.FavoritesButton = function(element, options) { + this.element = $(element); + this.options = $.extend({}, WPFavorites.config, options); + this.postId = this.element.data('post-id'); + this.isFavorited = false; + this.isLoading = false; + + this.init(); + }; + + WPFavorites.FavoritesButton.prototype = { + init: function() { + this.bindEvents(); + this.checkFavoriteStatus(); + }, + + bindEvents: function() { + var self = this; + this.element.on('click', function(e) { + e.preventDefault(); + self.toggleFavorite(); + }); + }, + + checkFavoriteStatus: function() { + var self = this; + $.ajax({ + url: self.options.restUrl + '/favorites', + method: 'GET', + beforeSend: function(xhr) { + xhr.setRequestHeader('X-WP-Nonce', self.options.nonce); + }, + success: function(response) { + if (response.success && response.favorites.includes(parseInt(self.postId))) { + self.setFavorited(true); + } + }, + error: function() { + // Silent fail for status check + } + }); + }, + + toggleFavorite: function() { + if (this.isLoading) return; + + if (this.isFavorited) { + this.unfavorite(); + } else { + this.favorite(); + } + }, + + favorite: function() { + var self = this; + this.setLoading(true); + + $.ajax({ + url: self.options.restUrl + '/favorite', + method: 'POST', + data: JSON.stringify({ + post_id: self.postId + }), + contentType: 'application/json', + beforeSend: function(xhr) { + xhr.setRequestHeader('X-WP-Nonce', self.options.nonce); + }, + success: function(response) { + if (response.success) { + self.setFavorited(true); + self.showMessage(self.options.messages.favorited, 'success'); + } else { + self.showMessage(response.message || self.options.messages.error, 'error'); + } + }, + error: function(xhr) { + var message = self.options.messages.error; + if (xhr.status === 401) { + message = self.options.messages.loginRequired; + } + self.showMessage(message, 'error'); + }, + complete: function() { + self.setLoading(false); + } + }); + }, + + unfavorite: function() { + var self = this; + this.setLoading(true); + + $.ajax({ + url: self.options.restUrl + '/unfavorite', + method: 'POST', + data: JSON.stringify({ + post_id: self.postId + }), + contentType: 'application/json', + beforeSend: function(xhr) { + xhr.setRequestHeader('X-WP-Nonce', self.options.nonce); + }, + success: function(response) { + if (response.success) { + self.setFavorited(false); + self.showMessage(self.options.messages.unfavorited, 'success'); + } else { + self.showMessage(response.message || self.options.messages.error, 'error'); + } + }, + error: function() { + self.showMessage(self.options.messages.error, 'error'); + }, + complete: function() { + self.setLoading(false); + } + }); + }, + + setFavorited: function(favorited) { + this.isFavorited = favorited; + this.element.toggleClass('favorited', favorited); + + var icon = this.element.find('.wp-favorites-icon'); + var text = this.element.find('.wp-favorites-text'); + + if (favorited) { + icon.text('♥'); + text.text('Favorited'); + } else { + icon.text('♡'); + text.text('Favorite'); + } + }, + + setLoading: function(loading) { + this.isLoading = loading; + this.element.toggleClass('loading', loading); + this.element.prop('disabled', loading); + }, + + showMessage: function(message, type) { + var messageElement = $('
') + .addClass('wp-favorites-message ' + type) + .text(message); + + $('body').append(messageElement); + + setTimeout(function() { + messageElement.fadeOut(300, function() { + $(this).remove(); + }); + }, 3000); + } + }; + + // jQuery plugin + $.fn.wpFavorites = function(options) { + return this.each(function() { + if (!$(this).data('wp-favorites')) { + $(this).data('wp-favorites', new WPFavorites.FavoritesButton(this, options)); + } + }); + }; + + // Auto-initialize on document ready + $(document).ready(function() { + $('.wp-favorites-button').wpFavorites(); + }); + +})(jQuery); \ No newline at end of file diff --git a/wp-favorites-plugin/config.php b/wp-favorites-plugin/config.php new file mode 100644 index 00000000..e448a098 --- /dev/null +++ b/wp-favorites-plugin/config.php @@ -0,0 +1,62 @@ + '1.0.0', + 'name' => 'WP Favorites Plugin', + 'description' => 'A WordPress plugin that allows logged-in users to favorite and unfavorite posts using WP REST API', + 'author' => 'WordPress Back-end Challenge', + 'license' => 'GPL v2 or later', + 'text_domain' => 'wp-favorites-plugin', + 'domain_path' => '/languages', + + // Database + 'table_name' => 'user_favorites', + + // REST API + 'rest_namespace' => 'wp-favorites/v1', + 'rest_routes' => array( + 'favorite' => '/favorite', + 'unfavorite' => '/unfavorite', + 'favorites' => '/favorites' + ), + + // Assets + 'assets' => array( + 'css' => array( + 'wp-favorites-style' => 'assets/css/wp-favorites.css' + ), + 'js' => array( + 'wp-favorites-script' => 'assets/js/wp-favorites.js' + ) + ), + + // Shortcodes + 'shortcodes' => array( + 'wp_favorites_button' => 'Favorite Button', + 'wp_favorites_list' => 'Favorites List' + ), + + // Hooks + 'hooks' => array( + 'wp_favorites_post_favorited' => 'Fired when a post is favorited', + 'wp_favorites_post_unfavorited' => 'Fired when a post is unfavorited' + ), + + // Requirements + 'requirements' => array( + 'php' => '5.6', + 'wordpress' => '4.7', + 'plugins' => array() + ) +); \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-wp-favorites-assets.php b/wp-favorites-plugin/includes/class-wp-favorites-assets.php new file mode 100644 index 00000000..d35f2744 --- /dev/null +++ b/wp-favorites-plugin/includes/class-wp-favorites-assets.php @@ -0,0 +1,106 @@ + rest_url('wp-favorites/v1'), + 'nonce' => wp_create_nonce('wp_rest'), + 'ajaxUrl' => admin_url('admin-ajax.php'), + 'isLoggedIn' => is_user_logged_in(), + 'strings' => array( + 'favorited' => __('Post added to favorites!', 'wp-favorites-plugin'), + 'unfavorited' => __('Post removed from favorites!', 'wp-favorites-plugin'), + 'error' => __('An error occurred. Please try again.', 'wp-favorites-plugin'), + 'loginRequired' => __('Please log in to favorite posts.', 'wp-favorites-plugin') + ) + )); + } + + /** + * Enqueue admin scripts and styles + */ + public static function enqueue_admin_scripts($hook) { + // Only enqueue on plugin settings page + if (strpos($hook, 'wp-favorites') === false) { + return; + } + + wp_enqueue_style( + 'wp-favorites-admin-style', + WP_FAVORITES_PLUGIN_URL . 'assets/css/wp-favorites-admin.css', + array(), + WP_FAVORITES_PLUGIN_VERSION + ); + + wp_enqueue_script( + 'wp-favorites-admin-script', + WP_FAVORITES_PLUGIN_URL . 'assets/js/wp-favorites-admin.js', + array('jquery'), + WP_FAVORITES_PLUGIN_VERSION, + true + ); + } + + /** + * Get asset URL + * + * @param string $path Asset path + * @return string + */ + public static function get_asset_url($path) { + return WP_FAVORITES_PLUGIN_URL . 'assets/' . ltrim($path, '/'); + } + + /** + * Get asset path + * + * @param string $path Asset path + * @return string + */ + public static function get_asset_path($path) { + return WP_FAVORITES_PLUGIN_PATH . 'assets/' . ltrim($path, '/'); + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-wp-favorites-helpers.php b/wp-favorites-plugin/includes/class-wp-favorites-helpers.php new file mode 100644 index 00000000..30d4beac --- /dev/null +++ b/wp-favorites-plugin/includes/class-wp-favorites-helpers.php @@ -0,0 +1,124 @@ +prefix . 'user_favorites'; + $user_id = get_current_user_id(); + + $result = $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) FROM $table_name WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + )); + + return $result > 0; + } + + /** + * Get user's favorite posts + * + * @param int $user_id User ID (optional, defaults to current user) + * @return array + */ + public static function get_user_favorites($user_id = null) { + if (!$user_id) { + $user_id = get_current_user_id(); + } + + if (!$user_id) { + return array(); + } + + global $wpdb; + $table_name = $wpdb->prefix . 'user_favorites'; + + $favorites = $wpdb->get_col($wpdb->prepare( + "SELECT post_id FROM $table_name WHERE user_id = %d ORDER BY created_at DESC", + $user_id + )); + + return $favorites; + } + + /** + * Get favorite count for a post + * + * @param int $post_id Post ID + * @return int + */ + public static function get_favorite_count($post_id) { + global $wpdb; + $table_name = $wpdb->prefix . 'user_favorites'; + + $count = $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) FROM $table_name WHERE post_id = %d", + $post_id + )); + + return (int) $count; + } + + /** + * Sanitize post ID + * + * @param mixed $post_id + * @return int|false + */ + public static function sanitize_post_id($post_id) { + $post_id = absint($post_id); + return $post_id > 0 ? $post_id : false; + } + + /** + * Validate post exists + * + * @param int $post_id Post ID + * @return bool + */ + public static function post_exists($post_id) { + return get_post($post_id) !== null; + } + + /** + * Get REST API base URL + * + * @return string + */ + public static function get_rest_base_url() { + return rest_url('wp-favorites/v1'); + } + + /** + * Get nonce for AJAX requests + * + * @return string + */ + public static function get_nonce() { + return wp_create_nonce('wp_rest'); + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-wp-favorites-plugin.php b/wp-favorites-plugin/includes/class-wp-favorites-plugin.php new file mode 100644 index 00000000..0978699c --- /dev/null +++ b/wp-favorites-plugin/includes/class-wp-favorites-plugin.php @@ -0,0 +1,240 @@ +init_hooks(); + } + + /** + * Initialize hooks + */ + private function init_hooks() { + add_action('init', array($this, 'init')); + register_activation_hook(WP_FAVORITES_PLUGIN_FILE, array($this, 'activate')); + register_deactivation_hook(WP_FAVORITES_PLUGIN_FILE, array($this, 'deactivate')); + } + + /** + * Initialize plugin + */ + public function init() { + $this->create_table(); + $this->register_rest_routes(); + $this->init_assets(); + $this->init_templates(); + } + + /** + * Plugin activation + */ + public function activate() { + $this->create_table(); + } + + /** + * Plugin deactivation + */ + public function deactivate() { + // Cleanup if needed + } + + /** + * Create custom table + */ + private function create_table() { + global $wpdb; + + $table_name = $wpdb->prefix . 'user_favorites'; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table_name ( + id mediumint(9) NOT NULL AUTO_INCREMENT, + user_id bigint(20) NOT NULL, + post_id bigint(20) 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); + } + + /** + * Register REST API routes + */ + private function register_rest_routes() { + add_action('rest_api_init', function () { + register_rest_route('wp-favorites/v1', '/favorite', array( + 'methods' => 'POST', + 'callback' => array($this, 'favorite_post'), + 'permission_callback' => array($this, 'check_user_logged_in'), + )); + + register_rest_route('wp-favorites/v1', '/unfavorite', array( + 'methods' => 'POST', + 'callback' => array($this, 'unfavorite_post'), + 'permission_callback' => array($this, 'check_user_logged_in'), + )); + + register_rest_route('wp-favorites/v1', '/favorites', array( + 'methods' => 'GET', + 'callback' => array($this, 'get_user_favorites'), + 'permission_callback' => array($this, 'check_user_logged_in'), + )); + }); + } + + /** + * Check if user is logged in + */ + public function check_user_logged_in() { + return is_user_logged_in(); + } + + /** + * Favorite a post + */ + public function favorite_post($request) { + $user_id = get_current_user_id(); + $post_id = $request->get_param('post_id'); + + if (!$post_id || !get_post($post_id)) { + return new WP_Error('invalid_post', 'Invalid post ID', array('status' => 400)); + } + + global $wpdb; + $table_name = $wpdb->prefix . 'user_favorites'; + + $result = $wpdb->insert( + $table_name, + array( + 'user_id' => $user_id, + 'post_id' => $post_id, + ), + array('%d', '%d') + ); + + if ($result === false) { + return new WP_Error('database_error', 'Failed to favorite post', array('status' => 500)); + } + + do_action('wp_favorites_post_favorited', $user_id, $post_id); + + return array( + 'success' => true, + 'message' => 'Post favorited successfully', + 'post_id' => $post_id + ); + } + + /** + * Unfavorite a post + */ + public function unfavorite_post($request) { + $user_id = get_current_user_id(); + $post_id = $request->get_param('post_id'); + + if (!$post_id) { + return new WP_Error('invalid_post', 'Invalid post ID', array('status' => 400)); + } + + global $wpdb; + $table_name = $wpdb->prefix . 'user_favorites'; + + $result = $wpdb->delete( + $table_name, + array( + 'user_id' => $user_id, + 'post_id' => $post_id, + ), + array('%d', '%d') + ); + + if ($result === false) { + return new WP_Error('database_error', 'Failed to unfavorite post', array('status' => 500)); + } + + do_action('wp_favorites_post_unfavorited', $user_id, $post_id); + + return array( + 'success' => true, + 'message' => 'Post unfavorited successfully', + 'post_id' => $post_id + ); + } + + /** + * Get user favorites + */ + public function get_user_favorites($request) { + $user_id = get_current_user_id(); + + global $wpdb; + $table_name = $wpdb->prefix . 'user_favorites'; + + $favorites = $wpdb->get_col($wpdb->prepare( + "SELECT post_id FROM $table_name WHERE user_id = %d ORDER BY created_at DESC", + $user_id + )); + + return array( + 'success' => true, + 'favorites' => $favorites + ); + } + + /** + * Initialize assets + */ + private function init_assets() { + require_once WP_FAVORITES_PLUGIN_PATH . 'includes/class-wp-favorites-assets.php'; + WP_Favorites_Assets::init(); + } + + /** + * Initialize template functions + */ + private function init_templates() { + require_once WP_FAVORITES_PLUGIN_PATH . 'includes/class-wp-favorites-helpers.php'; + require_once WP_FAVORITES_PLUGIN_PATH . 'includes/class-wp-favorites-template.php'; + WP_Favorites_Template::init(); + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-wp-favorites-template.php b/wp-favorites-plugin/includes/class-wp-favorites-template.php new file mode 100644 index 00000000..a06e6eb3 --- /dev/null +++ b/wp-favorites-plugin/includes/class-wp-favorites-template.php @@ -0,0 +1,148 @@ + get_the_ID(), + 'show_count' => false, + 'button_text' => __('Favorite', 'wp-favorites-plugin') + ); + + $args = wp_parse_args($args, $defaults); + + // Load template + $template_path = WP_FAVORITES_PLUGIN_PATH . 'templates/favorite-button.php'; + if (file_exists($template_path)) { + include $template_path; + } + } + + /** + * Favorite button shortcode + * + * @param array $atts Shortcode attributes + * @return string + */ + public static function favorite_button_shortcode($atts) { + $atts = shortcode_atts(array( + 'post_id' => get_the_ID(), + 'show_count' => 'false', + 'button_text' => __('Favorite', 'wp-favorites-plugin') + ), $atts); + + $atts['show_count'] = filter_var($atts['show_count'], FILTER_VALIDATE_BOOLEAN); + + ob_start(); + self::favorite_button($atts); + return ob_get_clean(); + } + + /** + * Favorites list shortcode + * + * @param array $atts Shortcode attributes + * @return string + */ + public static function favorites_list_shortcode($atts) { + if (!is_user_logged_in()) { + return '

' . __('Please log in to view your favorites.', 'wp-favorites-plugin') . '

'; + } + + $atts = shortcode_atts(array( + 'user_id' => get_current_user_id(), + 'limit' => 10, + 'show_excerpt' => 'false', + 'show_date' => 'false' + ), $atts); + + $favorites = WP_Favorites_Helpers::get_user_favorites($atts['user_id']); + + if (empty($favorites)) { + return '

' . __('No favorite posts found.', 'wp-favorites-plugin') . '

'; + } + + // Limit results + $favorites = array_slice($favorites, 0, (int) $atts['limit']); + + $output = '
'; + $output .= '

' . __('Your Favorite Posts', 'wp-favorites-plugin') . '

'; + $output .= '
    '; + + foreach ($favorites as $post_id) { + $post = get_post($post_id); + if (!$post) continue; + + $output .= '
  • '; + $output .= '' . get_the_title($post_id) . ''; + + if (filter_var($atts['show_date'], FILTER_VALIDATE_BOOLEAN)) { + $output .= ' (' . get_the_date('', $post_id) . ')'; + } + + if (filter_var($atts['show_excerpt'], FILTER_VALIDATE_BOOLEAN)) { + $excerpt = wp_trim_words(get_the_excerpt($post_id), 20); + $output .= '

    ' . $excerpt . '

    '; + } + + $output .= '
  • '; + } + + $output .= '
'; + $output .= '
'; + + return $output; + } + + /** + * Get template path + * + * @param string $template_name Template name + * @return string + */ + public static function get_template_path($template_name) { + return WP_FAVORITES_PLUGIN_PATH . 'templates/' . $template_name . '.php'; + } + + /** + * Load template + * + * @param string $template_name Template name + * @param array $args Template arguments + */ + public static function load_template($template_name, $args = array()) { + $template_path = self::get_template_path($template_name); + + if (file_exists($template_path)) { + extract($args); + include $template_path; + } + } +} \ No newline at end of file diff --git a/wp-favorites-plugin/templates/favorite-button.php b/wp-favorites-plugin/templates/favorite-button.php new file mode 100644 index 00000000..d2be9339 --- /dev/null +++ b/wp-favorites-plugin/templates/favorite-button.php @@ -0,0 +1,55 @@ + + + + + + + + \ 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..9f5184e5 --- /dev/null +++ b/wp-favorites-plugin/uninstall.php @@ -0,0 +1,23 @@ +prefix . 'user_favorites'; +$wpdb->query("DROP TABLE IF EXISTS $table_name"); + +// Delete plugin options +delete_option('wp_favorites_version'); +delete_option('wp_favorites_settings'); + +// Clear any cached data that has been removed +wp_cache_flush(); \ 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..06c18547 --- /dev/null +++ b/wp-favorites-plugin/wp-favorites-plugin.php @@ -0,0 +1,48 @@ + Date: Fri, 25 Jul 2025 14:18:45 -0300 Subject: [PATCH 2/6] Add comprehensive testing documentation --- wp-favorites-plugin/TESTING.md | 283 +++++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 wp-favorites-plugin/TESTING.md diff --git a/wp-favorites-plugin/TESTING.md b/wp-favorites-plugin/TESTING.md new file mode 100644 index 00000000..ffca42d6 --- /dev/null +++ b/wp-favorites-plugin/TESTING.md @@ -0,0 +1,283 @@ +# Testing WP Favorites Plugin + +This document explains how to test the WP Favorites Plugin functionality. + +## Installation Testing + +### 1. Install the Plugin +1. Upload the `wp-favorites-plugin` folder to `/wp-content/plugins/` +2. Activate the plugin through WordPress admin +3. Check that the custom table `wp_user_favorites` is created + +### 2. Verify Database Table +```sql +-- Check if table exists +SHOW TABLES LIKE 'wp_user_favorites'; + +-- Check table structure +DESCRIBE wp_user_favorites; +``` + +## REST API Testing + +### 1. Test Authentication +```bash +# Test without authentication (should fail) +curl -X POST "https://your-site.com/wp-json/wp-favorites/v1/favorite" \ + -H "Content-Type: application/json" \ + -d '{"post_id": 1}' +``` + +### 2. Test with Authentication +```bash +# Get nonce first +curl -X GET "https://your-site.com/wp-json/wp/v2/users/me" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# Test favorite endpoint +curl -X POST "https://your-site.com/wp-json/wp-favorites/v1/favorite" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"post_id": 1}' + +# Test unfavorite endpoint +curl -X POST "https://your-site.com/wp-json/wp-favorites/v1/unfavorite" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"post_id": 1}' + +# Test get favorites endpoint +curl -X GET "https://your-site.com/wp-json/wp-favorites/v1/favorites" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +## Frontend Testing + +### 1. Add Favorite Button to Theme +Add this code to your theme's `single.php` or any template: + +```php + get_the_ID(), + 'show_count' => true, + 'button_text' => 'Favorite this post' +)); +?> +``` + +### 2. Test Shortcodes +Add these shortcodes to any post or page: + +```php +// Favorite button shortcode +[wp_favorites_button post_id="1" show_count="true"] + +// Favorites list shortcode +[wp_favorites_list limit="5" show_date="true" show_excerpt="true"] +``` + +### 3. Test JavaScript Functionality +1. Open browser developer tools +2. Click the favorite button +3. Check network tab for API calls +4. Verify button state changes +5. Check for success/error messages + +## Manual Testing Checklist + +### Plugin Activation +- [ ] Plugin activates without errors +- [ ] Custom table is created +- [ ] No PHP errors in error log + +### REST API Endpoints +- [ ] `/wp-json/wp-favorites/v1/favorite` (POST) +- [ ] `/wp-json/wp-favorites/v1/unfavorite` (POST) +- [ ] `/wp-json/wp-favorites/v1/favorites` (GET) + +### Authentication +- [ ] Unauthenticated requests return 401 +- [ ] Authenticated requests work properly +- [ ] Nonce validation works + +### Database Operations +- [ ] Favoriting a post adds record to database +- [ ] Unfavoriting removes record from database +- [ ] Duplicate favorites are prevented +- [ ] Invalid post IDs are handled + +### Frontend Functionality +- [ ] Favorite button displays correctly +- [ ] Button state changes on click +- [ ] Loading states work +- [ ] Success/error messages display +- [ ] Non-logged-in users see login link + +### Shortcodes +- [ ] `[wp_favorites_button]` displays button +- [ ] `[wp_favorites_list]` displays favorites +- [ ] Shortcode attributes work correctly + +### CSS/JS Assets +- [ ] CSS styles load properly +- [ ] JavaScript functionality works +- [ ] No console errors +- [ ] Responsive design works + +## Automated Testing + +### PHPUnit Tests +Create a `tests` directory and add test files: + +```php +// tests/test-wp-favorites-plugin.php +class WP_Favorites_Plugin_Test extends WP_UnitTestCase { + + public function test_plugin_activation() { + // Test table creation + global $wpdb; + $table_name = $wpdb->prefix . 'user_favorites'; + $this->assertTrue($wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name); + } + + public function test_favorite_post() { + // Test favoriting functionality + $user_id = $this->factory->user->create(); + $post_id = $this->factory->post->create(); + + wp_set_current_user($user_id); + + $request = new WP_REST_Request('POST', '/wp-favorites/v1/favorite'); + $request->set_param('post_id', $post_id); + + $response = rest_do_request($request); + $this->assertEquals(200, $response->get_status()); + } +} +``` + +### JavaScript Tests +Use Jest or similar for JavaScript testing: + +```javascript +// tests/wp-favorites.test.js +describe('WP Favorites Plugin', () => { + test('should initialize favorite button', () => { + document.body.innerHTML = ` + + `; + + // Initialize plugin + $('.wp-favorites-button').wpFavorites(); + + expect($('.wp-favorites-button').length).toBe(1); + }); +}); +``` + +## Performance Testing + +### Database Queries +Monitor database performance: + +```php +// Enable query logging +define('SAVEQUERIES', true); + +// Check query count +global $wpdb; +echo "Total queries: " . count($wpdb->queries); +``` + +### Memory Usage +Monitor memory usage during operations: + +```php +echo "Memory usage: " . memory_get_usage(true) / 1024 / 1024 . " MB"; +``` + +## Security Testing + +### Input Validation +- [ ] Test with invalid post IDs +- [ ] Test with non-existent posts +- [ ] Test with negative numbers +- [ ] Test with string values + +### SQL Injection +- [ ] Test with malicious input +- [ ] Verify prepared statements work +- [ ] Check for SQL errors + +### XSS Prevention +- [ ] Test with script tags in input +- [ ] Verify output is properly escaped +- [ ] Check for reflected XSS + +## Browser Testing + +### Cross-browser Compatibility +Test in: +- [ ] Chrome +- [ ] Firefox +- [ ] Safari +- [ ] Edge +- [ ] Mobile browsers + +### Responsive Design +- [ ] Desktop (1920x1080) +- [ ] Tablet (768x1024) +- [ ] Mobile (375x667) + +## Error Handling + +### Test Error Scenarios +- [ ] Network failures +- [ ] Database connection issues +- [ ] Invalid responses +- [ ] Timeout scenarios + +### Logging +- [ ] Check error logs +- [ ] Verify debug information +- [ ] Test error reporting + +## Cleanup Testing + +### Plugin Deactivation +- [ ] Test plugin deactivation +- [ ] Verify no PHP errors +- [ ] Check database integrity + +### Plugin Uninstall +- [ ] Test uninstall process +- [ ] Verify table removal +- [ ] Check option cleanup + +## Integration Testing + +### WordPress Core +- [ ] Test with different WordPress versions +- [ ] Test with different themes +- [ ] Test with other plugins + +### REST API +- [ ] Test with different authentication methods +- [ ] Test with different content types +- [ ] Test rate limiting + +## Reporting Issues + +When reporting issues, include: +1. WordPress version +2. Plugin version +3. PHP version +4. Error messages +5. Steps to reproduce +6. Expected vs actual behavior +7. Browser/device information \ No newline at end of file From 429cdbc05a9970d3706e5fe2baa1188eb73f1b1e Mon Sep 17 00:00:00 2001 From: Lucas Sens Date: Fri, 25 Jul 2025 14:40:14 -0300 Subject: [PATCH 3/6] Fix database errors: Add table existence check and prevent duplicate favorites --- wp-favorites-plugin.zip | Bin 0 -> 11820 bytes wp-favorites-plugin/TESTING.md | 283 ------------------ .../includes/class-wp-favorites-plugin.php | 58 ++++ 3 files changed, 58 insertions(+), 283 deletions(-) create mode 100644 wp-favorites-plugin.zip delete mode 100644 wp-favorites-plugin/TESTING.md diff --git a/wp-favorites-plugin.zip b/wp-favorites-plugin.zip new file mode 100644 index 0000000000000000000000000000000000000000..aec266ea71e3548aa8d912d5dc84f1b5d0fb7bac GIT binary patch literal 11820 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 literal 0 HcmV?d00001 diff --git a/wp-favorites-plugin/TESTING.md b/wp-favorites-plugin/TESTING.md deleted file mode 100644 index ffca42d6..00000000 --- a/wp-favorites-plugin/TESTING.md +++ /dev/null @@ -1,283 +0,0 @@ -# Testing WP Favorites Plugin - -This document explains how to test the WP Favorites Plugin functionality. - -## Installation Testing - -### 1. Install the Plugin -1. Upload the `wp-favorites-plugin` folder to `/wp-content/plugins/` -2. Activate the plugin through WordPress admin -3. Check that the custom table `wp_user_favorites` is created - -### 2. Verify Database Table -```sql --- Check if table exists -SHOW TABLES LIKE 'wp_user_favorites'; - --- Check table structure -DESCRIBE wp_user_favorites; -``` - -## REST API Testing - -### 1. Test Authentication -```bash -# Test without authentication (should fail) -curl -X POST "https://your-site.com/wp-json/wp-favorites/v1/favorite" \ - -H "Content-Type: application/json" \ - -d '{"post_id": 1}' -``` - -### 2. Test with Authentication -```bash -# Get nonce first -curl -X GET "https://your-site.com/wp-json/wp/v2/users/me" \ - -H "Authorization: Bearer YOUR_JWT_TOKEN" - -# Test favorite endpoint -curl -X POST "https://your-site.com/wp-json/wp-favorites/v1/favorite" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_JWT_TOKEN" \ - -d '{"post_id": 1}' - -# Test unfavorite endpoint -curl -X POST "https://your-site.com/wp-json/wp-favorites/v1/unfavorite" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_JWT_TOKEN" \ - -d '{"post_id": 1}' - -# Test get favorites endpoint -curl -X GET "https://your-site.com/wp-json/wp-favorites/v1/favorites" \ - -H "Authorization: Bearer YOUR_JWT_TOKEN" -``` - -## Frontend Testing - -### 1. Add Favorite Button to Theme -Add this code to your theme's `single.php` or any template: - -```php - get_the_ID(), - 'show_count' => true, - 'button_text' => 'Favorite this post' -)); -?> -``` - -### 2. Test Shortcodes -Add these shortcodes to any post or page: - -```php -// Favorite button shortcode -[wp_favorites_button post_id="1" show_count="true"] - -// Favorites list shortcode -[wp_favorites_list limit="5" show_date="true" show_excerpt="true"] -``` - -### 3. Test JavaScript Functionality -1. Open browser developer tools -2. Click the favorite button -3. Check network tab for API calls -4. Verify button state changes -5. Check for success/error messages - -## Manual Testing Checklist - -### Plugin Activation -- [ ] Plugin activates without errors -- [ ] Custom table is created -- [ ] No PHP errors in error log - -### REST API Endpoints -- [ ] `/wp-json/wp-favorites/v1/favorite` (POST) -- [ ] `/wp-json/wp-favorites/v1/unfavorite` (POST) -- [ ] `/wp-json/wp-favorites/v1/favorites` (GET) - -### Authentication -- [ ] Unauthenticated requests return 401 -- [ ] Authenticated requests work properly -- [ ] Nonce validation works - -### Database Operations -- [ ] Favoriting a post adds record to database -- [ ] Unfavoriting removes record from database -- [ ] Duplicate favorites are prevented -- [ ] Invalid post IDs are handled - -### Frontend Functionality -- [ ] Favorite button displays correctly -- [ ] Button state changes on click -- [ ] Loading states work -- [ ] Success/error messages display -- [ ] Non-logged-in users see login link - -### Shortcodes -- [ ] `[wp_favorites_button]` displays button -- [ ] `[wp_favorites_list]` displays favorites -- [ ] Shortcode attributes work correctly - -### CSS/JS Assets -- [ ] CSS styles load properly -- [ ] JavaScript functionality works -- [ ] No console errors -- [ ] Responsive design works - -## Automated Testing - -### PHPUnit Tests -Create a `tests` directory and add test files: - -```php -// tests/test-wp-favorites-plugin.php -class WP_Favorites_Plugin_Test extends WP_UnitTestCase { - - public function test_plugin_activation() { - // Test table creation - global $wpdb; - $table_name = $wpdb->prefix . 'user_favorites'; - $this->assertTrue($wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name); - } - - public function test_favorite_post() { - // Test favoriting functionality - $user_id = $this->factory->user->create(); - $post_id = $this->factory->post->create(); - - wp_set_current_user($user_id); - - $request = new WP_REST_Request('POST', '/wp-favorites/v1/favorite'); - $request->set_param('post_id', $post_id); - - $response = rest_do_request($request); - $this->assertEquals(200, $response->get_status()); - } -} -``` - -### JavaScript Tests -Use Jest or similar for JavaScript testing: - -```javascript -// tests/wp-favorites.test.js -describe('WP Favorites Plugin', () => { - test('should initialize favorite button', () => { - document.body.innerHTML = ` - - `; - - // Initialize plugin - $('.wp-favorites-button').wpFavorites(); - - expect($('.wp-favorites-button').length).toBe(1); - }); -}); -``` - -## Performance Testing - -### Database Queries -Monitor database performance: - -```php -// Enable query logging -define('SAVEQUERIES', true); - -// Check query count -global $wpdb; -echo "Total queries: " . count($wpdb->queries); -``` - -### Memory Usage -Monitor memory usage during operations: - -```php -echo "Memory usage: " . memory_get_usage(true) / 1024 / 1024 . " MB"; -``` - -## Security Testing - -### Input Validation -- [ ] Test with invalid post IDs -- [ ] Test with non-existent posts -- [ ] Test with negative numbers -- [ ] Test with string values - -### SQL Injection -- [ ] Test with malicious input -- [ ] Verify prepared statements work -- [ ] Check for SQL errors - -### XSS Prevention -- [ ] Test with script tags in input -- [ ] Verify output is properly escaped -- [ ] Check for reflected XSS - -## Browser Testing - -### Cross-browser Compatibility -Test in: -- [ ] Chrome -- [ ] Firefox -- [ ] Safari -- [ ] Edge -- [ ] Mobile browsers - -### Responsive Design -- [ ] Desktop (1920x1080) -- [ ] Tablet (768x1024) -- [ ] Mobile (375x667) - -## Error Handling - -### Test Error Scenarios -- [ ] Network failures -- [ ] Database connection issues -- [ ] Invalid responses -- [ ] Timeout scenarios - -### Logging -- [ ] Check error logs -- [ ] Verify debug information -- [ ] Test error reporting - -## Cleanup Testing - -### Plugin Deactivation -- [ ] Test plugin deactivation -- [ ] Verify no PHP errors -- [ ] Check database integrity - -### Plugin Uninstall -- [ ] Test uninstall process -- [ ] Verify table removal -- [ ] Check option cleanup - -## Integration Testing - -### WordPress Core -- [ ] Test with different WordPress versions -- [ ] Test with different themes -- [ ] Test with other plugins - -### REST API -- [ ] Test with different authentication methods -- [ ] Test with different content types -- [ ] Test rate limiting - -## Reporting Issues - -When reporting issues, include: -1. WordPress version -2. Plugin version -3. PHP version -4. Error messages -5. Steps to reproduce -6. Expected vs actual behavior -7. Browser/device information \ No newline at end of file diff --git a/wp-favorites-plugin/includes/class-wp-favorites-plugin.php b/wp-favorites-plugin/includes/class-wp-favorites-plugin.php index 0978699c..a7a58ec9 100644 --- a/wp-favorites-plugin/includes/class-wp-favorites-plugin.php +++ b/wp-favorites-plugin/includes/class-wp-favorites-plugin.php @@ -95,6 +95,25 @@ private function create_table() { require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); + + // Verify table was created + $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name; + if (!$table_exists) { + error_log('WP Favorites Plugin: Failed to create table ' . $table_name); + } + } + + /** + * Check if table exists and create if not + */ + private function ensure_table_exists() { + global $wpdb; + $table_name = $wpdb->prefix . 'user_favorites'; + + $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name; + if (!$table_exists) { + $this->create_table(); + } } /** @@ -143,6 +162,24 @@ public function favorite_post($request) { global $wpdb; $table_name = $wpdb->prefix . 'user_favorites'; + // Ensure table exists + $this->ensure_table_exists(); + + // Check if already favorited + $existing = $wpdb->get_var($wpdb->prepare( + "SELECT id FROM $table_name WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + )); + + if ($existing) { + return array( + 'success' => true, + 'message' => 'Post already favorited', + 'post_id' => $post_id + ); + } + $result = $wpdb->insert( $table_name, array( @@ -179,6 +216,24 @@ public function unfavorite_post($request) { global $wpdb; $table_name = $wpdb->prefix . 'user_favorites'; + // Ensure table exists + $this->ensure_table_exists(); + + // Check if already favorited + $existing = $wpdb->get_var($wpdb->prepare( + "SELECT id FROM $table_name WHERE user_id = %d AND post_id = %d", + $user_id, + $post_id + )); + + if (!$existing) { + return array( + 'success' => true, + 'message' => 'Post not favorited', + 'post_id' => $post_id + ); + } + $result = $wpdb->delete( $table_name, array( @@ -210,6 +265,9 @@ public function get_user_favorites($request) { global $wpdb; $table_name = $wpdb->prefix . 'user_favorites'; + // Ensure table exists + $this->ensure_table_exists(); + $favorites = $wpdb->get_col($wpdb->prepare( "SELECT post_id FROM $table_name WHERE user_id = %d ORDER BY created_at DESC", $user_id From b6b63c97d379e9d0b559a6753f2d237c3ca16da2 Mon Sep 17 00:00:00 2001 From: Lucas Sens Date: Fri, 25 Jul 2025 14:41:14 -0300 Subject: [PATCH 4/6] Add simple step-by-step usage guide to README --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index a4f6c256..a4ea4f4e 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,29 @@ Desenvolva um Plugin em WordPress que implemente a funcionalidade de favoritar p * PHP >= 5.6 * Orientado a objetos +## Como Usar o Plugin + +### 1. Instalação +- Faça upload do plugin para `/wp-content/plugins/` +- Ative o plugin no painel administrativo + +### 2. Adicionar Botão de Favorito +No arquivo `single.php` do seu tema, adicione: +```php + +``` + +### 3. Exibir Lista de Favoritos +Use o shortcode em qualquer página: +``` +[wp_favorites_list] +``` + +### 4. API REST +- **Favoritar**: `POST /wp-json/wp-favorites/v1/favorite` +- **Desfavoritar**: `POST /wp-json/wp-favorites/v1/unfavorite` +- **Listar**: `GET /wp-json/wp-favorites/v1/favorites` + ## Dúvidas Em caso de dúvidas, crie uma issue. From b45aa32b9084301173de5aff92a56b2fd84842b1 Mon Sep 17 00:00:00 2001 From: Lucas Sens Date: Fri, 25 Jul 2025 14:43:25 -0300 Subject: [PATCH 5/6] fix: remove .zip --- wp-favorites-plugin.zip | Bin 11820 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 wp-favorites-plugin.zip diff --git a/wp-favorites-plugin.zip b/wp-favorites-plugin.zip deleted file mode 100644 index aec266ea71e3548aa8d912d5dc84f1b5d0fb7bac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11820 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 From fc3a4862be59b69263db5d1356699bb6d10ec60e Mon Sep 17 00:00:00 2001 From: Lucas Sens Date: Fri, 25 Jul 2025 14:43:33 -0300 Subject: [PATCH 6/6] change readme --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index a4ea4f4e..dc826039 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,3 @@ Use o shortcode em qualquer página: - **Favoritar**: `POST /wp-json/wp-favorites/v1/favorite` - **Desfavoritar**: `POST /wp-json/wp-favorites/v1/unfavorite` - **Listar**: `GET /wp-json/wp-favorites/v1/favorites` - -## Dúvidas - -Em caso de dúvidas, crie uma issue.