$skip Field ids to leave out.
*/
public function area_html( FormContext $context, string $area, array $skip = array() ): string {
$html = '';
foreach ( $this->fields->for_context( $context, $area ) as $field ) {
if ( ! in_array( $field->id(), $skip, true ) ) {
$html .= $field->render( $context );
}
}
return $html;
}
/**
* The plugin's fields for another plugin's comment form (WooCommerce's Product Review Form
* block), which keeps its own rating and text inputs. The trailing fields are added before
* that form's submit button until the form ends.
*
* @param \WC_Product $product Product.
*/
public function embed_fields( \WC_Product $product ): string {
$user_id = get_current_user_id();
if ( $this->submission->can_review( $product, $user_id ) instanceof \WP_Error || ( $user_id > 0 && null !== $this->own->find( $product->get_id(), $user_id ) ) ) {
return '';
}
$context = $this->submission->context( $product, $user_id );
$this->enqueue();
$this->after = '' . $this->area_html( $context, FormField::AREA_AFTER ) . $this->spam->render( $context ) . ClassicForm::hidden_fields( $product->get_id() ) . '
';
add_filter( 'comment_form_submit_field', array( $this, 'submit_field' ) );
add_action( 'comment_form_after', array( $this, 'end_embed' ) );
return '' . $this->area_html( $context, FormField::AREA_MAIN, array( 'rating', 'content' ) ) . '
'
. '';
}
/**
* Stop adding fields once the embedding form has been printed.
*/
public function end_embed(): void {
remove_filter( 'comment_form_submit_field', array( $this, 'submit_field' ) );
remove_action( 'comment_form_after', array( $this, 'end_embed' ) );
$this->after = null;
}
/**
* Put the trailing fields (display name, consent, spam protection) before the submit button.
*
* @param string $submit_field Submit button markup.
*/
public function submit_field( $submit_field ): string {
return null === $this->after ? (string) $submit_field : $this->after . '' . $submit_field;
}
/**
* Load the form script and the CAPTCHA script.
*/
public function enqueue(): void {
$this->assets->enqueue_style( Assets::FRONTEND_STYLE );
$this->assets->enqueue_script( self::SCRIPT );
$this->spam->enqueue();
/**
* Fires when the review form's script and styles are enqueued.
*
* @since 1.0.3
*/
do_action( 'wpipr_form_enqueued' );
}
/**
* Whether a signed-in author may change their review in the form. Reviews are final
* once sent unless an add-on allows editing them.
*
* @param \WP_Comment $review The author's review.
* @param int $user_id Author.
*/
private function can_edit( \WP_Comment $review, int $user_id ): bool {
/**
* Filter whether signed-in authors may edit their own review in the form.
*
* @since 1.0.3
* @param bool $editable Whether they may (false).
* @param \WP_Comment $review The review.
* @param int $user_id Author.
*/
return (bool) apply_filters( 'wpipr_can_edit_own_review', false, $review, $user_id );
}
/**
* Settings the form script needs for one form.
*
* @param FormContext $context Context.
* @param bool $use_put Edit through PUT /reviews/{id} (signed-in authors, where editing
* is allowed). Reviews opened from an email link are updated by
* posting the form again.
* @return array
*/
public function script_config( FormContext $context, bool $use_put = true ): array {
$product_id = $context->product->get_id();
$captcha = $this->spam->captcha();
$review_id = null !== $context->review && $use_put ? (int) $context->review->comment_ID : 0;
$config = array(
'product' => $product_id,
'review' => $review_id,
'auth' => $context->user_id > 0,
'submitUrl' => $review_id > 0 ? rest_url( 'wpinterface-product-reviews/v1/reviews/' . $review_id ) : rest_url( 'wpinterface-product-reviews/v1/products/' . $product_id . '/reviews' ),
'method' => $review_id > 0 ? 'PUT' : 'POST',
'deleteUrl' => $review_id > 0 ? rest_url( 'wpinterface-product-reviews/v1/reviews/' . $review_id ) : '',
'mediaUrl' => rest_url( 'wpinterface-product-reviews/v1/media' ),
'tokenUrl' => add_query_arg( 'product', $product_id, rest_url( 'wpinterface-product-reviews/v1/nonce' ) ),
'restNonceUrl' => admin_url( 'admin-ajax.php?action=rest-nonce' ),
'mode' => $this->config->mode(),
'dialogClass' => '',
'captcha' => null === $captcha ? '' : $captcha->id(),
'captchaKey' => null === $captcha ? '' : $this->config->captcha_site_key(),
'media' => array(
'photos' => $this->config->max_photos(),
'videos' => $this->config->max_videos(),
'photoBytes' => $this->config->max_photo_bytes(),
'videoBytes' => $this->config->max_video_bytes(),
'videoSeconds' => $this->config->max_video_seconds(),
'maxEdge' => FormConfig::MAX_IMAGE_EDGE,
'photoTypes' => array_values( array_unique( $this->config->photo_mimes() ) ),
'videoTypes' => array_values( array_unique( $this->config->video_mimes() ) ),
),
);
/**
* Filter the settings the form script gets for one form.
*
* @since 1.0.3
* @param array $config Settings.
* @param FormContext $context Form context.
*/
$filtered = apply_filters( 'wpipr_form_script_config', $config, $context );
return is_array( $filtered ) ? $filtered : $config;
}
/**
* What to tell the visitor above (or instead of) the form.
*
* @param \WP_Comment|null $existing The visitor's review of this product.
* @param bool $editable Whether they may edit it here.
* @return array{submitted: ?string, message: string, own: ?\WP_Comment}
*/
private function notice( ?\WP_Comment $existing, bool $editable ): array {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only flag set by the redirect after posting.
$submitted = isset( $_GET['wpipr_submitted'] ) ? sanitize_key( wp_unslash( $_GET['wpipr_submitted'] ) ) : '';
if ( in_array( $submitted, array( 'approved', 'pending' ), true ) ) {
return array(
'submitted' => $submitted,
'message' => 'approved' === $submitted ? $this->config->success_text() : $this->config->pending_text(),
'own' => $existing,
);
}
$message = '';
if ( null !== $existing ) {
$pending = '1' !== (string) $existing->comment_approved;
if ( $editable ) {
$message = $pending ? __( 'Your review is awaiting approval. You can still change it below.', 'wpinterface-product-reviews' ) : '';
} else {
$message = $pending
? __( 'Thanks for your review. It is awaiting approval.', 'wpinterface-product-reviews' )
/* translators: %s: date of the review. */
: sprintf( __( 'You reviewed this product on %s.', 'wpinterface-product-reviews' ), (string) wp_date( (string) get_option( 'date_format' ), (int) strtotime( $existing->comment_date_gmt . ' UTC' ) ) );
}
}
return array(
'submitted' => null,
'message' => $message,
'own' => $existing,
);
}
/**
* Review link token in the URL, when it is well formed.
*/
public static function query_token(): string {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Signed token, verified before use.
$token = isset( $_GET[ OrderLinkField::INPUT ] ) && is_string( $_GET[ OrderLinkField::INPUT ] ) ? sanitize_text_field( wp_unslash( $_GET[ OrderLinkField::INPUT ] ) ) : '';
return 1 === preg_match( '/^\d{1,20}\.\d{9,11}\.[A-Za-z0-9_-]{43}$/', $token ) ? $token : '';
}
/**
* Rating chosen by clicking a star in the email (1–5), or 0.
*/
private static function query_stars(): int {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Pre-selects a form value only.
$stars = isset( $_GET['wpipr_stars'] ) && is_scalar( $_GET['wpipr_stars'] ) ? absint( $_GET['wpipr_stars'] ) : 0;
return $stars >= 1 && $stars <= 5 ? $stars : 0;
}
/**
* A guest's pending review of this product, from WordPress's unapproved-comment cookie or link.
*
* @param int $product_id Product id.
*/
private function guest_pending_review( int $product_id ): ?\WP_Comment {
$email = function_exists( 'wp_get_unapproved_comment_author_email' ) ? wp_get_unapproved_comment_author_email() : '';
if ( '' === $email ) {
return null;
}
$review = $this->own->find( $product_id, 0, $email );
return null !== $review && '1' !== (string) $review->comment_approved ? $review : null;
}
}