options = $options; $this->features = $features; } /** * Whether a feature is available on this site. * * @param string $feature Feature id. */ public function has( string $feature ): bool { return $this->features->is_available( $feature ); } /** * A stored setting, or null when no module registers it. * * @param string $section Section id. * @param string $field Field id. * @return mixed */ public function option( string $section, string $field ) { return $this->options->get( $section, $field ); } /** * Inline below the reviews or in a dialog opened by "Write a review". */ public function mode(): string { $stored = (string) $this->options->get( 'form', 'mode' ); $mode = $this->has( 'inline_modal_form' ) && 'modal' === $stored ? 'modal' : 'inline'; /** * Filter where the review form opens. Add-ons that register other placements for the * form.mode setting return them here. * * @since 1.0.3 * @param string $mode "inline" or "modal". * @param string $stored The form.mode setting. * @param FormConfig $config Form configuration. */ return (string) apply_filters( 'wpipr_form_mode', $mode, $stored, $this ); } /** * Review title: off, optional or required. */ public function title_mode(): string { if ( ! $this->has( 'review_title' ) ) { return 'off'; } $mode = (string) $this->options->get( 'form', 'title' ); return in_array( $mode, array( 'off', 'optional', 'required' ), true ) ? $mode : 'optional'; } /** * Longest title. */ public function title_max(): int { return (int) $this->options->get( 'form', 'title_max' ); } /** * Shortest review text (0 for no minimum). */ public function content_min(): int { return min( (int) $this->options->get( 'form', 'content_min' ), $this->content_max() ); } /** * Longest review text. */ public function content_max(): int { return (int) $this->options->get( 'form', 'content_max' ); } /** * Ask "Would you recommend this product?". */ public function recommend(): bool { return $this->has( 'recommend_flag' ) && (bool) $this->options->get( 'form', 'recommend' ); } /** * Let reviewers choose how their name is shown. */ public function display_name_choice(): bool { return $this->has( 'display_name_options' ) && (bool) $this->options->get( 'form', 'display_name_choice' ); } /** * Default name format for new reviews. */ public function default_display_name(): string { $format = (string) $this->options->get( 'display', 'name_format' ); return in_array( $format, array( 'full', 'initial', 'anonymous' ), true ) ? $format : 'full'; } /** * Require a consent checkbox. */ public function consent(): bool { return $this->has( 'consent' ) && (bool) $this->options->get( 'form', 'consent' ); } /** * Consent text as plain text. "%privacy_policy%" becomes a link when a policy page exists. */ public function consent_text(): string { $text = trim( (string) $this->options->get( 'form', 'consent_text' ) ); if ( '' === $text ) { $text = __( 'I agree that my review, name and any photos are published on this site, as described in the %privacy_policy%.', 'wpinterface-product-reviews' ); } return $text; } /** * Message after a review is published. */ public function success_text(): string { $text = trim( (string) $this->options->get( 'form', 'success_text' ) ); return '' !== $text ? $text : __( 'Thank you! Your review is now live.', 'wpinterface-product-reviews' ); } /** * Message after a review is held for approval. */ public function pending_text(): string { $text = trim( (string) $this->options->get( 'form', 'pending_text' ) ); return '' !== $text ? $text : __( 'Thank you! Your review is awaiting approval and will appear once it has been checked.', 'wpinterface-product-reviews' ); } /** * Accept photos. */ public function photos(): bool { return $this->has( 'photo_reviews' ) && (bool) $this->options->get( 'media', 'photos' ); } /** * Most photos per review. */ public function max_photos(): int { if ( ! $this->photos() ) { return 0; } /** * Filter how many photos one review may have. * * @since 1.0.3 * @param int $max Most photos per review (1). * @param FormConfig $config Form configuration. */ return max( 1, (int) apply_filters( 'wpipr_form_max_photos', 1, $this ) ); } /** * Largest photo upload in bytes. */ public function max_photo_bytes(): int { return (int) $this->options->get( 'media', 'max_photo_mb' ) * MB_IN_BYTES; } /** * Other kinds of file the form takes besides photos: none, unless an add-on handles them * (see MediaUploader::add_kind()). * * @return array{max: int, bytes: int, seconds: int, mimes: array} */ public function video_limits(): array { $none = array( 'max' => 0, 'bytes' => 0, 'seconds' => 0, 'mimes' => array(), ); /** * Filter the videos the review form takes: how many, how large (bytes), how long * (seconds) and which types (extension pattern => MIME type). None by default. * * @since 1.0.3 * @param array{max: int, bytes: int, seconds: int, mimes: array} $limits Limits. * @param FormConfig $config Form configuration. */ $limits = apply_filters( 'wpipr_form_video_limits', $none, $this ); $limits = is_array( $limits ) ? $limits + $none : $none; return array( 'max' => max( 0, (int) $limits['max'] ), 'bytes' => max( 0, (int) $limits['bytes'] ), 'seconds' => max( 0, (int) $limits['seconds'] ), 'mimes' => is_array( $limits['mimes'] ) ? array_map( 'strval', $limits['mimes'] ) : array(), ); } /** * Most videos per review. */ public function max_videos(): int { return $this->video_limits()['max']; } /** * Largest video upload in bytes. */ public function max_video_bytes(): int { return $this->video_limits()['bytes']; } /** * Longest video in seconds. */ public function max_video_seconds(): int { return $this->video_limits()['seconds']; } /** * Allowed photo types: extension pattern => MIME type, as wp_check_filetype() expects. * * @return array */ public function photo_mimes(): array { return array( 'jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', ); } /** * Allowed video types. * * @return array */ public function video_mimes(): array { return $this->video_limits()['mimes']; } /** * Seconds a person needs at least to fill in the form (0 turns the check off). */ public function min_seconds(): int { return (int) $this->options->get( 'antispam', 'min_seconds' ); } /** * Active CAPTCHA provider id, or "none". Needs both keys, and Lite includes Turnstile only. */ public function captcha(): string { $provider = (string) $this->options->get( 'antispam', 'captcha' ); if ( 'none' === $provider || '' === $this->captcha_site_key() || '' === $this->captcha_secret_key() ) { return 'none'; } if ( ! $this->captcha_available( $provider ) ) { return 'none'; } return $provider; } /** * Whether this edition runs a CAPTCHA provider. Lite includes Turnstile only. * * @param string $provider Provider id. */ public function captcha_available( string $provider ): bool { /** * Filter the CAPTCHA providers this site runs. * * @since 1.0.3 * @param list $providers Provider ids. */ $providers = apply_filters( 'wpipr_captcha_providers', self::CAPTCHAS ); return in_array( $provider, is_array( $providers ) ? $providers : self::CAPTCHAS, true ); } /** * CAPTCHA site key. */ public function captcha_site_key(): string { return trim( (string) $this->options->get( 'antispam', 'captcha_site_key' ) ); } /** * CAPTCHA secret key. */ public function captcha_secret_key(): string { return trim( (string) $this->options->get( 'antispam', 'captcha_secret_key' ) ); } /** * Reviews per hour from one IP address. */ public function submit_limit(): int { return (int) $this->options->get( 'antispam', 'submit_limit' ); } /** * Uploads per hour from one IP address. */ public function upload_limit(): int { return (int) $this->options->get( 'antispam', 'upload_limit' ); } /** * Whether visitors who are not logged in may review (WordPress and WooCommerce settings). */ public function guests_allowed(): bool { return ! get_option( 'comment_registration' ) && ! $this->verification_required(); } /** * WooCommerce's "Reviews can only be left by verified owners". */ public function verification_required(): bool { self::prime_woo_options(); return 'yes' === get_option( 'woocommerce_review_rating_verification_required' ); } /** * WooCommerce's star rating settings. * * @return array{enabled: bool, required: bool} */ public function ratings(): array { self::prime_woo_options(); return array( 'enabled' => wc_review_ratings_enabled(), 'required' => wc_review_ratings_enabled() && wc_review_ratings_required(), ); } /** * Name and email are required from guests (WordPress discussion setting). */ public function name_email_required(): bool { return (bool) get_option( 'require_name_email', 1 ); } /** * WooCommerce stores its review settings without autoload; read them in one query. */ private static function prime_woo_options(): void { wp_prime_option_caches( array( 'woocommerce_review_rating_verification_required', 'woocommerce_review_rating_required' ) ); } }