/** * 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']; } } Open Mike on online casino bonus codes - Eluxhire

Eluxhire

Open Mike on online casino bonus codes

No Deposit Welcome Offers

If you use a bonus code or click on certain affiliate linkslisted on this page or elsewhere onOddsSeeker. Visa and Mastercard remain the default choices for many players because they’re familiar and easy to use. Before a fast paying casino makes it onto our list of recommendations, we conduct a thorough review to ensure it meets our high standards. Some payment methods are particularly popular in certain countries. Well, here’s where we need to have an honest conversation. Ready to unlock the future of gaming. There is an admirable effort from MyStake to provide a crypto gambling experience for almost every occasion. In this casino review 2024, you will learn more about its features, services and more. Once you’ve made sure that the paperwork checks out, you should also verify the casino’s reputation. However, those of you more attuned to big welcome packages and larger numbers may prefer the bonuses on offer at Winstler, which is totally fine. Mobile Games and Software: DuckyLuck offers 600+ mobile games from Rival Gaming, BetSoft, and other US friendly providers. You must be able to control your urges and play responsibly, and we offer a few beginner’s tips on how. Other popular casino table games include card games such as baccarat, casino poker, pai gow poker, andar bahar and teen patti; dice games like craps and sic bo; and lottery style games such as keno. Low volatility slots offer smaller, more frequent wins, which is great for managing a smaller budget. We take into consideration the priceless comments of thousands of real time players just like you, as we believe it’s valuable to have both viewpoints. 10bet Casino recently launched its app, quickly becoming a go to for video slot fans in the UK. With UK player protections, tailored daily offers, and a massive game library, Winlandia Casino is one of the strongest new online casinos UK players can try in 2025. Deposits start at $10, max slot bets hit $50, and weekly withdrawals go up to $50K in crypto. Alternatively, some bookmakers require you to contact their customer support team, after which they will credit the no deposit bonus to your account. Welcome bonus restrictions: Some online slot sites won’t accept https://truewinvip.com/bonus/ PayPal deposits when trying to claim their welcome bonus. Interest in Pirates Slots continues to grow across licensed UK casinos, driven by engaging design and solid return to player values. This website contains advertisement. Real money casino Malaysia games continue to attract players with their thrill, variety, and potential payouts. Legendz Originals features unique, in house games you won’t find anywhere else. Understanding these offerings could reveal significant insights into player preferences and market dynamics, prompting a closer examination of the leading contenders in this competitive landscape.

How to Grow Your online casino bonus codes Income

Best Casino Bonuses 2025 Where Promos Actually Work, Not Rot in the TandCs

After carefully reviewing the top online casinos in the UK, we have handpicked our favourite sites to try out. However, always check the bonus terms. Valid on selected games and expire in 7 days. Thanks to a direct line to their workshop, we can now share the hottest new releases launching this April. The top five is based on what really matters: fast payouts, fair payment limits, proper bonuses, and a smooth overall experience. I’ve been using BetRivers for a while now and it’s honestly one of the smoother online casinos I’ve tried. Why we chose Rollbit Casino: Real crypto bonuses without the fluff. Progressive jackpots gradually increase their jackpot. You get a clear look at how each casino performs on mobiles, how fast the payouts are, and how easy it is to move around the site once you sign up. These slots have three reels and a single payline right across the middle. Legal licensing and regulations of bitcoin casinos help to build credibility, scalability, and provide safe environments to players. The Welcome Bonus is only available to newly registered players who make a minimum initial deposit of £ 10. The best real money casino for you is the one that can cater to your very specific money needs. Stake £10 Get up to 200 Free Spins. Best Crypto Live Casino. It’s a hit in UK casinos for those seeking a simpler, faster paced option. For those in restricted jurisdictions, a VPN provides an alternative access route. Many offers have low wagering requirements, making it simpler to withdraw your winnings. In the UK, everyone has heard about how taking a break. Great examples of this include Lightning Dice, a simple version of Sic Bo, and Golden Wealth Baccarat. Naturally, the exact percentage and maximum value differ from site to site. With lots of exclusive and branded games, playing at BetMGM UK feels just like stepping into a real Las Vegas casino. Finding casino bonus codes is easy, thanks to our lists that show them right up front.

At Last, The Secret To online casino bonus codes Is Revealed

How Music Fans and Independent Artists Are Navigating Digital Finance: From Buying Gig Tickets to Managing Income Across Platforms

Here is an example of how we display a bonus offer in our casino reviews. Free Spins expire 48 hours after crediting. Always check the TandCs for details such as minimum deposits and wagering requirements. The foundation of any reputable casino lies in its licensing and security standards. To play online roulette at BetOnline, you have to simply visit the casino section and type “Roulette” in the search bar. Reducing internal bottlenecks is crucial for maintaining the rapid instant fast casino withdrawal payout times players expect from a top fast withdrawal casino. The first tier entitles new users to a 100% bonus when depositing $10 to $200, while the second deposit entitles users to a 150% bonus when depositing $200 to $1,000. Recommended streamers. The expected arrival time of a deposit is dependent on the platform and the method of payment selected. Playing at tables with comfortable limits allows you to stay in the game longer and reduces pressure to chase wins. Current legislation leaves most decisions regarding the legality of particular types of online gambling to individual provinces. In the US specifically, market projections suggest growth from $54. Features define a casino because they can make your experience much more enjoyable. With a well optimised mobile browser and highly rated iOS app, players enjoy smooth performance across platforms.

Did You Start online casino bonus codes For Passion or Money?

Tips For Selecting A UK online casino That Suits You

3 on the Play Store from users, way above most betting apps. Crypto bonuses can offer significant value, but wagering requirements vary widely. Welcome Bonus Percentage. Featured Bonus: Instant Casino offers a valuable 10% weekly cashback scheme that covers all net losses as long as you’ve made a minimum deposit of $20 or BTC equivalent. WR of 30x Deposit Plus Bonus amount Slots count 100% and any other game 10% within 30 days. These features are available from the moment of registration, and players are encouraged to activate them proactively. GamStop and GambleAware links: Access independent support services and professional help whenever needed. Our dedicated bonus reviews page has only the best promotions that you will benefit from. Each provider excels at creating slots, table games, game shows, and live games. Quick tip: If you’re used to standard poker, give yourself some time to adjust to new winning combinations – for example, the A2345 straight is the second highest straight in Pai Gow. Responsible play ensures lasting enjoyment. Org 18+ TandCs apply. You are wondering between two forms of play: Free casino games vs. Deposit £20 and get 100 Free Spins on Big Bass Bonanza. Before completing your deposit, check if a promo code is required. Names like the Malta Gaming Authority or the UK Gambling Commission pop up often, and when their logo is on a site, players usually take it as a sign that payouts and security are being watched closely. Jackpot, Table Games, Video Slots, Video Poker, Virtual Games, Crash Games, Scratch Cards, Video Bingo, Bonus Buy, Megaways and Multiways, Crypto Games. Max bonus winnings: £1,000. Here are five big reasons why more people are choosing live dealer games. Early casino platforms were extremely basic and required players to download the software to their desktop. Their systems undergo pre launch audits and their operational infrastructure must be fully tested before going live. Jedoch sind für alle Änderungen – wie das Aktualisieren oder Deinstallieren von Treibern – Administratorrechte zwingend erforderlich. If you have arrived on this page not via the designated offer of ICE36 you will not be eligible for the offer. The main purpose of UK casinos is to facilitate a fun and hassle free gambling experience. It ticks all the boxes, including amazing bonuses, top games, and quick payouts. Our top picks include BC. Bonus spins must be used within 10 days. However, there are quite a few websites that also feature live dealer and sports betting options.

Ho To online casino bonus codes Without Leaving Your Office

Why you can trust our experts

Complete KYC early, use the same method for deposits and withdrawals, and meet all wagering before requesting a payout. £10 min deposit £10 in slot bets grant 50 spins on Big Bass Splash £5 total spin value £0. If you are ready to start, explore how to play and win real money online or browse the full range of online casino games available to SA players. These 10 famous games were among the highest paying and most popular slots every month at PlayOJO since the turn of the year. For the quickest payouts, Bitcoin or Tether typically processes within 24 hours. What’s more, the game graphics on mobile apps tend to be of a much higher quality compared to those on mobile casino sites. Every platform uses Secure Socket Layer encryption to protect your data. Irrespective of the type of player you are, we have reviewed games with betting limits for low rollers, high rollers, and everything in between. This commitment to quality and innovation makes it a standout choice for those looking to enjoy online gaming in a secure and user friendly environment. Starburst, Book of Dead. It has 6600+ wide varieties of games, including slot games, live casino games, and exclusive mini games with visually appealing graphics and UI/UX features that give a satisfying, fun experience. Credited within 48 hours. Please remember to check your local laws to ensure online gambling is legal where you live. The best online casino sites offer a wide range of games; below are the top game types users will find while playing. Low Wagering Bonus: Comes with fewer restrictions. 🎓 Referenced by Harvard, Columbia. In the UK, gambling winnings, including those from crypto casino online gambling, are not taxable for individual players. To qualify for loyalty rewards, it’s often the case that you’ll need to have wagered a certain amount of money on online slots or other games. Before using a free sign up bonus, look at our tips below. Look for online casino apps that offer.

Free Spins No Deposit

When used carefully, these schemes can improve your expected value without requiring larger deposits or risky play. I hope you are doing well. For instance, some sites offer progression based log in rewards that mimic the battle passes you’ll find on Fortnite and other games. For example, set aside a small portion of your budget specifically for bonus buys and don’t exceed it. The platforms we have reviewed have undergone thorough vetting for safety, licensing, fairness, and user satisfaction. There are some interesting promotions for existing players. The minimum legal age to enter a casino in the UK is 18. Instant Casino is licensed and regulated by the Government of Curaçao. As an existing user if you don’t feel satisfied with your current casino bonus site, there are plenty of options out there to provide a different experience. If you want fast gameplay, stick with craps or roulette. 50 spins added immediately, 50 24h later. Traditional bank transfers can take up to 5 working days, although it may be longer. 3 ★ Android While the app ratings are lower due to occasional technical glitches, 10bet offers a distinct “VIP” feel with their Ten Club. We look for diverse selections with wide reaching limits to suit both casual and high stakes players. This ensures that all data transported between users and servers is kept private and protected. The speed of your payment will also depend on the crypto you’re using and possible network congestion. Cryptocurrency transactions provide additional privacy layers that traditional banking can’t match.

Software Providers Powering the Casino Games

LeoVegas was founded in 2011 and it has become a trusted name within the gaming industry. It is most typically employed in slots, video poker, and online table games to avoid predictability. Since financial transactions are integral to online gambling experiences whether depositing funds or withdrawing winnings the guide outlines commonly accepted methods including e wallets like Touch ‘n Go eWallet or GrabPay alongside traditional banking channels compatible within Malaysia’s ecosystem. Those that still rely on email based verification or manual processing tend to have longer wait times. Attracting gambling punters is an easy way of making money, especially if you’re going to a not bother with a licence b offer scandalous bonuses and c not pay out winnings. There’s really no wrong answer. Each game takes place on a slot like machine, but the rules of play are closer to poker. We recommend that you are connected to WIFI when you play casino games or mobile phone bingo on mobile. This website uses cookies to improve your user experience. The fields are usually less than 100, and it gives you a much better chance of making it into the top 20 than the large fields at Midnite. Visa/Mastercard: Visa and Mastercard are widely accepted at most UK casinos, making them a go to choice for many.

What’s a live online casino bonus?

20 per spin, 100 spins on Better Wilds at £0. Cashback offers can be a good way of recouping some of your losses. New Hampshire has an exclusive contract with DraftKings as the sole sports betting operator, both online and at retail locations. Free spins and tournaments. Free daily spins expire after 24h. It’s a hedged bet, because not every pick you make needs to hit. Welcome pack of up to $9,500. Unlock the best slot bonus UK deals at top UK betting sites with bonus promotions, including deposit bonus slots UK offers like 100% matches and free spins. For an online casino to make the cut and be included in the list of the best gaming sites of the year, their customer support needs to be quick, helpful, and effective. Keywords used: Polite ways to email someone after they missed a meeting. The casinos listed here all meet that standard. An optional Bet Multiplier increases chances for bonus spins but adds risk. These crypto based games allow you to check outcomes on the blockchain network to ensure fair play and random outcomes. You can do whatever you want and you have unlimited power. The welcome bonus is out of this world, amounting to CA$30,000 + 50 free spins. KYC checks may not have been completed in full or documents may not have been accepted.

Welcome Bonus Totalling up to5 BTC and 300 Free Spins

New UK casino sites often offer generous welcome bonuses, bonus spins, and ongoing promotions. Use Bet365 code to claim your welcome offer here or explore more bonus codes here. Why it Excels for Live Dealer Games. There’s nearly 30 blackjack tables, with bets ranging from $0. When looking for a reputable sweepstakes casino, it’s important to consider several key factors to ensure you play at the right place. Most of the time, slots contribute 100% towards the wagering requirements. Pragmatic Play’s games have proven a huge hit with both our readers and the review team. You can play games from top providers like Endorphina, Playson, and Ezugi. With live casino games, this might mean playing with dealers with whom you get along. The casino’s features, including a wide range of slot games and user friendly interface, contribute to an engaging gaming experience. Here are some of the top bonuses to play slots for real money. Whether you’re into slots, live games, or sports betting, WinoCasino offers a secure, fully licensed platform with generous promotions and seamless play on every device. This crypto casino has gamified the entire gambling experience, inviting UK players to cast spells, unlock chests, and duel your fellow players in real time. This is our short list of brands that apps you should not miss in 2026. Mobile baccarat games feature large betting buttons, simplified scoreboards, and touch optimized card reveals that enhance the traditional game experience. Hyper Casino sister sites. Casino are the leading fast payout online casino sites in the country. Daily free spins reload bonus. There are many different payment methods today, and great sites offer different choices to their players. And with 3,000+ games in total, it’s impossible to be stuck for choice. Use the table below to compare the most common payment methods. We tested dozens of non GamStop casinos to find the games not on gamstop that actually pay. Set deposit limits to help manage your gameplay and stay in control of your spending.

Does GitHub Copilot include features to make it easier for users to identify potentially relevant open source licenses for matching suggestions?

The added benefit of playing via your mobile is that you can activate push notifications. Let’s have a look at a fairly standard casino welcome bonus. For example, Slots and Keno can be anywhere between 15% and 25%, depending on the specific game. As long as you have a crypto wallet and an internet connection, it’s possible to enjoy seamless online gambling throughout the UK—whether you’re in London, Manchester, or anywhere in between. 40x wagering dep+bonus. Provider: Play’n GORTP: 96. Reliable and convenient banking options are essential for players in the United Kingdom. If you like getting rewarded just for showing up and playing, this is where to start. You’ll also like its no deposit bonus, which new players can get by entering promo code “FREE5”. You place your bets on a number, colour, or section of the wheel and then watch as the dealer spins the wheel and drops the ball. UK casinos regularly provide new no deposit bonuses to attract new casino players. All the goodies in one place – The Dog House Megaways, Bandit Megaways, Big Bass Bonanza Megaways, and others. To make our readers’ lives easier, we have listed the types of casino players and the best site for them below. Anubis and Thanatos collide in Gods Go Pew Pew from Sneaky Slots. These are provided by approved software producers and use random number generators RNG which have been independently tested and approved by companies such as eCOGRA and iTech Labs as providing fair and unbiased outcomes. Free spins are a low pressure way to sample themes and features. 18+ Terms Apply, Please gamble responsibly,. We’ve selected a few different slot categories we like to play and picked the top 3 games for each. Tuy nhiên với Chat GPT hay các dạng AI khác thì việc này hầu như đơn giản hơn nhiều. 100% up to R3,000, 7x wagering. If this sounds like your speed, here are the top five demo Vegas slots available on BonusFinder. Joe Turner is a content editor at ValueWalk with experience covering cryptocurrency, blockchain, and crypto gambling. All tips on our site are based on the personal opinion of the author. Players can pick from multiple rule sets and table restrictions, making these games accessible to beginners and pros.

100% Match up to $1,000 + 2500 Reward Credits

Some unique shows, such as Monopoly Live and Money Time, are also available. Get £20 in Casino Bonus Funds 10x wagering. You can, alternatively, use cryptocurrencies to benefit from faster payouts. Withdrawal requests made late on Friday or during bank holidays are more likely to face delays. Payment methods and terms can change due to maintenance and policy updates, so we focus on what is clearly shown in each cashier/terms page at the time of review. If you want to try your luck with the biggest jackpots, you look for the jackpot offers on new gaming platform websites. The best online casino for real money in the UK is Casumo, according to Bojoko’s experts. The Gambling Act certainly gives you a sense of security knowing there is protection and that you at least have some form of recourse if you feel like a gambling site has violated anything both parties agreed to. Take your chance at Mega Casino’s live roulette promo, with new missions revealed every single day. For more information about games and promotions, see our Jackpot Slots article. From Crash to Limbo, our specialists discovered one hundred or more digital variations of table games. All the same, some things stay constant choose trustworthy platforms, understand bonus terms, and gamble responsibly whatever the market brings. The platform focuses on premium players seeking substantial bonus value and exclusive experiences. Com Group, and BetMGM. No download is necessary and you still get instant access, and often experience fewer crashes during gameplay. Mega multiplier slot games are also on offer. Bet Casino may sound more like a sports betting site. However, despite running officially sanctioned software, they are considered independent operators. Each of these brands will have their own special offers that aim to make it more attractive for new customers to sign up to their sites. Rather than simply listing platforms, Innovate Change conducts in depth evaluations of each online casino, verifying licensing credentials, payment security, bonus fairness, and overall trustworthiness. Bonus: 80 Wager Free Spins. Full methodology, criteria weights and scoring breakdowns are covered on our How We Rate page.

Bonuses and Promotions

Claim your 50 Free spins from your promotional hub. Free spins no deposit no ID verification let users try selected slots instantly, though full checks are required before withdrawals. However, in some cases, the actual bonus is tiered which means it’s spread across a series of deposits. Spins credited upon spend of £20. With over seven years of experience in the sports and casino industry, she combines expert knowledge with a clear, approachable writing style. Log in and open the cashier sometimes labeled “Deposit”. Established in 2012, it has a strong presence in Malaysia and Singapore. Bonus Features: The best online slots feature bonus rounds, which are where the game’s biggest wins are achieved. The table below highlights the most popular game categories available to UK players using Trustly payments. All company, product and service names used in this website are for identification purposes only. £10 free play + 100% deposit match up to £100. Moreover, players love its seamless user experience, strong bonuses, and a selection of exclusive games you won’t find at any other casino site. E Mail Adressen Allgemeines• Übersicht über alle E Mail Adressen Ihres GMX Postfachs• Absendername wählen, der bei Empfängern neben der E Mail Adresse erscheint• Standard Adresse definieren oder Zusatzadressen löschenZusätzlich zur Hauptadresse weitere E Mail Adressen anlegen• Bis zu 49 GMX Adressen z. New live casinos benefit from improved streaming technology that delivers high definition dealer interactions without excessive data consumption. AnsweredView answer in the context of this discussion. The platform blends casino entertainment with one of the busiest poker networks available, offering tournaments, fast fold tables, and casual play. High volatility games are a riskier approach, though the potential payouts are higher. Variations include Omaha, Seven Card Stud, Razz and Five Card Draw. Should you focus on high RTP slots to increase your chances. Cons of Instant Casino. Most UK online casinos are compatible with mobile devices, including the iPhone, iPad, and Android devices. Free spins expiration: 3 days. We checked forums, social media, and reviewed many casinos not on gamstop to see what players from the UK were saying. Here are some of our favourite new slot machines. Opt for the bonus if the game is to your taste and you are familiar with it. If you’re still not satisfied, you can fill out a form and contact the LuckLand team that way. Free Spins: on Double Bubble. The sign up process is instant — no KYC, no delays. As the UK online gambling world continues to boom, it can be difficult to figure out which platform is the best for your betting needs — or even more importantly, which ones are trustworthy.

Free Spins Bonuses

Best online casinos UK are rated based on several critical factors, such as. Baccarat is a niche game, so most casino operators offer only one or two variants of this game. It’s like chess – no matter the position of the pieces on the board, there is always a ‘best move’. But remember: they’re not free gifts. Channels are normally activated within 5 minutes, but can take up to 4 hours during peak times. This bonus is divided into your first six deposits. Stake offers bettors access to over a thousand games. BestOdds is operated by Stram Entertainment Ltd for customers in Great Britain, licensed and regulated by the Great Britain Gambling Commission. Wager a minimum £30 on slots and receive 90 free spins on Big Bass Bonanza. ” If your cards have a combined value of 22 or above, you “bust” and lose. The welcome offer includes a 100% deposit match up to £50 and 20 free spins on Big Bass Splash. The exclusive bonuses and user friendly features make it an excellent choice for new and veteran players alike. You are guaranteed to find something to your liking, no matter what type of slots you prefer. Game restrictions apply. Casumo consistently delivers withdrawals within 24 hours for verified users. The overall experience is smooth and sleek, making online gambling accessible and easy.

Blackjack guides

Many of the best online casinos have dedicated apps that offer you everything their browser version does, but even more optimized for the smaller screen of your phone. The loyalty “Perks” tier system hands out layered rewards, and the Weekly Mystery Bonus is a much anticipated and often generous surprise. If you win, you can cash out to a PayPal account, Apple Pay digital wallet, Visa credit card, or Venmo account. This licensing also means that the casino is regularly audited, giving players confidence in the platform’s integrity. All top rated sites on this page are thoroughly reviewed and meet strict trust, innovation, and value criteria. Red Dice Casino is a gambling site featuring a well rounded player experience, offering casino games and sports betting. Casino laws in Europe vary from country to country, so check your local laws to determine if online gambling is legal in your area. All real money casinos operating in the UK must be licensed by the UK Gambling Commission UKGC. Red Kings has a selection of exclusive titles that you won’t find elsewhere, and you’ll also find a handful of poker games, including the ever popular Live Casino Hold’em. These platforms elevate online casino gambling to the next level by offering ultra quick transactions, the latest casino games, and some of the UK’s most generous bonuses. They come with robust fraud protection and relatively fast processing times. Registration at an online casino should always be a simple process. Excellent sports integration. I’m eager to see how Legendz continues to evolve. The William Hill Vegas promo code to claim this offer is available by clicking link 👉. Whether you’re a casual gamer or a seasoned player, Legendz Casino provides a reliable platform with zero upfront investment—perfect for testing the waters risk free. The most dynamic system is Megaways, a game changing mechanic where the number of symbols on each reel changes every spin, creating a random number of ways to win—often over 100,000. Lucrative Bonus Schemes: Look for platforms that offer attractive bonuses and promotions to enhance your playing experience.

Rock, Popand Metal

A reputable online gambling site has a valid EU gaming license and takes care of protecting customer data. Just like any other bonus, wagering conditions apply to your winnings. Just like Mr Vegas, Videoslots process withdrawals every few minutes around the clock, which means the majority of their payment methods receive payouts in five minutes or less, whilst the rest will see maximum waits of 12 hours. Our recommended offshore mobile casinos don’t offer apps but are optimized for an excellent mobile experience on Android and iOS devices. Operators can still set differential contribution rates a game contributing only 10% toward wagering at a 10x cap creates an effective 100x requirement on that game. If the mobile version is poor, we won’t recommend it. 5% RTP with Strategy. Additional promotions are frequently available through social media channels, offering free play options and bonus Virtual Credits for specific games. Here’s how the top crypto casinos stack up on bonuses, wagering requirements, and game variety—on one fast, scannable table. Follow these simple steps when playing on UK casino apps. CoinCasino doesn’t just lead with one of the biggest crypto welcome bonuses, it backs it up with a strong mix of high RTP games, fast cashouts, and ongoing promotions that make it one of the best platforms for serious crypto players. Being among the finest UK online casino sites still means our experts have enjoyed them. Sleek design and packed full of slots, progressives, card and table games, casino games and live casino available. According to an expert study, online gambling sites often use way higher RTPs during demo slots play, while conventional RTPs are set to around 92%. FanDuel Casino offers a unique welcome package focused on risk free play. Compare the best real money slot sites with the help of our carefully selected list, and use our expert reviews to learn more about each site. Playing at an online casino with a bad reputation leaves your personal information at risk. Players in New Jersey can enjoy playing all of the available casino games at the online BetRivers Casino. For something different, try the exciting Icy Hot Multigame where you play two 3 reel slots simultaneously. These methods often include debit cards like Visa and Mastercard, e wallets like PayPal, mobile options like Apple Pay and GPay, and prepaid cards like Paysafecard. You can opt to have session time reminders at particular intervals. For the most enthusiastic and risk loving casino players, highroller bonuses are incredibly generous but also quite difficult to get due to the increased difficulty of their requirements. Ricorda che gli anziani non hanno alcun potere o autorità legale: non sei obbligato a incontrarli o anche solo a parlare con loro. It’s a win win situation, the casino gets a new player to join their site, and the new player gets to take advantage of the value offered by the bonuses and promotions. When we read through the terms and conditions for the bonus offer we noticed that the max bonus conversion equal £100, meaning you can not win more than £100 from the bonus funds.

Palazzo Sorlini Events: The new exclusive destination for weddings and events

In practice, my request cleared in under 20 hours with zero hiccups. Poker, the quintessential card game, often springs to mind when thinking about gambling. One of the best things about online slots, besides the obvious fun, is the convenience of accessing them on your mobile device. Our review team assessed each casino for its game selection, payment methods, and player satisfaction, ensuring you can use free spins and bonus funds to win real cash prizes safely. Plus, you can get 50% off on your first purchase after joining. This offer is exclusively for new players and applies only to the first deposit. Take is a roadmap to select your best platform from my top 10 crypto casino list. Crypto players can snag up to $3,750 across their first three deposits, with a 125% match each time. Don’t want to bother with it. We may receive commissions from partners, but this never affects our impartiality or the integrity of our recommendations. Blackjack is the classic casino card game that everyone knows the rules to. Different games will also have different weightings in terms of wagering requirements. Want to integrate AI into your sports betting. Die Angabe des Deutschland Tickets beeinflusst die Stornierungs und Erstattungsmöglichkeiten nicht. Operators provide the tools, but you must decide when to use them. Learning the optimal moves can shave the casino’s advantage down dramatically, giving you more long‑term staying power. Either way, you’ve not had to spend a penny to play with these offers. ✓ Bonus: 150% up to R1,500, 4x wagering on sports odds 2. Max one claim per player. You can also play on the go with the bet365 Casino mobile app, which is a great approximation of the desktop site and allows for easy access to other bet365 products. Let’s be real: most “top casino” lists have the same boring sites. Bonuses create buffer against losing streaks that destroy bankrolls quickly.