Completed
Push — master ( b74217...7d9fdb )
by Matt
02:51
created

Client::submitSpam()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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