/** * REST API: WP_REST_Comments_Controller class * * @package WordPress * @subpackage REST_API * @since 4.7.0 */ /** * Core controller used to access comments via the REST API. * * @since 4.7.0 * * @see WP_REST_Controller */ class WP_REST_Comments_Controller extends WP_REST_Controller { /** * Instance of a comment meta fields object. * * @since 4.7.0 * @var WP_REST_Comment_Meta_Fields */ protected $meta; /** * Constructor. * * @since 4.7.0 */ public function __construct() { $this->namespace = 'wp/v2'; $this->rest_base = 'comments'; $this->meta = new WP_REST_Comment_Meta_Fields(); } /** * Registers the routes for comments. * * @since 4.7.0 * * @see register_rest_route() */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the comment.' ), 'type' => 'integer', ), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 'password' => array( 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), 'type' => 'string', ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'update_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Whether to bypass Trash and force deletion.' ), ), 'password' => array( 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), 'type' => 'string', ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Checks if a given request has access to read comments. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, error object otherwise. */ public function get_items_permissions_check( $request ) { if ( ! empty( $request['post'] ) ) { foreach ( (array) $request['post'] as $post_id ) { $post = get_post( $post_id ); if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) ); } } } if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! current_user_can( 'edit_posts' ) ) { $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' ); $forbidden_params = array(); foreach ( $protected_params as $param ) { if ( 'status' === $param ) { if ( 'approve' !== $request[ $param ] ) { $forbidden_params[] = $param; } } elseif ( 'type' === $param ) { if ( 'comment' !== $request[ $param ] ) { $forbidden_params[] = $param; } } elseif ( ! empty( $request[ $param ] ) ) { $forbidden_params[] = $param; } } if ( ! empty( $forbidden_params ) ) { return new WP_Error( 'rest_forbidden_param', /* translators: %s: List of forbidden parameters. */ sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) ); } } return true; } /** * Retrieves a list of comment items. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function get_items( $request ) { // Retrieve the list of registered collection query parameters. $registered = $this->get_collection_params(); /* * This array defines mappings between public API query parameters whose * values are accepted as-passed, and their internal WP_Query parameter * name equivalents (some are the same). Only values which are also * present in $registered will be set. */ $parameter_mappings = array( 'author' => 'author__in', 'author_email' => 'author_email', 'author_exclude' => 'author__not_in', 'exclude' => 'comment__not_in', 'include' => 'comment__in', 'offset' => 'offset', 'order' => 'order', 'parent' => 'parent__in', 'parent_exclude' => 'parent__not_in', 'per_page' => 'number', 'post' => 'post__in', 'search' => 'search', 'status' => 'status', 'type' => 'type', ); $prepared_args = array(); /* * For each known parameter which is both registered and present in the request, * set the parameter's value on the query $prepared_args. */ foreach ( $parameter_mappings as $api_param => $wp_param ) { if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { $prepared_args[ $wp_param ] = $request[ $api_param ]; } } // Ensure certain parameter values default to empty strings. foreach ( array( 'author_email', 'search' ) as $param ) { if ( ! isset( $prepared_args[ $param ] ) ) { $prepared_args[ $param ] = ''; } } if ( isset( $registered['orderby'] ) ) { $prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] ); } $prepared_args['no_found_rows'] = false; $prepared_args['update_comment_post_cache'] = true; $prepared_args['date_query'] = array(); // Set before into date query. Date query must be specified as an array of an array. if ( isset( $registered['before'], $request['before'] ) ) { $prepared_args['date_query'][0]['before'] = $request['before']; } // Set after into date query. Date query must be specified as an array of an array. if ( isset( $registered['after'], $request['after'] ) ) { $prepared_args['date_query'][0]['after'] = $request['after']; } if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) { $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); } /** * Filters WP_Comment_Query arguments when querying comments via the REST API. * * @since 4.7.0 * * @link https://developer.wordpress.org/reference/classes/wp_comment_query/ * * @param array $prepared_args Array of arguments for WP_Comment_Query. * @param WP_REST_Request $request The REST API request. */ $prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request ); $query = new WP_Comment_Query(); $query_result = $query->query( $prepared_args ); $comments = array(); foreach ( $query_result as $comment ) { if ( ! $this->check_read_permission( $comment, $request ) ) { continue; } $data = $this->prepare_item_for_response( $comment, $request ); $comments[] = $this->prepare_response_for_collection( $data ); } $total_comments = (int) $query->found_comments; $max_pages = (int) $query->max_num_pages; if ( $total_comments < 1 ) { // Out-of-bounds, run the query again without LIMIT for total count. unset( $prepared_args['number'], $prepared_args['offset'] ); $query = new WP_Comment_Query(); $prepared_args['count'] = true; $prepared_args['orderby'] = 'none'; $total_comments = $query->query( $prepared_args ); $max_pages = (int) ceil( $total_comments / $request['per_page'] ); } $response = rest_ensure_response( $comments ); $response->header( 'X-WP-Total', $total_comments ); $response->header( 'X-WP-TotalPages', $max_pages ); $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); if ( $request['page'] > 1 ) { $prev_page = $request['page'] - 1; if ( $prev_page > $max_pages ) { $prev_page = $max_pages; } $prev_link = add_query_arg( 'page', $prev_page, $base ); $response->link_header( 'prev', $prev_link ); } if ( $max_pages > $request['page'] ) { $next_page = $request['page'] + 1; $next_link = add_query_arg( 'page', $next_page, $base ); $response->link_header( 'next', $next_link ); } return $response; } /** * Get the comment, if the ID is valid. * * @since 4.7.2 * * @param int $id Supplied ID. * @return WP_Comment|WP_Error Comment object if ID is valid, WP_Error otherwise. */ protected function get_comment( $id ) { $error = new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) ); if ( (int) $id <= 0 ) { return $error; } $id = (int) $id; $comment = get_comment( $id ); if ( empty( $comment ) ) { return $error; } if ( ! empty( $comment->comment_post_ID ) ) { $post = get_post( (int) $comment->comment_post_ID ); if ( empty( $post ) ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); } } return $comment; } /** * Checks if a given request has access to read the comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access for the item, error object otherwise. */ public function get_item_permissions_check( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); } $post = get_post( $comment->comment_post_ID ); if ( ! $this->check_read_permission( $comment, $request ) ) { return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( $post && ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Retrieves a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function get_item( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } $data = $this->prepare_item_for_response( $comment, $request ); $response = rest_ensure_response( $data ); return $response; } /** * Checks if a given request has access to create a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to create items, error object otherwise. */ public function create_item_permissions_check( $request ) { if ( ! is_user_logged_in() ) { if ( get_option( 'comment_registration' ) ) { return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); } /** * Filters whether comments can be created via the REST API without authentication. * * Enables creating comments for anonymous users. * * @since 4.7.0 * * @param bool $allow_anonymous Whether to allow anonymous comments to * be created. Default `false`. * @param WP_REST_Request $request Request used to generate the * response. */ $allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request ); if ( ! $allow_anonymous ) { return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); } } // Limit who can set comment `author`, `author_ip` or `status` to anything other than the default. if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_comment_invalid_author', /* translators: %s: Request parameter. */ sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ), array( 'status' => rest_authorization_required_code() ) ); } if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) { if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) { return new WP_Error( 'rest_comment_invalid_author_ip', /* translators: %s: Request parameter. */ sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ), array( 'status' => rest_authorization_required_code() ) ); } } if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) { return new WP_Error( 'rest_comment_invalid_status', /* translators: %s: Request parameter. */ sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'status' ), array( 'status' => rest_authorization_required_code() ) ); } if ( empty( $request['post'] ) ) { return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) ); } $post = get_post( (int) $request['post'] ); if ( ! $post ) { return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) ); } if ( 'draft' === $post->post_status ) { return new WP_Error( 'rest_comment_draft_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) ); } if ( 'trash' === $post->post_status ) { return new WP_Error( 'rest_comment_trash_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) ); } if ( ! $this->check_read_post_permission( $post, $request ) ) { return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } if ( ! comments_open( $post->ID ) ) { return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed for this item.' ), array( 'status' => 403 ) ); } return true; } /** * Creates a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function create_item( $request ) { if ( ! empty( $request['id'] ) ) { return new WP_Error( 'rest_comment_exists', __( 'Cannot create existing comment.' ), array( 'status' => 400 ) ); } // Do not allow comments to be created with a non-default type. if ( ! empty( $request['type'] ) && 'comment' !== $request['type'] ) { return new WP_Error( 'rest_invalid_comment_type', __( 'Cannot create a comment with that type.' ), array( 'status' => 400 ) ); } $prepared_comment = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_comment ) ) { return $prepared_comment; } $prepared_comment['comment_type'] = 'comment'; if ( ! isset( $prepared_comment['comment_content'] ) ) { $prepared_comment['comment_content'] = ''; } if ( ! $this->check_is_comment_content_allowed( $prepared_comment ) ) { return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) ); } // Setting remaining values before wp_insert_comment so we can use wp_allow_comment(). if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) { $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true ); } // Set author data if the user's logged in. $missing_author = empty( $prepared_comment['user_id'] ) && empty( $prepared_comment['comment_author'] ) && empty( $prepared_comment['comment_author_email'] ) && empty( $prepared_comment['comment_author_url'] ); if ( is_user_logged_in() && $missing_author ) { $user = wp_get_current_user(); $prepared_comment['user_id'] = $user->ID; $prepared_comment['comment_author'] = $user->display_name; $prepared_comment['comment_author_email'] = $user->user_email; $prepared_comment['comment_author_url'] = $user->user_url; } // Honor the discussion setting that requires a name and email address of the comment author. if ( get_option( 'require_name_email' ) ) { if ( empty( $prepared_comment['comment_author'] ) || empty( $prepared_comment['comment_author_email'] ) ) { return new WP_Error( 'rest_comment_author_data_required', __( 'Creating a comment requires valid author name and email values.' ), array( 'status' => 400 ) ); } } if ( ! isset( $prepared_comment['comment_author_email'] ) ) { $prepared_comment['comment_author_email'] = ''; } if ( ! isset( $prepared_comment['comment_author_url'] ) ) { $prepared_comment['comment_author_url'] = ''; } if ( ! isset( $prepared_comment['comment_agent'] ) ) { $prepared_comment['comment_agent'] = ''; } $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_comment ); if ( is_wp_error( $check_comment_lengths ) ) { $error_code = $check_comment_lengths->get_error_code(); return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) ); } $prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment, true ); if ( is_wp_error( $prepared_comment['comment_approved'] ) ) { $error_code = $prepared_comment['comment_approved']->get_error_code(); $error_message = $prepared_comment['comment_approved']->get_error_message(); if ( 'comment_duplicate' === $error_code ) { return new WP_Error( $error_code, $error_message, array( 'status' => 409 ) ); } if ( 'comment_flood' === $error_code ) { return new WP_Error( $error_code, $error_message, array( 'status' => 400 ) ); } return $prepared_comment['comment_approved']; } /** * Filters a comment before it is inserted via the REST API. * * Allows modification of the comment right before it is inserted via wp_insert_comment(). * Returning a WP_Error value from the filter will short-circuit insertion and allow * skipping further processing. * * @since 4.7.0 * @since 4.8.0 `$prepared_comment` can now be a WP_Error to short-circuit insertion. * * @param array|WP_Error $prepared_comment The prepared comment data for wp_insert_comment(). * @param WP_REST_Request $request Request used to insert the comment. */ $prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request ); if ( is_wp_error( $prepared_comment ) ) { return $prepared_comment; } $comment_id = wp_insert_comment( wp_filter_comment( wp_slash( (array) $prepared_comment ) ) ); if ( ! $comment_id ) { return new WP_Error( 'rest_comment_failed_create', __( 'Creating comment failed.' ), array( 'status' => 500 ) ); } if ( isset( $request['status'] ) ) { $this->handle_status_param( $request['status'], $comment_id ); } $comment = get_comment( $comment_id ); /** * Fires after a comment is created or updated via the REST API. * * @since 4.7.0 * * @param WP_Comment $comment Inserted or updated comment object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a comment, false * when updating. */ do_action( 'rest_insert_comment', $comment, $request, true ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $comment_id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $fields_update = $this->update_additional_fields_for_object( $comment, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view'; $request->set_param( 'context', $context ); /** * Fires completely after a comment is created or updated via the REST API. * * @since 5.0.0 * * @param WP_Comment $comment Inserted or updated comment object. * @param WP_REST_Request $request Request object. * @param bool $creating True when creating a comment, false * when updating. */ do_action( 'rest_after_insert_comment', $comment, $request, true ); $response = $this->prepare_item_for_response( $comment, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) ); return $response; } /** * Checks if a given REST request has access to update a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to update the item, error object otherwise. */ public function update_item_permissions_check( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } if ( ! $this->check_edit_permission( $comment ) ) { return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Updates a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function update_item( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } $id = $comment->comment_ID; if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) { return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you are not allowed to change the comment type.' ), array( 'status' => 404 ) ); } $prepared_args = $this->prepare_item_for_database( $request ); if ( is_wp_error( $prepared_args ) ) { return $prepared_args; } if ( ! empty( $prepared_args['comment_post_ID'] ) ) { $post = get_post( $prepared_args['comment_post_ID'] ); if ( empty( $post ) ) { return new WP_Error( 'rest_comment_invalid_post_id', __( 'Invalid post ID.' ), array( 'status' => 403 ) ); } } if ( empty( $prepared_args ) && isset( $request['status'] ) ) { // Only the comment status is being changed. $change = $this->handle_status_param( $request['status'], $id ); if ( ! $change ) { return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment status failed.' ), array( 'status' => 500 ) ); } } elseif ( ! empty( $prepared_args ) ) { if ( is_wp_error( $prepared_args ) ) { return $prepared_args; } if ( isset( $prepared_args['comment_content'] ) && empty( $prepared_args['comment_content'] ) ) { return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) ); } $prepared_args['comment_ID'] = $id; $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_args ); if ( is_wp_error( $check_comment_lengths ) ) { $error_code = $check_comment_lengths->get_error_code(); return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) ); } $updated = wp_update_comment( wp_slash( (array) $prepared_args ), true ); if ( is_wp_error( $updated ) ) { return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment failed.' ), array( 'status' => 500 ) ); } if ( isset( $request['status'] ) ) { $this->handle_status_param( $request['status'], $id ); } } $comment = get_comment( $id ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ do_action( 'rest_insert_comment', $comment, $request, false ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $id ); if ( is_wp_error( $meta_update ) ) { return $meta_update; } } $fields_update = $this->update_additional_fields_for_object( $comment, $request ); if ( is_wp_error( $fields_update ) ) { return $fields_update; } $request->set_param( 'context', 'edit' ); /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ do_action( 'rest_after_insert_comment', $comment, $request, false ); $response = $this->prepare_item_for_response( $comment, $request ); return rest_ensure_response( $response ); } /** * Checks if a given request has access to delete a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has access to delete the item, error object otherwise. */ public function delete_item_permissions_check( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } if ( ! $this->check_edit_permission( $comment ) ) { return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this comment.' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Deletes a comment. * * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or error object on failure. */ public function delete_item( $request ) { $comment = $this->get_comment( $request['id'] ); if ( is_wp_error( $comment ) ) { return $comment; } $force = isset( $request['force'] ) ? (bool) $request['force'] : false; /** * Filters whether a comment can be trashed via the REST API. * * Return false to disable trash support for the comment. * * @since 4.7.0 * * @param bool $supports_trash Whether the comment supports trashing. * @param WP_Comment $comment The comment object being considered for trashing support. */ $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment ); $request->set_param( 'context', 'edit' ); if ( $force ) { $previous = $this->prepare_item_for_response( $comment, $request ); $result = wp_delete_comment( $comment->comment_ID, true ); $response = new WP_REST_Response(); $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data(), ) ); } else { // If this type doesn't support trashing, error out. if ( ! $supports_trash ) { return new WP_Error( 'rest_trash_not_supported', /* translators: %s: force=true */ sprintf( __( "The comment does not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); } if ( 'trash' === $comment->comment_approved ) { return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.' ), array( 'status' => 410 ) ); } $result = wp_trash_comment( $comment->comment_ID ); $comment = get_comment( $comment->comment_ID ); $response = $this->prepare_item_for_response( $comment, $request ); } if ( ! $result ) { return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) ); } /** * Fires after a comment is deleted via the REST API. * * @since 4.7.0 * * @param WP_Comment $comment The deleted comment data. * @param WP_REST_Response $response The response returned from the API. * @param WP_REST_Request $request The request sent to the API. */ do_action( 'rest_delete_comment', $comment, $response, $request ); return $response; } /** * Prepares a single comment output for response. * * @since 4.7.0 * @since 5.9.0 Renamed `$comment` to `$item` to match parent class for PHP 8 named parameter support. * * @param WP_Comment $item Comment object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. $comment = $item; $fields = $this->get_fields_for_response( $request ); $data = array(); if ( in_array( 'id', $fields, true ) ) { $data['id'] = (int) $comment->comment_ID; } if ( in_array( 'post', $fields, true ) ) { $data['post'] = (int) $comment->comment_post_ID; } if ( in_array( 'parent', $fields, true ) ) { $data['parent'] = (int) $comment->comment_parent; } if ( in_array( 'author', $fields, true ) ) { $data['author'] = (int) $comment->user_id; } if ( in_array( 'author_name', $fields, true ) ) { $data['author_name'] = $comment->comment_author; } if ( in_array( 'author_email', $fields, true ) ) { $data['author_email'] = $comment->comment_author_email; } if ( in_array( 'author_url', $fields, true ) ) { $data['author_url'] = $comment->comment_author_url; } if ( in_array( 'author_ip', $fields, true ) ) { $data['author_ip'] = $comment->comment_author_IP; } if ( in_array( 'author_user_agent', $fields, true ) ) { $data['author_user_agent'] = $comment->comment_agent; } if ( in_array( 'date', $fields, true ) ) { $data['date'] = mysql_to_rfc3339( $comment->comment_date ); } if ( in_array( 'date_gmt', $fields, true ) ) { $data['date_gmt'] = mysql_to_rfc3339( $comment->comment_date_gmt ); } if ( in_array( 'content', $fields, true ) ) { $data['content'] = array( /** This filter is documented in wp-includes/comment-template.php */ 'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment, array() ), 'raw' => $comment->comment_content, ); } if ( in_array( 'link', $fields, true ) ) { $data['link'] = get_comment_link( $comment ); } if ( in_array( 'status', $fields, true ) ) { $data['status'] = $this->prepare_status_response( $comment->comment_approved ); } if ( in_array( 'type', $fields, true ) ) { $data['type'] = get_comment_type( $comment->comment_ID ); } if ( in_array( 'author_avatar_urls', $fields, true ) ) { $data['author_avatar_urls'] = rest_get_avatar_urls( $comment ); } if ( in_array( 'meta', $fields, true ) ) { $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request ); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { $response->add_links( $this->prepare_links( $comment ) ); } /** * Filters a comment returned from the REST API. * * Allows modification of the comment right before it is returned. * * @since 4.7.0 * * @param WP_REST_Response $response The response object. * @param WP_Comment $comment The original comment object. * @param WP_REST_Request $request Request used to generate the response. */ return apply_filters( 'rest_prepare_comment', $response, $comment, $request ); } /** * Prepares links for the request. * * @since 4.7.0 * * @param WP_Comment $comment Comment object. * @return array Links for the given comment. */ protected function prepare_links( $comment ) { $links = array( 'self' => array( 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ), ), 'collection' => array( 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), ), ); if ( 0 !== (int) $comment->user_id ) { $links['author'] = array( 'href' => rest_url( 'wp/v2/users/' . $comment->user_id ), 'embeddable' => true, ); } if ( 0 !== (int) $comment->comment_post_ID ) { $post = get_post( $comment->comment_post_ID ); $post_route = rest_get_route_for_post( $post ); if ( ! empty( $post->ID ) && $post_route ) { $links['up'] = array( 'href' => rest_url( $post_route ), 'embeddable' => true, 'post_type' => $post->post_type, ); } } if ( 0 !== (int) $comment->comment_parent ) { $links['in-reply-to'] = array( 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ), 'embeddable' => true, ); } // Only grab one comment to verify the comment has children. $comment_children = $comment->get_children( array( 'count' => true, 'orderby' => 'none', ) ); if ( ! empty( $comment_children ) ) { $args = array( 'parent' => $comment->comment_ID, ); $rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) ); $links['children'] = array( 'href' => $rest_url, 'embeddable' => true, ); } return $links; } /** * Prepends internal property prefix to query parameters to match our response fields. * * @since 4.7.0 * * @param string $query_param Query parameter. * @return string The normalized query parameter. */ protected function normalize_query_param( $query_param ) { $prefix = 'comment_'; switch ( $query_param ) { case 'id': $normalized = $prefix . 'ID'; break; case 'post': $normalized = $prefix . 'post_ID'; break; case 'parent': $normalized = $prefix . 'parent'; break; case 'include': $normalized = 'comment__in'; break; default: $normalized = $prefix . $query_param; break; } return $normalized; } /** * Checks comment_approved to set comment status for single comment output. * * @since 4.7.0 * * @param string|int $comment_approved comment status. * @return string Comment status. */ protected function prepare_status_response( $comment_approved ) { switch ( $comment_approved ) { case 'hold': case '0': $status = 'hold'; break; case 'approve': case '1': $status = 'approved'; break; case 'spam': case 'trash': default: $status = $comment_approved; break; } return $status; } /** * Prepares a single comment to be inserted into the database. * * @since 4.7.0 * * @param WP_REST_Request $request Request object. * @return array|WP_Error Prepared comment, otherwise WP_Error object. */ protected function prepare_item_for_database( $request ) { $prepared_comment = array(); /* * Allow the comment_content to be set via the 'content' or * the 'content.raw' properties of the Request object. */ if ( isset( $request['content'] ) && is_string( $request['content'] ) ) { $prepared_comment['comment_content'] = trim( $request['content'] ); } elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) { $prepared_comment['comment_content'] = trim( $request['content']['raw'] ); } if ( isset( $request['post'] ) ) { $prepared_comment['comment_post_ID'] = (int) $request['post']; } if ( isset( $request['parent'] ) ) { $prepared_comment['comment_parent'] = $request['parent']; } if ( isset( $request['author'] ) ) { $user = new WP_User( $request['author'] ); if ( $user->exists() ) { $prepared_comment['user_id'] = $user->ID; $prepared_comment['comment_author'] = $user->display_name; $prepared_comment['comment_author_email'] = $user->user_email; $prepared_comment['comment_author_url'] = $user->user_url; } else { return new WP_Error( 'rest_comment_author_invalid', __( 'Invalid comment author ID.' ), array( 'status' => 400 ) ); } } if ( isset( $request['author_name'] ) ) { $prepared_comment['comment_author'] = $request['author_name']; } if ( isset( $request['author_email'] ) ) { $prepared_comment['comment_author_email'] = $request['author_email']; } if ( isset( $request['author_url'] ) ) { $prepared_comment['comment_author_url'] = $request['author_url']; } if ( isset( $request['author_ip'] ) && current_user_can( 'moderate_comments' ) ) { $prepared_comment['comment_author_IP'] = $request['author_ip']; } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) { $prepared_comment['comment_author_IP'] = $_SERVER['REMOTE_ADDR']; } else { $prepared_comment['comment_author_IP'] = '127.0.0.1'; } if ( ! empty( $request['author_user_agent'] ) ) { $prepared_comment['comment_agent'] = $request['author_user_agent']; } elseif ( $request->get_header( 'user_agent' ) ) { $prepared_comment['comment_agent'] = $request->get_header( 'user_agent' ); } if ( ! empty( $request['date'] ) ) { $date_data = rest_get_date_with_gmt( $request['date'] ); if ( ! empty( $date_data ) ) { list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; } } elseif ( ! empty( $request['date_gmt'] ) ) { $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); if ( ! empty( $date_data ) ) { list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; } } /** * Filters a comment added via the REST API after it is prepared for insertion into the database. * * Allows modification of the comment right after it is prepared for the database. * * @since 4.7.0 * * @param array $prepared_comment The prepared comment data for `wp_insert_comment`. * @param WP_REST_Request $request The current request. */ return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request ); } /** * Retrieves the comment's schema, conforming to JSON Schema. * * @since 4.7.0 * * @return array */ public function get_item_schema() { if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'comment', 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Unique identifier for the comment.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'author' => array( 'description' => __( 'The ID of the user object, if author was a user.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ), 'author_email' => array( 'description' => __( 'Email address for the comment author.' ), 'type' => 'string', 'format' => 'email', 'context' => array( 'edit' ), 'arg_options' => array( 'sanitize_callback' => array( $this, 'check_comment_author_email' ), 'validate_callback' => null, // Skip built-in validation of 'email'. ), ), 'author_ip' => array( 'description' => __( 'IP address for the comment author.' ), 'type' => 'string', 'format' => 'ip', 'context' => array( 'edit' ), ), 'author_name' => array( 'description' => __( 'Display name for the comment author.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'author_url' => array( 'description' => __( 'URL for the comment author.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), ), 'author_user_agent' => array( 'description' => __( 'User agent for the comment author.' ), 'type' => 'string', 'context' => array( 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_text_field', ), ), 'content' => array( 'description' => __( 'The content for the comment.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). ), 'properties' => array( 'raw' => array( 'description' => __( 'Content for the comment, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML content for the comment, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ), 'date' => array( 'description' => __( "The date the comment was published, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit', 'embed' ), ), 'date_gmt' => array( 'description' => __( 'The date the comment was published, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'link' => array( 'description' => __( 'URL to the comment.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'parent' => array( 'description' => __( 'The ID for the parent of the comment.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'default' => 0, ), 'post' => array( 'description' => __( 'The ID of the associated post object.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'default' => 0, ), 'status' => array( 'description' => __( 'State of the comment.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_key', ), ), 'type' => array( 'description' => __( 'Type of the comment.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); if ( get_option( 'show_avatars' ) ) { $avatar_properties = array(); $avatar_sizes = rest_get_avatar_sizes(); foreach ( $avatar_sizes as $size ) { $avatar_properties[ $size ] = array( /* translators: %d: Avatar image size in pixels. */ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'embed', 'view', 'edit' ), ); } $schema['properties']['author_avatar_urls'] = array( 'description' => __( 'Avatar URLs for the comment author.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, 'properties' => $avatar_properties, ); } $schema['properties']['meta'] = $this->meta->get_field_schema(); $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); } /** * Retrieves the query params for collections. * * @since 4.7.0 * * @return array Comments collection parameters. */ public function get_collection_params() { $query_params = parent::get_collection_params(); $query_params['context']['default'] = 'view'; $query_params['after'] = array( 'description' => __( 'Limit response to comments published after a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); $query_params['author'] = array( 'description' => __( 'Limit result set to comments assigned to specific user IDs. Requires authorization.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['author_exclude'] = array( 'description' => __( 'Ensure result set excludes comments assigned to specific user IDs. Requires authorization.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['author_email'] = array( 'default' => null, 'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ), 'format' => 'email', 'type' => 'string', ); $query_params['before'] = array( 'description' => __( 'Limit response to comments published before a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', ); $query_params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['include'] = array( 'description' => __( 'Limit result set to specific IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), ); $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', ); $query_params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc', ), ); $query_params['orderby'] = array( 'description' => __( 'Sort collection by comment attribute.' ), 'type' => 'string', 'default' => 'date_gmt', 'enum' => array( 'date', 'date_gmt', 'id', 'include', 'post', 'parent', 'type', ), ); $query_params['parent'] = array( 'default' => array(), 'description' => __( 'Limit result set to comments of specific parent IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['parent_exclude'] = array( 'default' => array(), 'description' => __( 'Ensure result set excludes specific parent IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['post'] = array( 'default' => array(), 'description' => __( 'Limit result set to comments assigned to specific post IDs.' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), ); $query_params['status'] = array( 'default' => 'approve', 'description' => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ), 'sanitize_callback' => 'sanitize_key', 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', ); $query_params['type'] = array( 'default' => 'comment', 'description' => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ), 'sanitize_callback' => 'sanitize_key', 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', ); $query_params['password'] = array( 'description' => __( 'The password for the post if it is password protected.' ), 'type' => 'string', ); /** * Filters REST API collection parameters for the comments controller. * * This filter registers the collection parameter, but does not map the * collection parameter to an internal WP_Comment_Query parameter. Use the * `rest_comment_query` filter to set WP_Comment_Query parameters. * * @since 4.7.0 * * @param array $query_params JSON Schema-formatted collection parameters. */ return apply_filters( 'rest_comment_collection_params', $query_params ); } /** * Sets the comment_status of a given comment object when creating or updating a comment. * * @since 4.7.0 * * @param string|int $new_status New comment status. * @param int $comment_id Comment ID. * @return bool Whether the status was changed. */ protected function handle_status_param( $new_status, $comment_id ) { $old_status = wp_get_comment_status( $comment_id ); if ( $new_status === $old_status ) { return false; } switch ( $new_status ) { case 'approved': case 'approve': case '1': $changed = wp_set_comment_status( $comment_id, 'approve' ); break; case 'hold': case '0': $changed = wp_set_comment_status( $comment_id, 'hold' ); break; case 'spam': $changed = wp_spam_comment( $comment_id ); break; case 'unspam': $changed = wp_unspam_comment( $comment_id ); break; case 'trash': $changed = wp_trash_comment( $comment_id ); break; case 'untrash': $changed = wp_untrash_comment( $comment_id ); break; default: $changed = false; break; } return $changed; } /** * Checks if the post can be read. * * Correctly handles posts with the inherit status. * * @since 4.7.0 * * @param WP_Post $post Post object. * @param WP_REST_Request $request Request data to check. * @return bool Whether post can be read. */ protected function check_read_post_permission( $post, $request ) { $post_type = get_post_type_object( $post->post_type ); // Return false if custom post type doesn't exist if ( ! $post_type ) { return false; } $posts_controller = $post_type->get_rest_controller(); /* * Ensure the posts controller is specifically a WP_REST_Posts_Controller instance * before using methods specific to that controller. */ if ( ! $posts_controller instanceof WP_REST_Posts_Controller ) { $posts_controller = new WP_REST_Posts_Controller( $post->post_type ); } $has_password_filter = false; // Only check password if a specific post was queried for or a single comment $requested_post = ! empty( $request['post'] ) && ( ! is_array( $request['post'] ) || 1 === count( $request['post'] ) ); $requested_comment = ! empty( $request['id'] ); if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) { add_filter( 'post_password_required', '__return_false' ); $has_password_filter = true; } if ( post_password_required( $post ) ) { $result = current_user_can( 'edit_post', $post->ID ); } else { $result = $posts_controller->check_read_permission( $post ); } if ( $has_password_filter ) { remove_filter( 'post_password_required', '__return_false' ); } return $result; } /** * Checks if the comment can be read. * * @since 4.7.0 * * @param WP_Comment $comment Comment object. * @param WP_REST_Request $request Request data to check. * @return bool Whether the comment can be read. */ protected function check_read_permission( $comment, $request ) { if ( ! empty( $comment->comment_post_ID ) ) { $post = get_post( $comment->comment_post_ID ); if ( $post ) { if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) { return true; } } } if ( 0 === get_current_user_id() ) { return false; } if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) { return false; } if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) { return true; } return current_user_can( 'edit_comment', $comment->comment_ID ); } /** * Checks if a comment can be edited or deleted. * * @since 4.7.0 * * @param WP_Comment $comment Comment object. * @return bool Whether the comment can be edited or deleted. */ protected function check_edit_permission( $comment ) { if ( 0 === (int) get_current_user_id() ) { return false; } if ( current_user_can( 'moderate_comments' ) ) { return true; } return current_user_can( 'edit_comment', $comment->comment_ID ); } /** * Checks a comment author email for validity. * * Accepts either a valid email address or empty string as a valid comment * author email address. Setting the comment author email to an empty * string is allowed when a comment is being updated. * * @since 4.7.0 * * @param string $value Author email value submitted. * @param WP_REST_Request $request Full details about the request. * @param string $param The parameter name. * @return string|WP_Error The sanitized email address, if valid, * otherwise an error. */ public function check_comment_author_email( $value, $request, $param ) { $email = (string) $value; if ( empty( $email ) ) { return $email; } $check_email = rest_validate_request_arg( $email, $request, $param ); if ( is_wp_error( $check_email ) ) { return $check_email; } return $email; } /** * If empty comments are not allowed, checks if the provided comment content is not empty. * * @since 5.6.0 * * @param array $prepared_comment The prepared comment data. * @return bool True if the content is allowed, false otherwise. */ protected function check_is_comment_content_allowed( $prepared_comment ) { $check = wp_parse_args( $prepared_comment, array( 'comment_post_ID' => 0, 'comment_author' => null, 'comment_author_email' => null, 'comment_author_url' => null, 'comment_parent' => 0, 'user_id' => 0, ) ); /** This filter is documented in wp-includes/comment.php */ $allow_empty = apply_filters( 'allow_empty_comment', false, $check ); if ( $allow_empty ) { return true; } /* * Do not allow a comment to be created with missing or empty * comment_content. See wp_handle_comment_submission(). */ return '' !== $check['comment_content']; } } Why red tiger slots Is A Tactic Not A Strategy - Eluxhire

