• File: RateLimit.php
  • Full Path: /home/matthif/www/wp-content/plugins/wpforms-lite/src/Integrations/Stripe/RateLimit.php
  • Date Modified: 02/16/2024 11:45 AM
  • File size: 8.31 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php

namespace WPForms\Integrations\Stripe;

/**
 * Stripe error rate limiting.
 *
 * @since 1.8.2
 */
final class RateLimit {

    
/**
     * Allowed number of attempts.
     *
     * @since 1.8.2
     *
     * @var int
     */
    
private $allowed_attempts;

    
/**
     * Rate Limit block expiration time.
     *
     * @since 1.8.2
     *
     * @var int
     */
    
private $expires_in;

    
/**
     * Perform certain things on class init.
     *
     * @since 1.8.2
     */
    
public function init() {

        
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
        /**
         * This filter allow to modify Stripe rate limit attempts count.
         *
         * @since 1.8.2
         *
         * @param int $count Attempts count.
         */
        
$this->allowed_attempts = (int) apply_filters'wpforms_stripe_rate_limit_allowed_attempts');

        
/**
         * This filter allow to modify Stripe rate limit expiration time.
         *
         * @since 1.8.2
         *
         * @param int $expires_in Expiration time.
         */
        
$this->expires_in = (int) apply_filters'wpforms_stripe_rate_limit_expires_in'HOUR_IN_SECONDS );
        
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
    
}

    
/**
     * Check if rate limit is under threshold and passes.
     *
     * @since 1.8.2
     *
     * @return bool
     */
    
public function is_ok() {

        
$entry $this->get_entry();

        if ( empty( 
$entry['attempts'] ) ) {
            return 
true;
        }

        if ( 
$entry['attempts'] < $this->allowed_attempts ) {
            return 
true;
        }

        
$this->increment_attempts$entry );

        return 
false;
    }

    
/**
     * Increment the number of attempts for a specific IP address.
     *
     * @since 1.8.2
     *
     * @param array $entry Rate limit entry data.
     *
     * @return bool
     */
    
public function increment_attempts$entry = [] ) {

        if ( empty( 
$entry ) ) {
            
$entry $this->get_entry();
        }

        
$entry['attempts'] = (int) $entry['attempts'] + 1;

        return 
$this->update_entry$entry['attempts'] );
    }

    
/**
     * Get rate limit entry id based on IP address.
     *
     * @since 1.8.2
     *
     * @return string
     */
    
private function get_entry_id() {

        return 
'wpforms_stripe_attempt_' wp_hashwpforms_get_ip() );
    }

    
/**
     * Get rate limit entry attempts and expiration data.
     *
     * @since 1.8.2
     *
     * @return array
     */
    
private function get_entry() {

        
$storage  $this->get_storage_type();
        
$entry_id $this->get_entry_id();

        if ( 
$storage === 'file' ) {
            return 
$this->get_file_entry$entry_id );
        }

        if ( 
$storage === 'transient' ) {
            return 
$this->get_transient_entry$entry_id );
        }

        return [
            
'attempts'   => false,
            
'expiration' => false,
        ];
    }

    
/**
     * Update rate limit entry attempts and expiration data.
     *
     * @since 1.8.2
     *
     * @param int $attempts Number of attempts to set.
     *
     * @return bool
     */
    
private function update_entry$attempts ) {

        
$storage  $this->get_storage_type();
        
$entry_id $this->get_entry_id();

        if ( 
$storage === 'file' ) {
            return 
$this->update_file_entry$entry_id$attempts );
        }

        if ( 
$storage === 'transient' ) {
            return 
$this->update_transient_entry$entry_id$attempts );
        }

        return 
false;
    }

    
/**
     * Get a storage type where rate limit entries are saved.
     *
     * @since 1.8.2
     *
     * @return string
     */
    
private function get_storage_type() {

        
$file $this->get_file_path();

        if ( empty( 
$file ) ) {
            return 
'transient';
        }

        if ( ! 
file_exists$file ) ) {
            
$this->create_file$file );
        }

        if ( ! 
is_writable$file ) ) {
            return 
'transient';
        }

        return 
'file';
    }

    
/**
     * Get file path to store the rate limit entries in.
     *
     * @since 1.8.2
     *
     * @return string
     */
    
private function get_file_path() {

        if ( 
function_exists'wpforms_upload_dir' ) ) {
            
$upload_dir wpforms_upload_dir();
        }

        if ( isset( 
$upload_dir['path'] ) ) {
            
$upload_dir['path'] = trailingslashit$upload_dir['path'] ) . 'stripe';
        }

        
$file_name wp_hashsite_url() ) . '-rate-limiting.log';

        return isset( 
$upload_dir['path'] ) ? wp_normalize_pathtrailingslashit$upload_dir['path'] ) . $file_name ) : '';
    }

    
