Completed
Push — update/lazily-load-recaptcha-i... ( b27ee1 )
by
unknown
36:01 queued 27:34
created

Jetpack_ReCaptcha::get_default_config()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Class that handles reCAPTCHA.
5
 */
6
class Jetpack_ReCaptcha {
7
8
	/**
9
	 * URL to which requests are POSTed.
10
	 *
11
	 * @const string
12
	 */
13
	const VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
14
15
	/**
16
	 * Site key to use in HTML code.
17
	 *
18
	 * @var string
19
	 */
20
	private $site_key;
21
22
	/**
23
	 * Shared secret for the site.
24
	 *
25
	 * @var string
26
	 */
27
	private $secret_key;
28
29
	/**
30
	 * Config for reCAPTCHA instance.
31
	 *
32
	 * @var array
33
	 */
34
	private $config;
35
36
	/**
37
	 * Error codes returned from reCAPTCHA API.
38
	 *
39
	 * @see https://developers.google.com/recaptcha/docs/verify
40
	 *
41
	 * @var array
42
	 */
43
	private $error_codes;
44
45
	/**
46
	 * Create a configured instance to use the reCAPTCHA service.
47
	 *
48
	 * @param string $site_key   Site key to use in HTML code.
49
	 * @param string $secret_key Shared secret between site and reCAPTCHA server.
50
	 * @param array  $config     Config array to optionally configure reCAPTCHA instance.
51
	 */
52
	public function __construct( $site_key, $secret_key, $config = array() ) {
53
		$this->site_key   = $site_key;
54
		$this->secret_key = $secret_key;
55
		$this->config     = wp_parse_args( $config, $this->get_default_config() );
0 ignored issues
show
Documentation introduced by
$this->get_default_config() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation Bug introduced by
It seems like wp_parse_args($config, $...->get_default_config()) can be null. However, the property $config is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
56
57
		$this->error_codes = array(
58
			'missing-input-secret'   => __( 'The secret parameter is missing', 'jetpack' ),
59
			'invalid-input-secret'   => __( 'The secret parameter is invalid or malformed', 'jetpack' ),
60
			'missing-input-response' => __( 'The response parameter is missing', 'jetpack' ),
61
			'invalid-input-response' => __( 'The response parameter is invalid or malformed', 'jetpack' ),
62
			'invalid-json'           => __( 'Invalid JSON', 'jetpack' ),
63
			'unexpected-response'    => __( 'Unexpected response', 'jetpack' ),
64
			'unexpected-hostname'    => __( 'Unexpected hostname', 'jetpack' ),
65
		);
66
	}
67
68
	/**
69
	 * Get default config for this reCAPTCHA instance.
70
	 *
71
	 * @return array Default config
72
	 */
73
	public function get_default_config() {
74
		return array(
75
			'language'       => get_locale(),
76
			'script_async'   => false,
77
			'script_defer'   => true,
78
			'script_lazy'    => false,
79
			'tag_class'      => 'g-recaptcha',
80
			'tag_attributes' => array(
81
				'theme'    => 'light',
82
				'type'     => 'image',
83
				'tabindex' => 0,
84
			),
85
		);
86
	}
87
88
	/**
89
	 * Calls the reCAPTCHA siteverify API to verify whether the user passes
90
	 * CAPTCHA test.
91
	 *
92
	 * @param string $response  The value of 'g-recaptcha-response' in the submitted
93
	 *                          form.
94
	 * @param string $remote_ip The end user's IP address.
95
	 *
96
	 * @return bool|WP_Error Returns true if verified. Otherwise WP_Error is returned.
97
	 */
98
	public function verify( $response, $remote_ip ) {
99
		// No need make a request if response is empty.
100
		if ( empty( $response ) ) {
101
			return new WP_Error( 'missing-input-response', $this->error_codes['missing-input-response'], 400 );
102
		}
103
104
		$resp = wp_remote_post( self::VERIFY_URL, $this->get_verify_request_params( $response, $remote_ip ) );
105
		if ( is_wp_error( $resp ) ) {
106
			return $resp;
107
		}
108
109
		$resp_decoded = json_decode( wp_remote_retrieve_body( $resp ), true );
110
		if ( ! $resp_decoded ) {
111
			return new WP_Error( 'invalid-json', $this->error_codes['invalid-json'], 400 );
112
		}
113
114
		// Default error code and message.
115
		$error_code    = 'unexpected-response';
116
		$error_message = $this->error_codes['unexpected-response'];
117
118
		// Use the first error code if exists.
119
		if ( isset( $resp_decoded['error-codes'] ) && is_array( $resp_decoded['error-codes'] ) ) {
120
			if ( isset( $resp_decoded['error-codes'][0] ) && isset( $this->error_codes[ $resp_decoded['error-codes'][0] ] ) ) {
121
				$error_message = $this->error_codes[ $resp_decoded['error-codes'][0] ];
122
				$error_code    = $resp_decoded['error-codes'][0];
123
			}
124
		}
125
126
		if ( ! isset( $resp_decoded['success'] ) ) {
127
			return new WP_Error( $error_code, $error_message );
128
		}
129
130
		if ( true !== $resp_decoded['success'] ) {
131
			return new WP_Error( $error_code, $error_message );
132
		}
133
		// Validate the hostname matches expected source
134
		if ( isset( $resp_decoded['hostname'] ) ) {
135
			$url = wp_parse_url( get_home_url() );
136
137
			/**
138
			 * Allow other valid hostnames.
139
			 *
140
			 * This can be useful in cases where the token hostname is expected to be
141
			 * different from the get_home_url (ex. AMP recaptcha token contains a different hostname)
142
			 *
143
			 * @module sharedaddy
144
			 *
145
			 * @since 9.1.0
146
			 *
147
			 * @param array [ $url['host'] ] List of the valid hostnames to check against.
148
			 */
149
			$valid_hostnames = apply_filters( 'jetpack_recaptcha_valid_hostnames', array( $url['host'] ) );
150
151
			if ( ! in_array( $resp_decoded['hostname'], $valid_hostnames, true ) ) {
152
				return new WP_Error( 'unexpected-host', $this->error_codes['unexpected-hostname'] );
153
			}
154
		}
155
156
		return true;
157
	}
158
159
	/**
160
	 * Get siteverify request parameters.
161
	 *
162
	 * @param string $response  The value of 'g-recaptcha-response' in the submitted
163
	 *                          form.
164
	 * @param string $remote_ip The end user's IP address.
165
	 *
166
	 * @return array
167
	 */
168
	public function get_verify_request_params( $response, $remote_ip ) {
169
		return array(
170
			'body' => array(
171
				'secret'   => $this->secret_key,
172
				'response' => $response,
173
				'remoteip' => $remote_ip,
174
			),
175
			'sslverify' => true,
176
		);
177
	}
178
179
	/**
180
	 * Get reCAPTCHA HTML to render.
181
	 *
182
	 * @return string
183
	 */
184
	public function get_recaptcha_html() {
185
		$url = sprintf(
186
			'https://www.google.com/recaptcha/api.js?hl=%s',
187
			rawurlencode( $this->config['language'] )
188
		);
189
190
		$html = sprintf(
191
			'
192
			<div
193
				class="%s"
194
				data-sitekey="%s"
195
				data-theme="%s"
196
				data-type="%s"
197
				data-tabindex="%s"
198
				data-lazy="%s"
199
				data-url="%s"></div>
200
			',
201
			esc_attr( $this->config['tag_class'] ),
202
			esc_attr( $this->site_key ),
203
			esc_attr( $this->config['tag_attributes']['theme'] ),
204
			esc_attr( $this->config['tag_attributes']['type'] ),
205
			esc_attr( $this->config['tag_attributes']['tabindex'] ),
206
			$this->config['script_lazy'] ? 'true' : 'false',
207
			esc_attr( $url )
208
		);
209
210
		if ( ! $this->config['script_lazy'] ) {
211
			$html = $html . sprintf(
212
				// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
213
				'<script src="%s"%s%s></script>
214
				',
215
				$url,
216
				$this->config['script_async'] && ! $this->config['script_defer'] ? ' async' : '',
217
				$this->config['script_defer'] ? ' defer' : ''
218
			);
219
		}
220
221
		return $html;
222
	}
223
}
224