Eluxhire

Why red tiger slots Is A Tactic Not A Strategy

Best Online Casino Bonuses in the UK

Free spins are not the only no deposit bonus you can get at UK casinos. By offering a 100% deposit match up to £100, Casumo effectively doubles your initial bankroll, giving you ample funds to properly test their gaming lobby. If anything, some UK online casinos know how to bring the goodies. When compiling this PlayOJO casino review we found the website a dream to use. 2026 updates made verification quicker — usually under 24 hours. Payment Methods and Payout Times: 4. All casino game providers also have to apply for a licence with the UK Gambling Commission. Como editor de un portal especializado en juegos de azar. In truth, the range of promotions looks much the same as what you’d find at the big corporate brands. Others have already built a reputation outside the UK and are looking to expand their casino into the huge UK casino market. Trademark ™ 2026 PlayOJO. Free casino card games typically have more depth, in terms of rules, gameplay, and strategy, than Slots games do. Online casinos are no longer only available on desktop. If you’re a fan of the game,make sure to check out one of the top casinos on our list for a mix of specialty games alongsidethe classics. Free spins, gratissnurr, gratisspinn – ja, kärt barn har många namn brukar man säga, men oavsett vilken av dessa termer som casinot använder sig av är innebörden densamma. Welcome to Hippodrome Casino, a notable online UK casino with big prize drops, lots of games, and plenty of reasons to spend your time and money there. Opt in and wager £20 or more on selected games within 14 days of registration. The sites with a lower RTP will probably pay out bigger amounts, but your chances of winning are limited. Bei Lemon Casino nutze ich fürs Echtgeld Banking lieber E Wallet oder Bank, weil Auszahlungen dort klar sind. Claim within 7 days from reg. Activate bonus in your casino account.

