Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.31% covered (warning)
84.31%
86 / 102
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Settings_Payments
84.31% covered (warning)
84.31%
86 / 102
40.00% covered (danger)
40.00%
2 / 5
14.76
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 record_page_visit_time
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 add_section
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 settings
98.36% covered (success)
98.36%
60 / 61
0.00% covered (danger)
0.00%
0 / 1
3
 print_attempts_per_interval_settings_field
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2/**
3 * Settings page to display in WooCommerce.
4 *
5 * @see /wp-admin/admin.php?page=wc-settings&tab=advanced&section=checkout-rate-limiting
6 *
7 * @author     BrianHenryIE <BrianHenryIE@gmail.com>
8 * @link       https://BrianHenryIE.com
9 * @since      1.0.0
10 * @package brianhenryie/bh-wc-checkout-rate-limiter
11 */
12
13namespace BrianHenryIE\Checkout_Rate_Limiter\WooCommerce;
14
15use BrianHenryIE\Checkout_Rate_Limiter\Settings_Interface;
16use Psr\Log\LoggerAwareTrait;
17use Psr\Log\LoggerInterface;
18use Psr\Log\LogLevel;
19use WC_Admin_Settings;
20
21/**
22 * * Adds the settings section to WooCommerce, under Payments.
23 * * Provides the list of settings.
24 * * Contains a custom setting type for printing two integer input boxes alongside each other.
25 *
26 * Class Settings_Payments
27 *
28 * @package brianhenryie/bh-wc-checkout-rate-limiter
29 */
30class Settings_Payments {
31
32    use LoggerAwareTrait;
33
34    /**
35     * The plugin's settings.
36     *
37     * @var Settings_Interface
38     */
39    protected Settings_Interface $settings;
40
41    /**
42     * Instantiate.
43     *
44     * @param Settings_Interface $settings The plugin settings.
45     * @param LoggerInterface    $logger PSR logger.
46     */
47    public function __construct( Settings_Interface $settings, LoggerInterface $logger ) {
48        $this->settings = $settings;
49        $this->setLogger( $logger );
50    }
51
52    /**
53     * Record the last visited time of the settings page so the admin notice can be hidden.
54     *
55     * @hooked current_screen
56     */
57    public function record_page_visit_time(): void {
58
59        if ( ! function_exists( 'wc_get_current_admin_url' ) ) {
60            return;
61        }
62
63        $wc_admin_url = wc_get_current_admin_url();
64
65        if ( empty( $wc_admin_url ) ) {
66            return;
67        }
68
69        $url_parts = wp_parse_url( $wc_admin_url );
70
71        if ( empty( $url_parts['query'] ) ) {
72            return;
73        }
74
75        $query_parts = array();
76        wp_parse_str( $url_parts['query'], $query_parts );
77
78        if ( ! isset( $query_parts['section'] ) || 'checkout-rate-limiting' !== $query_parts['section'] ) {
79            return;
80        }
81
82        update_option( 'bh_wc_checkout_rate_limiter_visited_settings_time', time() );
83    }
84
85    /**
86     * Add the settings section to WordPress/WooCommerce/Settings/Advanced/Rate Limiting
87     *
88     * /wp-admin/admin.php?page=wc-settings&tab=advanced&section=checkout-rate-limiting
89     *
90     * @hooked woocommerce_get_sections_checkout
91     * @see \WC_Settings_Advanced::get_sections()
92     *
93     * @param array<string, string> $sections The horizontal subsections in the WooCommerce settings.
94     * @return array<string, string>
95     */
96    public function add_section( array $sections ): array {
97
98        $sections['checkout-rate-limiting'] = 'Rate Limiting';
99
100        return $sections;
101    }
102
103    /**
104     * Adds the settings:
105     * * Title + description
106     * * Enable/disable
107     * * Rate limits: attempts per interval
108     * * Log level
109     *
110     * * Empty cart?!
111     *
112     * @hooked woocommerce_get_settings_checkout
113     * @see \WC_Settings_Advanced::get_settings()
114     *
115     * @param array<int|string, array<string, mixed>> $settings WC_Settings_API settings fields.
116     * @param string                                  $current_section The slug of the current horizontal sub-section.
117     *
118     * @return array<int|string, array<string, mixed>>
119     */
120    public function settings( array $settings, string $current_section ): array {
121
122        if ( 'checkout-rate-limiting' !== $current_section ) {
123            return $settings;
124        }
125
126        $settings[] = array(
127            'title' => 'Checkout Rate-Limiting',
128            'type'  => 'title',
129            'desc'  => 'Each time a customer clicks "Place Order", their IP address is checked to see how many times they have already tried to place an order recently.',
130            'id'    => 'checkout-rate-limiting',
131        );
132
133        // TODO: Add link to GitHub. Add link to logs.
134
135        $settings['bh_wc_checkout_rate_limiter_checkout_rate_limiting_enabled'] = array(
136            'title'   => __( 'Limit checkout attempts', 'bh-wc-checkout-rate-limiter' ),
137            'desc'    => __( 'When enabled, each IP address can only make as many attempts at payment as specified below.', 'bh-wc-checkout-rate-limiter' ),
138            'id'      => 'bh_wc_checkout_rate_limiter_checkout_rate_limiting_enabled',
139            'type'    => 'checkbox',
140            'default' => 'yes',
141        );
142
143        // Attempts per interval.
144        $settings['bh_wc_checkout_rate_limiter_allowed_attempts_per_interval_1'] = array(
145            'title'   => '',
146            'id'      => 'bh_wc_checkout_rate_limiter_allowed_attempts_per_interval_1',
147            'type'    => 'attempts_per_interval',
148            'default' => array(
149                'interval' => 60,
150                'attempts' => 2,
151            ),
152        );
153        $settings['bh_wc_checkout_rate_limiter_allowed_attempts_per_interval_2'] = array(
154            'title'   => '',
155            'id'      => 'bh_wc_checkout_rate_limiter_allowed_attempts_per_interval_2',
156            'type'    => 'attempts_per_interval',
157            'default' => array(
158                'interval' => 120,
159                'attempts' => 3,
160            ),
161        );
162        $settings['bh_wc_checkout_rate_limiter_allowed_attempts_per_interval_3'] = array(
163            'title'   => '',
164            'id'      => 'bh_wc_checkout_rate_limiter_allowed_attempts_per_interval_3',
165            'type'    => 'attempts_per_interval',
166            'default' => array(
167                'interval' => 300,
168                'attempts' => 5,
169            ),
170        );
171
172        $log_levels        = array( 'none', LogLevel::ERROR, LogLevel::WARNING, LogLevel::NOTICE, LogLevel::INFO, LogLevel::DEBUG );
173        $log_levels_option = array();
174        foreach ( $log_levels as $log_level ) {
175            $log_levels_option[ $log_level ] = ucfirst( $log_level );
176        }
177
178        $settings['bh_wc_checkout_rate_limiter_log_level'] = array(
179            'title'    => __( 'Log Level', 'bh-wc-checkout-rate-limiter' ),
180            'label'    => __( 'Enable Logging', 'bh-wc-checkout-rate-limiter' ),
181            'type'     => 'select',
182            'options'  => $log_levels_option,
183            'desc'     => __( 'Increasingly detailed levels of logs. ', 'bh-wc-checkout-rate-limiter' ) . '<a href="' . admin_url( 'admin.php?page=bh-wc-checkout-rate-limiter-logs' ) . '">View Logs</a>',
184            'desc_tip' => false,
185            'default'  => 'notice',
186            'id'       => 'bh_wc_checkout_rate_limiter_log_level',
187        );
188
189        $settings[] = array(
190            'type' => 'sectionend',
191            'id'   => 'checkout-rate-limiting',
192        );
193
194        return $settings;
195    }
196
197    /**
198     *
199     * // TODO: Is there a better name than $value here? (since it's an array with a "value" element).
200     *
201     * @see \WC_Admin_Settings::output_fields()
202     *
203     * @hooked woocommerce_admin_field_attempts_per_interval
204     * @param array<string, mixed> $value The template data to output.
205     */
206    public function print_attempts_per_interval_settings_field( array $value ): void {
207
208        $option_value = $value['value'];
209
210        if ( ! isset( $option_value['attempts'] ) ) {
211            $option_value['attempts'] = '';
212        }
213
214        if ( ! isset( $option_value['interval'] ) ) {
215            $option_value['interval'] = '';
216        }
217
218        // Description handling... copied from WooCommerce WC_Admin_Settings.
219        $field_description = WC_Admin_Settings::get_field_description( $value );
220        $description       = $field_description['description'];
221        $tooltip_html      = $field_description['tooltip_html'];
222
223        ?>
224        <tr valign="top">
225            <th scope="row" class="titledesc">
226                <label for="<?php echo esc_attr( $value['id'] ); ?>"><?php echo esc_html( $value['title'] ); ?> <?php
227                    // Already escaped in WC_Admin_Settings::get_field_description().
228                    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
229                    echo $tooltip_html;
230                ?>
231                    </label>
232            </th>
233            <td class="forminp">
234                <input
235                    name="<?php echo esc_attr( $value['id'] ); ?>[attempts]"
236                    id="<?php echo esc_attr( $value['id'] ); ?>"
237                    type="number"
238                    style="width: 80px;"
239                    value="<?php echo esc_attr( $option_value['attempts'] ); ?>"
240                    class="<?php echo esc_attr( $value['class'] ); ?>"
241                    step="1"
242                    min="1"
243                />&nbsp;attempts per&nbsp;
244                <input
245                    name="<?php echo esc_attr( $value['id'] ); ?>[interval]"
246                    id="<?php echo esc_attr( $value['id'] ); ?>"
247                    type="number"
248                    style="width: 80px;"
249                    value="<?php echo esc_attr( $option_value['interval'] ); ?>"
250                    class="<?php echo esc_attr( $value['class'] ); ?>"
251                    step="1"
252                    min="0"
253                />&nbspseconds.
254            </td>
255        </tr>
256        <?php
257    }
258}