Completed
Push — master ( 3c3f9e...b74217 )
by Matt
02:25
created

Client::setKey()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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