Double Your Profit With These 5 Tips on red tiger slots

Best New Casino Sites in the UK and What They Offer

Slot Rivals: Win £50 by playing free tournaments. According to the statutory British gambling regulator, the Gambling Commission, “Safe and responsible gambling comes from an industry that takes care of its customers, customers who are empowered with the knowledge to manage their gambling and a regulator that ensures the consumer is at the heart of everything we do. A good deal lets you play slots online a bit longer, hunt features, and try fresh mechanics without rushing. Understanding different games, managing your bankroll, and staying informed through resources like World Casino News and Gaming Industry News can help you make the most of your casino adventures. Get involved and best of luck. It is operated by Jumpman Gaming Limited and runs on the Jumpman Gaming platform, which powers a large network of casino and bingo brands in the UK. Other Casino Games: 4. Built on the Jumpman Gaming platform, the site is part of the Jumpman Slots network, which powers dozens of online casino brands across the UK. The top prize of 12,500x offers better maximum returns than other well known red tiger slots titles like Dead or Alive 12,000x and Wild West Gold Megaways 5,000x. Don’t be the last to know about the latest, exclusive, and top bonuses.

The Business Of red tiger slots

Best Online Casinos 2025 — UK Casino Sites

However, fiat transactions are usually slower and come with higher fees, which is why most players prefer cryptocurrency for faster, cheaper, and more efficient casino payments. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. There are countless casinos online, and UK players are flooded with new casino sites all the time. If something goes wrong, such as a delayed withdrawal, the casino must acknowledge your complaint and respond within eight weeks. For you as a Dutch player, the ones below are more important than any other. It’s built for quick claiming and casual grinding. WR of 10x Bonus amount and Free Spin winnings amount only Slots count within 30 days. The best online casinos should be able to offer a varied selection of quality casino games, from slots and table games to live dealers and bingo. However, for those of you who do not know what the UK Gambling Commission do, read below. All online casino sites reviewed and rated on our site are fully UK licensed unless otherwise stated. These games include live blackjack, roulette, and unique variations like Lightning Blackjack Live and Crazy Balls Live, providing an immersive live casino gaming experience. Com will not fail you. Firstly, we look out for great welcome bonuses followed by exciting regular offers.

