Completed
Push — try/gutenberg-separate-jetpack... ( e8dd3e...f0efb9 )
by Bernhard
39:26 queued 23:22
created

Jetpack_ReCaptcha::get_recaptcha_html()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 0
dl 0
loc 20
rs 9.6
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() );
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'   => true,
77
			'tag_class'      => 'g-recaptcha',
78
			'tag_attributes' => array(
79
				'theme'    => 'light',
80
				'type'     => 'image',
81
				'tabindex' => 0,
82
			),
83
		);
84
	}
85
86
	/**
87
	 * Calls the reCAPTCHA siteverify API to verify whether the user passes
88
	 * CAPTCHA test.
89
	 *
90
	 * @param string $response  The value of 'g-recaptcha-response' in the submitted
91
	 *                          form.
92
	 * @param string $remote_ip The end user's IP address.
93
	 *
94
	 * @return bool|WP_Error Returns true if verified. Otherwise WP_Error is returned.
95
	 */
96
	public function verify( $response, $remote_ip ) {
97
		// No need make a request if response is empty.
98
		if ( empty( $response ) ) {
99
			return new WP_Error( 'missing-input-response', $this->error_codes['missing-input-response'], 400 );
100
		}
101
102
		$resp = wp_remote_post( self::VERIFY_URL, $this->get_verify_request_params( $response, $remote_ip ) );
103
		if ( is_wp_error( $resp ) ) {
104
			return $resp;
105
		}
106
107
		$resp_decoded = json_decode( wp_remote_retrieve_body( $resp ), true );
108
		if ( ! $resp_decoded ) {
109
			return new WP_Error( 'invalid-json', $this->error_codes['invalid-json'], 400 );
110
		}
111
112
		// Default error code and message.
113
		$error_code    = 'unexpected-response';
114
		$error_message = $this->error_codes['unexpected-response'];
115
116
		// Use the first error code if exists.
117
		if ( isset( $resp_decoded['error-codes'] ) && is_array( $resp_decoded['error-codes'] ) ) {
118
			if ( isset( $resp_decoded['error-codes'][0] ) && isset( $this->error_codes[ $resp_decoded['error-codes'][0] ] ) ) {
119
				$error_message = $this->error_codes[ $resp_decoded['error-codes'][0] ];
120
				$error_code    = $resp_decoded['error-codes'][0];
121
			}
122
		}
123
124
		if ( ! isset( $resp_decoded['success'] ) ) {
125
			return new WP_Error( $error_code, $error_message );
126
		}
127
128
		if ( true !== $resp_decoded['success'] ) {
129
			return new WP_Error( $error_code, $error_message );
130
		}
131
132
		// Validate the hostname matches expected source
133
		if ( isset( $resp_decoded['hostname'] ) ) {
134
			$url = wp_parse_url( get_home_url() );
135
			if ( $url['host'] !== $resp_decoded['hostname'] ) {
136
				return new WP_Error( 'unexpected-host', $this->error_codes['unexpected-hostname'] );
137
			}
138
		}
139
140
		return true;
141
	}
142
143
	/**
144
	 * Get siteverify request parameters.
145
	 *
146
	 * @param string $response  The value of 'g-recaptcha-response' in the submitted
147
	 *                          form.
148
	 * @param string $remote_ip The end user's IP address.
149
	 *
150
	 * @return array
151
	 */
152
	public function get_verify_request_params( $response, $remote_ip ) {
153
		return array(
154
			'body' => array(
155
				'secret'   => $this->secret_key,
156
				'response' => $response,
157
				'remoteip' => $remote_ip,
158
			),
159
			'sslverify' => true,
160
		);
161
	}
162
163
	/**
164
	 * Get reCAPTCHA HTML to render.
165
	 *
166
	 * @return string
167
	 */
168
	public function get_recaptcha_html() {
169
		return sprintf(
170
			'
171
			<div
172
				class="%s"
173
				data-sitekey="%s"
174
				data-theme="%s"
175
				data-type="%s"
176
				data-tabindex="%s"></div>
177
			<script type="text/javascript" src="https://www.google.com/recaptcha/api.js?hl=%s"%s></script>
178
			',
179
			esc_attr( $this->config['tag_class'] ),
180
			esc_attr( $this->site_key ),
181
			esc_attr( $this->config['tag_attributes']['theme'] ),
182
			esc_attr( $this->config['tag_attributes']['type'] ),
183
			esc_attr( $this->config['tag_attributes']['tabindex'] ),
184
			rawurlencode( $this->config['language'] ),
185
			$this->config['script_async'] ? ' async' : ''
186
		);
187
	}
188
}
189