/**
     * Create index.html file in the specified directory if it doesn't exist.
     *
     * @since 1.8.2
     *
     * @return bool True if file exists or was successfully created, false on failure.
     */
    
private function create_index_html_file() {

        
$file $this->get_file_path();

        if ( empty( 
$file ) ) {
            return 
false;
        }

        
$index_file wp_normalize_pathtrailingslashitdirname$file ) ) . 'index.html' );

        
// Do nothing if index.html exists in the directory.
        
if ( file_exists$index_file ) ) {
            return 
true;
        }

        
// Create empty index.html.
        // phpcs:ignore WordPress.WP.AlternativeFunctions
        
return file_put_contents$index_file'' ) !== false;
    }

    
/**
     * Create a file path to store the rate limit entries in.
     *
     * @since 1.8.2
     *
     * @param string $file File path.
     *
     * @return bool
     */
    
private function create_file$file ) {

        if ( ! 
wp_mkdir_pdirname$file ) ) ) {
            return 
false;
        }

        if ( ! 
$this->create_index_html_file() ) {
            return 
false;
        }

        
// phpcs:ignore WordPress.WP.AlternativeFunctions
        
if ( file_put_contents$file'' ) === false ) {
            return 
false;
        }

        
chmod$file0664 );

        return 
true;
    }

    
/**
     * Read full contents of a rate limit entries file.
     *
     * @since 1.8.2
     *
     * @return array
     */
    
private function read_whole_file() {

        
$file $this->get_file_path();

        if ( empty( 
$file ) ) {
            return [];
        }

        
$contents file_get_contents$file );
        
$contents json_decode$contentstrue );

        return 
is_array$contents ) ? $contents : [];
    }

    
/**
     * Write full contents to a rate limit entries file.
     *
     * @since 1.8.2
     *
     * @param array $contents Array of all rate limit entries.
     *
     * @return bool
     */
    
private function write_whole_file$contents ) {

        if ( ! 
is_array$contents ) ) {
            return 
false;
        }

        
$file $this->get_file_path();

        if ( ! 
is_writable$file ) ) {
            return 
false;
        }

        
// phpcs:ignore WordPress.WP.AlternativeFunctions
        
return (bool) file_put_contents$filewp_json_encode$contents ) );
    }

    
/**
     * Filter out the expired rate limit entries from a file.
     *
     * @since 1.8.2
     *
     * @param array  $contents Array of all rate limit entries.
     * @param string $entry_id Rate limit entry id.
     *
     * @return array
     */
    
private function filter_expired_file_entry$contents$entry_id ) {

        
$expiration = isset( $contents$entry_id ]['expiration'] ) ? (int) $contents$entry_id ]['expiration'] : false;

        if ( empty( 
$expiration ) ) {
            return 
$contents;
        }

        if ( 
$expiration >= time() ) {
            return 
$contents;
        }

        unset( 
$contents$entry_id ] );

        
$this->write_whole_file$contents );

        return 
$contents;
    }

    
/**
     * Get rate limit entry attempts and expiration data from a file.
     *
     * @since 1.8.2
     *
     * @param string $entry_id Rate limit entry id.
     *
     * @return array
     */
    
private function get_file_entry$entry_id ) {

        
$contents $this->read_whole_file();
        
$contents $this->filter_expired_file_entry$contents$entry_id );

        return [
            
'attempts'   => isset( $contents$entry_id ]['attempts'] ) ? $contents$entry_id ]['attempts'] : false,
            
'expiration' => isset( $contents$entry_id ]['expiration'] ) ? $contents$entry_id ]['expiration'] : false,
        ];
    }

    
/**
     * Update rate limit entry attempts and expiration data in a file.
     *
     * @since 1.8.2
     *
     * @param string $entry_id Rate limit entry id.
     * @param int    $attempts Number of attempts to set.
     *
     * @return bool
     */
    
private function update_file_entry$entry_id$attempts ) {

        if ( ! 
$this->create_index_html_file() ) {
            return 
false;
        }

        
$contents $this->read_whole_file();

        
$contents$entry_id ] = [
            
'attempts'   => $attempts,
            
'expiration' => time() + $this->expires_in,
        ];

        return 
$this->write_whole_file$contents );
    }

    
/**
     * Get rate limit entry attempts and expiration data from a transient.
     *
     * @since 1.8.2
     *
     * @param string $entry_id Rate limit entry id.
     *
     * @return array
     */
    
private function get_transient_entry$entry_id ) {

        return [
            
'attempts'   => get_transient$entry_id ),
            
'expiration' => get_option'_transient_timeout_' $entry_id ),
        ];
    }

    
/**
     * Update rate limit entry attempts and expiration data in a transient.
     *
     * @since 1.8.2
     *
     * @param string $entry_id Rate limit entry id.
     * @param int    $attempts Number of attempts to set.
     *
     * @return bool
     */
    
private function update_transient_entry$entry_id$attempts ) {

        return 
set_transient$entry_id$attempts$this->expires_in );
    }
}