How To Teach red tiger slots Better Than Anyone Else

Other Recommended Casino Sites

Blockchain casino, TFS tokens. If you win big and want to make a large withdrawal, you might have to process a few separate payouts, however. There are a lot of good reasons why some people much prefer to play at live casinos, UK and worldwide. Get a £30 Welcome Bonus. One of the easiest ways to spot a reliable, new online casino is to open the live chat and ask a question. The platform enforces a strict €5 max bet during bonus play; a single round above that voids the bonus. With real life dealers managing the tables, players can immerse themselves in a lifelike gambling atmosphere through live streaming. Moreover, it’s always smart to know each game’s house edge, which you can check from the options button.

How to Unlock More UK Casino Bonuses

Immediate VIP Program Access. Withdrawals typically process within 48 hours, and no fees are applicable if you’re cashing out more than £30. Membership benefits at this lavish casino include 24 hours services, exquisitely decorated private gambling rooms, a refined dining offer and more, all designed to keep the high rollers gaming in comfort and ease. Among the table games are a selection of online roulette variants like American Roulette and European Roulette, as well as a handful of blackjack games. These amounts vary between sites but they are typically £10 or £20. 40 Spins on Book of Dead per day after each qualifying wager in the first 5 days. Reputable casinos also. Gaming Highlights: Casumo boasts an exceptionally large game library, featuring thousands of online slot machines covering a vast range of themes and innovative features. However, the FAQ is hard to find, lacks a search function, and is poorly structured, making it the weakest support element. New users who meet the requirents can claim a deposit match welcome bonus and another freebie. Content is for entertainment only and is not financial or gambling advice. Reach out early if gambling starts to affect your mood, money, or relationships. Live dealer games redefine casino sites, merging real time action with digital ease. Simply deposit at least £10, and the bonus spins are yours for the taking. That is the question, and here’s the answer. For the most part, it’s a few pence, depending on network demand. Com is a registered trademark of GDC Media Limited. If you are a fan of new casino games, you will find them at many new online casinos. These platforms offer unrestricted access to a wide variety of games, generous bonuses, and the ability to bypass limitations imposed by the UK self exclusion programme.

