prefix . BEEP_LIKES_TABLE; } /** * Create or migrate the likes table. * The 'post_id' column historically only stored beep IDs; we now also * use it for comment IDs (replies). 'object_type' distinguishes them. */ public static function create_table() { global $wpdb; $table = self::table_name(); $charset = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, object_type VARCHAR(20) NOT NULL DEFAULT 'post', post_id BIGINT(20) UNSIGNED NOT NULL, user_id BIGINT(20) UNSIGNED NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY object_user (object_type, post_id, user_id), KEY post_id (post_id), KEY user_id (user_id) ) $charset;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql); // Migration for installs from v1.0-1.3: add object_type column if missing. $col = $wpdb->get_var($wpdb->prepare( "SHOW COLUMNS FROM `$table` LIKE %s", 'object_type' )); if (!$col) { $wpdb->query("ALTER TABLE `$table` ADD COLUMN object_type VARCHAR(20) NOT NULL DEFAULT 'post' AFTER id"); } } public static function register_routes() { // Like a beep (post). register_rest_route('beep/v1', '/like/(?P\d+)', [ 'methods' => 'POST', 'callback' => [__CLASS__, 'toggle_post_like'], 'permission_callback' => [__CLASS__, 'can_interact'], ]); // Like a reply (comment). register_rest_route('beep/v1', '/reply-like/(?P\d+)', [ 'methods' => 'POST', 'callback' => [__CLASS__, 'toggle_reply_like'], 'permission_callback' => [__CLASS__, 'can_interact'], ]); } public static function can_interact() { return is_user_logged_in() && !beep_user_is_banned(get_current_user_id()); } public static function toggle_post_like($request) { $id = (int) $request['id']; if (get_post_type($id) !== Beep_CPT::POST_TYPE) { return new WP_Error('beep_not_a_beep', 'Not a beep.', ['status' => 400]); } return rest_ensure_response(self::toggle($id, 'post')); } public static function toggle_reply_like($request) { $id = (int) $request['id']; $c = get_comment($id); if (!$c || get_post_type($c->comment_post_ID) !== Beep_CPT::POST_TYPE) { return new WP_Error('beep_not_a_reply', 'Not a reply.', ['status' => 400]); } return rest_ensure_response(self::toggle($id, 'reply')); } /** * Toggle a like for current user on the given object. * @return array {liked: bool, count: int} */ public static function toggle($object_id, $object_type) { global $wpdb; $table = self::table_name(); $user_id = get_current_user_id(); $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table WHERE object_type = %s AND post_id = %d AND user_id = %d", $object_type, $object_id, $user_id )); if ($existing) { $wpdb->delete($table, ['id' => $existing], ['%d']); $liked = false; } else { $wpdb->insert($table, [ 'object_type' => $object_type, 'post_id' => $object_id, 'user_id' => $user_id, 'created_at' => current_time('mysql'), ], ['%s', '%d', '%d', '%s']); $liked = true; } return [ 'liked' => $liked, 'count' => self::get_count($object_id, $object_type), ]; } public static function get_count($object_id, $object_type = 'post') { global $wpdb; $table = self::table_name(); return (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE object_type = %s AND post_id = %d", $object_type, $object_id )); } public static function user_has_liked($object_id, $object_type = 'post', $user_id = null) { global $wpdb; if (!$user_id) { $user_id = get_current_user_id(); } if (!$user_id) { return false; } $table = self::table_name(); return (bool) $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table WHERE object_type = %s AND post_id = %d AND user_id = %d", $object_type, $object_id, $user_id )); } }