Passed
Pull Request — master (#4)
by
unknown
13:26 queued 10:15
created

Client::verifyKey()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 26
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 10
Bugs 1 Features 0
Metric Value
cc 4
eloc 12
c 10
b 1
f 0
nc 8
nop 2
dl 0
loc 26
ccs 12
cts 12
cp 1
crap 4
rs 9.8666
1
<?php
2
namespace Gothick\AkismetClient;
3
4
use \Gothick\AkismetClient\Result\VerifyKeyResult;
5
use \Gothick\AkismetClient\Result\CommentCheckResult;
6
use \Gothick\AkismetClient\Result\SubmitHamResult;
7
use \Gothick\AkismetClient\Result\SubmitSpamResult;
8
9
/**
10
 * Akismet API client.
11
 * @author matt
12
 *
13
 */
14
class Client
15
{
16
	const VERB_VERIFY_KEY = 'verify-key';
17
	const VERB_COMMENT_CHECK = 'comment-check';
18
	const VERB_SUBMIT_SPAM = 'submit-spam';
19
	const VERB_SUBMIT_HAM = 'submit-ham';
20
	/**
21
	 * Akismet API key
22
	 *
23
	 * @var string
24
	 */
25
	private $api_key;
26
27
	/**
28
	 * Our Guzzle client.
29
	 * This can be passed in for DI, or if not we'll create a default one ouselves.
30
	 *
31
	 * @var \GuzzleHttp\Client
32
	 */
33
	private $guzzle_client;
34
35
	/**
36
	 * URL of the site using us.
37
	 * Akismet calls this "blog", because WordPress.
38
	 *
39
	 * @var string
40
	 */
41
	private $blog;
42
43
	/**
44
	 * Name of the site using us.
45
	 *
46
	 * @var string
47
	 */
48
	private $app_name;
49
50
	/**
51
	 * Version string of the site using us.
52
	 *
53
	 * @var string
54
	 */
55
	private $app_version;
56
57
	/**
58
	 * Version of this Client.
59
	 * Akismet likes to know. We should bump this every time
60
	 * we package a new version.
61
	 *
62
	 * @var string
63
	 */
64
	const VERSION = '0.1';
65
66
	/**
67
	 * Make an Akismet API client.
68
	 * Typically you'd provide an API key in $api_key, at which point you can make any call. Without the optional
69
	 * $api_key you're limited to calling verifyApiKey. Once you've verified a key you can call setApiKey()
70
	 * later and start using the rest of the API.
71
	 *
72
	 * @param string $app_url
73
	 *        	e.g. http://forum.example.com/
74
	 * @param string $app_name
75
	 *        	e.g. phpBB
76
	 * @param string $app_version
77
	 *        	e.g. 3.2.1
78
	 * @param string $api_key
79
	 *        	(optional) Akismet API key
80
	 * @param
81
	 *        	\GuzzleHttp\Client (optional) $guzzle_client. You can inject a mock, or a non-Curl-using Guzzle
82
	 *        	client here, say. Otherwise we'll just make one.
83
	 * @throws \Gothick\AkismetClient\AkismetException
84
	 */
85 69
	public function __construct($app_url, $app_name, $app_version, $api_key = null, $guzzle_client = null)
86
	{
87 69
		if ((empty($app_url)) || (empty($app_name)) || (empty($app_version)))
88
		{
89 6
			throw new AkismetException('Must supply app URL, name and version in ' . __METHOD__);
90
		}
91
		// The Akismet API calls it a blog, so keep consistent.
92 63
		$this->blog = $app_url;
93
94 63
		$this->app_name = $app_name;
95 63
		$this->app_version = $app_version;
96 63
		$this->api_key = $api_key;
97
98
		// Our client is passed in, as dependency injection is helpful for
99
		// testing, but in the normal course of things we'll probably just
100
		// create it ourselves.
101 63
		$this->guzzle_client = $guzzle_client;
102 63
		if (!isset($this->guzzle_client))
103
		{
104 9
			$this->guzzle_client = new \GuzzleHttp\Client();
105
		}
106 63
	}
107
108
	/**
109
	 * Headers to be sent on every API call
110
	 *
111
	 * @return string[]
112
	 */
113 51
	private function getStandardHeaders()
114
	{
115
		// I'd use Guzzle middleware for this, as we want to add it on
116
		// every request, but how do I do that and support dependency
117
		// injection of our client? You can't add middleware to a
118
		// Guzzle client after it's been constructed, right?
119
		return array(
120 51
				'User-Agent' => $this->getOurUserAgent()
121
		);
122
	}
123
124
	/**
125
	 * From the docs:
126
	 * Setting your user agent If possible, your user agent string should always use the following format: Application Name/Version | Plugin Name/Version
127
	 * e.g. WordPress/4.4.1 | Akismet/3.1.7
128
	 *
129
	 * @return string
130
	 */
131 51
	private function getOurUserAgent()
132
	{
133 51
		return "{$this->app_name}/{$this->app_version} | Gothick\\AkismetClient/" . self::VERSION;
134
	}
135
136
	/**
137
	 * You may want to verify a key before you use it. To do that, construct a Client without an API
138
	 * key, then use verifyKey($key) to verify the key, then use setKey($key) to set the validated
139
	 * key. You can call verifyKey without a key set, but you must set a key before calling any other
140
	 * API method.
141
	 *
142
	 * @param string $api_key
143
	 * @throws \Gothick\AkismetClient\AkismetException
144
	 */
145 11
	public function setKey($api_key)
146
	{
147 11
		if (empty($api_key))
148
		{
149 2
			throw new AkismetException('Must provide an API key in ' . __METHOD__);
150
		}
151 9
		$this->api_key = $api_key;
152 9
	}
153
154
	/**
155
	 * Verify an Akismet API key.
156
	 * @param string $api_key
157
	 * @param string[] $params Optional parameters. In verify-key, the only useful parameter is "is_test",
158
	 *                         which you should only pass when testing. To be honest, it's not even clear
159
	 *                         from the documentation if that parameter is used in verify-key, but better
160
	 *                         safe than sorry...
161
	 * @throws \Gothick\AkismetClient\AkismetException
162
	 * @return \Gothick\AkismetClient\Result\VerifyKeyResult
163
	 */
164 15
	public function verifyKey($api_key = null, $params = array())
165
	{
166 15
		$key_to_verify = empty($api_key) ? $this->api_key : $api_key;
167
168 15
		if (empty($key_to_verify))
169
		{
170 1
			throw new AkismetException('Must provide or pre-configure a key in ' . __METHOD__);
171
		}
172
173
		try
174
		{
175 14
			$params = array_merge(
176 14
					$params,
177
					[
178 14
						"key" => $key_to_verify,
179 14
						"blog" => $this->blog
180
					]
181
			);
182 14
			$response = $this->callApiMethod(self::VERB_VERIFY_KEY, $params);
183 1
		} catch (\Exception $e)
184
		{
185
			// Wrap whatever exception we caught up in a new exception of our
186
			// own type and throw it along up the line.
187 1
			throw new AkismetException('Unexpected exception in ' . __METHOD__, 0, $e);
188
		}
189 13
		return new VerifyKeyResult($response);
190
	}
191
192
	/**
193
	 * Check a comment for spam.
194
	 * See the Akismet API documentation for full details:
195
	 * https://akismet.com/development/api/#comment-check.
196
	 * Returns a valid CommentCheckResult object or throws an exception.
197
	 *
198
	 * @param string[] $params
199
	 *        	User IP, User-Agent, the message, etc. See the Akismet API
200
	 *        	documentation for details.
201
	 * @param string[] $server_params
202
	 *        	This can just be $_SERVER, if you have access to it
203
	 * @return \Gothick\AkismetClient\Result\CommentCheckResult
204
	 */
205 18
	public function commentCheck($params = array(), $server_params = array())
206
	{
207 18
		return new CommentCheckResult($this->callSpamMethod(self::VERB_COMMENT_CHECK, $params, $server_params));
208
	}
209
210
	/**
211
	 * Submit a comment as spam. This must use the same parameters as those used when checking the
212
	 * comment with commetnCheck.
213
	 * See the Akismet API documentation for full details:
214
	 * https://akismet.com/development/api/#comment-check.
215
	 * Returns a valid SubmitSpamResult object or throws an exception.
216
	 *
217
	 * @param string[] $params
218
	 *        	User IP, User-Agent, the message, etc. See the Akismet API
219
	 *        	documentation for details.
220
	 * @param string[] $server_params
221
	 *        	This can just be $_SERVER, if you have access to it
222
	 * @return \Gothick\AkismetClient\Result\SubmitSpamResult
223
	 */
224 14
	public function submitSpam($params = array(), $server_params = array())
225
	{
226 14
		return new SubmitSpamResult($this->callSpamMethod(self::VERB_SUBMIT_SPAM, $params, $server_params));
227
	}
228
229
	/**
230
	 * Submit a comment as ham. This must use the same parameters as those used when checking the
231
	 * comment with commetnCheck.
232
	 * See the Akismet API documentation for full details:
233
	 * https://akismet.com/development/api/#comment-check.
234
	 * Returns a valid SubmitHamResult object or throws an exception.
235
	 *
236
	 * @param string[] $params
237
	 *        	User IP, User-Agent, the message, etc. See the Akismet API
238
	 *        	documentation for details.
239
	 * @param string[] $server_params
240
	 *        	This can just be $_SERVER, if you have access to it
241
	 * @return \Gothick\AkismetClient\Result\SubmitHamResult
242
	 */
243 14
	public function submitHam($params = array(), $server_params = array())
244
	{
245 14
		return new SubmitHamResult($this->callSpamMethod(self::VERB_SUBMIT_HAM, $params, $server_params));
246
	}
247
248
	/**
249
	 * Common code for calling check-comment, submit-ham and submit-spam; these all
250
	 * work in the same way, just returning slightly different results.
251
	 * @param string $verb
252
	 * @param string[] $params
253
	 * @param string[] $server_params
254
	 * @throws \Gothick\AkismetClient\AkismetException
255
	 */
256 46
	protected function callSpamMethod($verb, $params, $server_params)
257
	{
258
		// comment-check, submit-spam and submit-ham all work the same way and take
259
		// the same arguments, so this handles them all.
260 46
		if (empty($params[ 'user_ip' ]) || empty($params[ 'user_agent' ]))
261
		{
262 6
			throw new AkismetException(__METHOD__ . ' requires user_ip and user_agent in $params (' . $verb . ')');
263
		}
264 40
		$params = array_merge($server_params, $params);
265 40
		$params = array_merge($params, [
266 40
				'blog' => $this->blog
267
		]);
268
269
		try
270
		{
271 40
			$response = $this->callApiMethod($verb, $params);
272 3
		} catch (\Exception $e)
273
		{
274 3
			throw new AkismetException('Unexpected exception in ' . __METHOD__ . ' (' . $verb . ')', 0, $e);
275
		}
276 37
		return $response;
277
	}
278
	/**
279
	 * Call an Akisemet API method.
280
	 * @param string $verb
281
	 * @param array $params
282
	 * @return \GuzzleHttp\Psr7\Response
283
	 */
284 54
	private function callApiMethod($verb, $params)
285
	{
286 54
		return $this->guzzle_client->request(
287 54
				'POST',
288 54
				$this->apiUri($verb),
289
				[
290 51
						'form_params' => $params,
291 51
						'headers' => $this->getStandardHeaders()
292
				]);
293
	}
294
295
	/**
296
	 * Work out the Akismet API URL given the REST verb and our configured key. This would
297
	 * be far less of a pain if Akismet just had you pass the API key as a parameter or
298
	 * a header. Gawd knows why they change the host for authenticated calls.
299
	 * @param string $verb
300
	 * @throws \Gothick\AkismetClient\AkismetException
301
	 * @return string
302
	 */
303 54
	private function apiUri($verb)
304
	{
305 54
		if ($verb == self::VERB_VERIFY_KEY)
306
		{
307 14
			return "https://rest.akismet.com/1.1/verify-key";
308
		} else
309
		{
310 40
			if (empty($this->api_key))
311
			{
312 3
				throw new AkismetException("Can't call authenticated method without setting an API key in " . __METHOD__);
313
			}
314 37
			return "https://{$this->api_key}.rest.akismet.com/1.1/$verb";
315
		}
316
	}
317
}