Global Gaming Awards EMEA online casino of the year award

Their exclusive welcome offer includes a 100% match up to £25 plus 50 free spins for new joiners who make a qualifying deposit. When they appear, they are usually heavily advertised, tightly limited, or both. Finding the best US online casinos isn’t easy, but Bovada takes the crown for American players. Free spins expire after 3 days of claiming. Video slots are more modern, typically using 5 reels and offering a wider range of features. There, you will also find our experts’ experiences of the Fun Casino. However, watch out for short expiry windows some free spin offers expire within 48 hours of being credited, maximum win caps on free spin winnings, and game restrictions, with free spins nearly always locked to specific titles. Disadvantages: The platform lacks in promotions for existing players, apart from the daily scratch. Some slot games add side bets, treating them as optional. The list of the top games by Mirax includes I’m Feeling Lucky, Aloha King Elvis, Coins of Alkemor, 7s Fruit Fiesta, and Dolphin’s Wealth. While deposits are simple, withdrawals can take a few business days, so this method is best for players who prefer stability over speed. The UK market has exploded, and for those interested in having a flutter on betting sites or spinning the wheels on a roulette table, there’s now a huge amount of choice. Use your deposit money and any bonus winnings to play blackjack. We say what we like and what we don’t. Most jurisdictions tax gambling winnings regardless of the platform. Its status in the online gaming world remains as high as when a poker scene was a cliché that every western movie had to deliver. 888 Casino delivers one of the best live experiences on mobile, offering over 200 dealer tables with crisp HD streaming and an interface tailored for smaller screens. The country has a strictly regulated but heavily monopolized land based gambling industry that comprises 14 gambling halls, all of which are operated by the state owned company Holland Casino. Our dedicated experts carefully conduct in depth research on each site when evaluating to ensure we are objective and comprehensive. Kitty Bingo has a fun, lighthearted style and is great if you’re into bingo as much as slots. Parimatch, a Ukranian born brand, is swiftly becoming a go to place for bettors on a budget to visit, with a low minimum deposit required to access the sportsbook and the site’s 1,000+ slots. You can spend hours and hours doing the relevant research when it comes to finding real money casino sites in the UK. New registering players only. Ready to play Bitcoin casino games in the UK. As we explain their best features.

Wagering Bonuses

These casinos are ideal for beginners, with low stakes games and offers like no deposit free spins providing easy ways to get started. Maximum win caps determine how much of your bonus derived winnings you can actually withdraw. Daily Races: Race your way to the top of the Leaderboard. RegalWins Casino belongs to the Rank Group and is a part of the impressive portfolio of gaming sites for UK gamblers. Bet the Responsible Way. A little later, I discovered gambling in casinos. The game offers a box limit of £10 to £2,500 and allows you to play up to 2 seats. The wagering requirement is calculated on bonus bets only. For smooth banking and quick support, Red Dog remains a reliable choice. In total, new UK players will take advantage of their 600% welcome package and claim up to €9,500. You’ll regularly see free spins attached to. Sure, the majority of bonuses are designed to attract new players, but that doesn’t mean existing customers are left out. When evaluating real money online casinos, their team doesn’t fall for shiny landing pages or unrealistic bonuses. PayPal is one of the most popular e wallets available at UK online casinos, offering convenience, speed, and security. Another advantage of iGaming platforms is that they offer bonuses and promotions. Each bonus must be wagered within 7 days x40, otherwise it expires. Yes, bitcoin casinos are safe as long as you stick to licensed and reputable platforms. One of Betway’s most notable features is the sheer number of branded games in its library. The bonus itself cannot be withdrawn. With an innate talent for numerical examination and a keen analytical intellect, Harper’s transition from her humble roots to a notable position within the online gambling sphere reflects her relentless commitment and passion for the field. Simple yet full of suspense, these games usually come with a chat box for a social aspect.

1 CoinCasino – Most Recommended Crypto Casino in 2026: 10/10

We will discuss more about these types of bonuses available. The customer support service runs 24/7, and it includes a live chat option. This includes meme coins like DOGE and SHIB, stablecoins like USDT and USDC, and chain tokens like AVAX, BNB, and MATIC. Playing at online casinos is fun, but there are ways to make your gaming experience more enjoyable; the best ways to do this are listed below. We’ve spent a lot of time researching UK sites and playing on them to find out which among them are the best. The minimum deposit to get the bonus is €20, and you need to complete 30 times the wagering to withdraw the bonus money. For instance, the maximum conversion from a no deposit bonus to real money might be limited to £50 or £250 depending on the casino. If you like a certain game or game type, see who the developer is and if the new online casino you choose offers their games. Reviews from users who specifically mention claiming the free spins are especially useful in understanding whether the offer lives up to its promises. The mobile app deserves special mention. Here are a few smart tips to help you stay in control and keep things enjoyable. What we look for: No wagering or no deposit bonuses tied to other offers. Minimum odds on qualifying bet 1/1 2. Disclaimer: All promotional codes or free bet offers, welcome bonuses and promotions that are listed on this site are subject to the terms and conditions of the respective operators. Bonus Policy applies. It once again goes back to what you want to get from gambling on these games. Each of these UK online casinos has been thoroughly tested and vetted by the GamesHub team of gambling experts, with a particular focus on withdrawal speeds, fees, and safety. Common wagering requirements for winnings from no deposit free spins typically range from 20x to 40x. 01, and you can also withdraw one penny using PayPal and MuchBetter. We review our operations annually and focus on creating excellent customer experiences on our websites to serve all of you better. They usually fall into one of the following categories. No single platform on this list does more at once. Additional spins may be granted upon making a deposit, providing further incentives for players to explore the casino’s offerings. Contribution may vary per game. ✍️ Editor’s Note:”I appreciate the blend of visual flair with functionality at Temple Nile.

Le Bandit

Never spend more than you’re comfortable with or go overboard. There are a number of providers of live online casino games, but two are dominating the field: Evolution Games and NetEnt. Absolutely no deposit required. Gambling can be addictive, which can impact your life drastically. Cryptocurrencies like Bitcoin and Ethereum further enhance this with lightning fast transactions and minimal delays. Keep reading to learn more about the different types of free spins bonus. It’s no use claiming a no wager bonus if you have to spend a fortune to release it. You may find the same game is available with various table limits included. These platforms operate outside the UKGC’s jurisdiction, meaning they aren’t part of the Gamstop scheme. We may earn a commission if you click on one of our partner links and make a deposit at no extra cost to you. Betfred is one of the top slots sites UK players can join as it has an excellent selection of popular slots including six figure progressive jackpot slot machines, and there’s even a live dealer section with branded tables. To claim the Fun Casino welcome bonus, new players must make a deposit and wager at least £10 to unlock Free Spins on Big Bass Splash each worth £0. Find out what constitutes our online casino criteria below. Written by: Julius De Vries The review was last updated: 02 October 2025 Fact check by: Kim Birch.

Reputation

Free spins typically have shorter expiry periods 7 14 days, while cash bonuses may last up to 30 days. Instead, it spreads the love over 10 days, delivering 25 free spins per day for a total of 250 spins. Every casino we recommend is fully UKGC licensed and tested for safety and fairness. Here’s why you can trust what we have to say. The minimum deposit sits at £20, keeping entry accessible. Use a strong password and enable two factor authentication. Searching for the newest slot games to hit the scene. All UK casino sites that operate legally in the UK are licensed. Nowadays, there’s not even one internet casino that doesn’t offer games with real dealers and there’s a good reason for that – players absolutely love them. Take some time to review the supported payment methods on your site of choice. Customers can play a wide range of slot games and for every 200 spins they use, they will get the opportunity to Spin and Win. Double your first bingo deposit at Dotty with a 100% bonus. Web wallets like PayPal tend to be the fastest, with payments processing in just a few hours. In the USA, laws vary by state, so it is crucial to verify your local regulations before playing. Step 3: Claim Your Bonus Bonuses activate through different methods depending on the casino. Sometimes, Bally UK Casinos offer free play mode for newly registered players limited slot sessions, up to 20 spins. Speaking of online slots, it was their huge range of progressive jackpot slots that caught our eye. 20 spins on 1st deposit and 30 spins on 2nd deposit. 1+ deposit with Debit Card. This is a very popular game among UK players with high RTP and modern design. One recurring question from users remains: is love casino legit. The headline feature is the ongoing 10% weekly cashback on all slot play, credited every Thursday. Thanks to pronecasino I walked away from a couple of ‘generous’ sites with shady terms and settled on a stricter but much more predictable brand. While some players may welcome the opportunity to enjoy free spins on slots games using the lady luck casino no deposit bonus amount, other players may find the limitations attached too restrictive.

Leave a Reply Cancel reply

50x wagering applies as do weighting requirements. Instead, it uses tokenisation to replace your card’s Primary Account Number PAN with a Device Account Number. Max one claim per player. So make sure you’ve at least had a glance at our more detailed reviews before deciding where to sign up. When it comes to your money, you have complete control over your deposits and withdrawals you can add to your bankroll or cash out your winnings at any time, using a variety of options including e wallets, online transfers, and debit cards. Among the top rated operators, Betfred and Betboro stand out for delivering a strong balance of innovation, fairness, and smooth cryptocurrency integration — setting a clear benchmark for what a modern bitcoin casino should offer. Crypto only banking with instant payouts. 4 Wild Riches is another game that caught our eye amongst the thousands available. This casino accepts many payment options, including crypto, obviously. 50 can turn your birthday into a big celebration if you win big. Twitter LinkedIn YouTube Instagram Facebook. At the same time, there’s a great variety in terms of gaming options, including live dealer games, poker, and bingo. A good new casino will have a library of 2,000+ games including slots, jackpots, and live casino tables from top developers like NetEnt, Play’n GO, and Evolution. Withdrawals at LeoVegas are processed efficiently, if not instantly. Slots review, I’ll take a closer ;look at the POP. Non Gamstop casinos are gambling sites not linked to the UK’s self exclusion scheme. Com is an independent affiliate site. Cashback is usually calculated on your total bets, any requested withdrawals and losses on deposits. Scan trusted sites like Trustpilot or Reddit to see if the same complaints keep surfacing again and again. If you love slots with personality, this one brings. Side bets and multi seat options are often available. Click on the links in the table to go to the full analysis of the best casino sites for each game type. NOWPayments combines all the best practices of the industry — we guarantee the best possible service quality for all our clients. If you want to find better no wagering deals on your own, make sure to look for the UK Gambling Commission logo at the bottom of the website to ensure the casino is legal. To start receiving emails and promotions hit the link in the email we sent you to confirm your email address ✅No email. Withdrawals are processed Monday to Friday with up to a 48 hour pending period, and each deposit must be wagered at least 1x before you can cash out. We have a comprehensive guide to the most common type of casino bonus, the welcome bonus, elsewhere on the site, and if you’re a rational gambler, we, of course, recommend you read it as soon as you’re finished here. Below are the best games you can find at real money casinos.

Bacana Play

Credit Cards 1 5 days. A platform created to showcase all of our efforts aimed at bringing the vision of a safer and more transparent online gambling industry to reality. First is that there is a fee on mobile deposits, and the second is that the site is too similar to most other Progress Play casinos. Once you have satisfied any wagering requirement on the Free Spins, you are free to withdraw any winnings as cash. Welcome Bonus Only Available to New Customers, Max Deposit £/€50, Bonus 100% paid out in 100% increments to Main Account Balance. Additional TandCs apply. They often feature larger welcome bonuses and ongoing promotions compared to UKGC licensed sites. Online payment services. The UKGC focuses more on withdrawals e. However, it is important to keep track of terms and conditions as they will all be different. Set a coin budget, and when you’ve reached your limit, end the gaming session or at least take a break. Free Spins will be available when you open the game Kong 3 Even Bigger Bonus. Typically, cashback is calculated from the net losses. Clicking “No” means no popup for 1 hour. The best cashback casino in our opinion is All British, here you get a 10% cashback. We get a small payment in exchange for registrations through our links, but we only accept UK Gambling Commission licensed brands to our lists.

Evoke extended Bally’s Intralot deadline, keeping potential takeover discussions moving forward

Whether you prefer to play bingo games or table games at the best UK casino sites, the best way to have fun is to play your favourite games. Even if you’ve never heard of the brand, we’ll tell you whether it’s new and growing, or globally established behind the scenes. Evolution Gaming, Playtech, and NetEnt are among the top tier casino software providers that collaborate with prominent live casinos to provide a diverse selection of games, such as blackjack, roulette, baccarat, and innovative game shows. You can learn more from our article about roulette rules, odds, and bets. After all, you don’t want to play something you don’t like, even if it’s free of charge. Spins must be used and/or Bonus must be claimed before using deposited funds. Not all free spins offers work the same way. The minimum deposit sits at $25, higher than most rivals on this UK crypto casinos list. In fact, only 13 of these 200 made the cut. Usually about how rigged the games are, needs to take KYC before able to transfer money. Max cashout from bonus spin winnings: £50. In blockchain powered casinos, these results are recorded on a public ledger, so they can’t be changed later and can be checked by anyone. Developers like Hacksaw Gaming or Nolimit City are known for producing unique and high volatility slots, which appeal to experienced players seeking excitement and challenge. Welcome Offer £30 In Bonuses With £10 In Bets. Bitcoin live dealer casinos offer entertainment, security, and anonymity. BetMGM also have one of the best casino welcome offers. Sign up and bet £10 and BetMGM will give you 100 free spins. CoinCasino never requires documents regardless of withdrawal amounts. Although it does not offer large payouts in the base game, it features exciting bonus rounds. We’ve personally verified the licensing status of every casino on our list. It is essential to understand all terms and conditions attached to the leading free spins and no deposit bonuses to prepare players for redeeming bonuses. From free games to daily no deposit bonuses and welcome no deposit bonuses, the following online casinos give you the best casino no deposit bonuses in the UK.

Spin Casino UK Welcome Bonus 2025 – Double Your Bankroll with 200% Match + 100 Free Spins

I requested the withdrawal from Sky Bet. There are age and location restrictions on where you can gamble online. Verification commonly requires proof of identity and address to protect accounts and prevent fraud. Join 30,000 locals who stay current on Orlando news, culture, and events. An initiative we launched with the goal to create a global self exclusion system, which will allow vulnerable players to block their access to all online gambling opportunities. Drops and Wins, weekend reloads, and zero hidden fee withdrawals round out a package that feels focused, not fluffy. However, you must have made a prior deposit using Apple Pay. As you can see, depositing and enjoying your free spins is more beneficial. Check the welcome offer for new customers. UKGC licensed sites must meet strict standards around things like fair play, responsible gambling tools, and protecting players’ money. Baccarat roadmap tracking represents the most sophisticated casino score display system. Get up to 200 Cash Spins No Wagering, No Max Win. Jack Bit casino only supports crypto as a method for deposits and withdrawals.

Betway Casino Welcome Bonus for Canadian Players 2025

Their unique selling point is their frequent Cashback offers on losses, which is a rarity in the UK market. All transactions are processed quickly, and we found that the money gets to your account within just a few hours. Read the terms and conditions to know how much you can win on any bonus offer. Prioritize UKGC licensed platforms for safety. Standouts include Sweet Rush Megaways, Back to Venus, and Super Golden Dragon Inferno. Grab a 100% welcome bonus worth £100 + 50 free spins on Rainbow Riches. A 10 Mbps connection is recommended for HD live streaming. Up to 140 Free Spins 20/day for 7 consecutive days on selected games. Whether you are a seasoned player or new to the crypto gambling scene, understanding how these casinos operate and what they provide can enhance your overall gaming experience. Remember: no bonus is worth losing control over. 50+ Progressive Jackpot slots, including Irish Riches and Genie Jackpots. Love spinning the reels. With a no wagering bonus, you can keep whatever you win, so these can be valuable bonuses for players. However, flawless streaming alone isn’t enough—it must be supported by an intuitive and responsive interface. Some casinos offer variety — we offer a complete experience. There are, however, brands which offer no deposit bonuses that do not come with wagering requirements. Whether you want a high deposit match or a bonus with minimal wagering requirements, our list has the best choices lined up. 18+ Play Responsibly TandCs Apply Licence: 39411. Cryptorino’s gaming library is diverse, with slots offering up to 30 weekly free spins. A legitimate UK casino must hold a valid licence from the UK Gambling Commission UKGC. These sites meet all the requirements imposed by the UKGC and stand out for their safe gaming and payment options. We look at which deposit methods are accepted and which are excluded from deposit bonus eligibility, what withdrawal options are available, and how long withdrawals actually take in practice highlighting the best fast withdrawal casinos. Free spins are the game’s main bonus feature and are triggered by landing three or more Book symbols.

Desert Nights Casino Review

>> Get up to $2500 and 50 spins. These include slots, table games, live dealer games, Slingo, bingo, and many more. For example, MrQ Casino gives you 10 bonus spins with no wagering when you confirm your mobile number. On strong cashiers: e wallets, cryptocurrencies and instant bank rails can be same day; cards often require several working days; traditional bank transfers vary by corridor. Bonus funds must be used within 30 days, spins within 72 hours. Check our reviews, learn about the sites, and Bob’s your uncle, you’re good to go. Upon signing up, you can take advantage of a welcome bonus and a host of ongoing promotions are available for both casino and sports, including daily cashback, tournaments, and drops and wins. The Online Casino offers a wide range of secure deposit methods. The VIP rewards at Wild. We have come up with several tips that will help you to make the most of your experience. Edict eGaming is an experienced game developer that has been around since the 90s. 700% up to €4,000 + 200 Free Spins. The Bonus Wheel activates with three or more bonus symbols, offering instant wins up to 250x your stake. Unlike other casinos that give you all spins at once which you might burn through in minutes, bet365 spreads them out over 10 days. Average Withdrawal Time: 2 working days. There are over 80 live casino games for you to explore, and All British Casino categorises its games, making them easier to find and play. This means the ease of the site and how easy it is to navigate and look around. A site that works for one player may not necessarily be the best for another. Most popular casino games in the UK, including Drop and Wins and Megaways. For card enthusiasts, the best online casino for poker delivers robust selections, from Texas Hold’em to innovative variants. These games are streamed in real time from professional studios, offering a more immersive experience than standard digital games. The spins are typically credited automatically after enrolling and confirming your account, or after a brief email or SMS confirmation. The online casino market is constantly evolving, and 2026 has seen the launch of some exciting new platforms. The best UK casino sites have new player bonuses that can double or even triple the amount of money you have to play with. Free spins must be used within 48 hours of qualifying. Each UK online casino we feature here is licensed and regulated by the UK Gambling Commission. Then you’ll want to check out the best casino bonuses. Sign up with accurate account details and verify your email if prompted. Finding a crypto casino isn’t difficult, but choosing one that offers fast payouts and avoids surprise KYC checks or stalled withdrawals is.