1 <?php
2
3 /**
4 * Akismet anti-comment spam service
5 *
6 * The class in this package allows use of the {@link http://akismet.com Akismet} anti-comment spam service in any PHP5 application.
7 *
8 * This service performs a number of checks on submitted data and returns whether or not the data is likely to be spam.
9 *
10 * Please note that in order to use this class, you must have a vaild {@link http://wordpress.com/api-keys/ WordPress API key}. They are free for non/small-profit types and getting one will only take a couple of minutes.
11 *
12 * For commercial use, please {@link http://akismet.com/commercial/ visit the Akismet commercial licensing page}.
13 *
14 * Please be aware that this class is PHP5 only. Attempts to run it under PHP4 will most likely fail.
15 *
16 * See the Akismet class documentation page linked to below for usage information.
17 *
18 * @package cms
19 * @subpackage comments
20 * @author Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
21 * @version 0.1
22 * @copyright Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
23 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
24 */
25
26 /**
27 * The Akismet PHP5 Class
28 *
29 * This class takes the functionality from the Akismet WordPress plugin written by {@link http://photomatt.net/ Matt Mullenweg} and allows it to be integrated into any PHP5 application or website.
30 *
31 * The original plugin is {@link http://akismet.com/download/ available on the Akismet website}.
32 *
33 * <b>Usage:</b>
34 * <code>
35 * $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue');
36 * $akismet->setCommentAuthor($name);
37 * $akismet->setCommentAuthorEmail($email);
38 * $akismet->setCommentAuthorURL($url);
39 * $akismet->setCommentContent($comment);
40 * $akismet->setPermalink('http://www.example.com/blog/alex/someurl/');
41 * if($akismet->isCommentSpam())
42 * // store the comment but mark it as spam (in case of a mis-diagnosis)
43 * else
44 * // store the comment normally
45 * </code>
46 *
47 * @version 0.2
48 * @author Alex Potsides
49 * @link http://www.achingbrain.net/
50 * @package cms
51 * @subpackage comments
52 */
53 class Akismet
54 {
55 private $version = '0.2';
56 private $wordPressAPIKey;
57 private $blogURL;
58 private $comment;
59 private $apiPort;
60 private ;
61 private ;
62
63 // This prevents some potentially sensitive information from being sent accross the wire.
64 private $ignore = array('HTTP_COOKIE',
65 'HTTP_X_FORWARDED_FOR',
66 'HTTP_X_FORWARDED_HOST',
67 'HTTP_MAX_FORWARDS',
68 'HTTP_X_FORWARDED_SERVER',
69 'REDIRECT_STATUS',
70 'SERVER_PORT',
71 'PATH',
72 'DOCUMENT_ROOT',
73 'SERVER_ADMIN',
74 'QUERY_STRING',
75 'PHP_SELF' );
76
77
78 /**
79 * @throws Exception An exception is thrown if your API key is invalid.
80 * @param string Your WordPress API key.
81 * @param string $blogURL The URL of your blog.
82 */
83 public function __construct($blogURL, $wordPressAPIKey)
84 {
85 $this->blogURL = $blogURL;
86 $this->wordPressAPIKey = $wordPressAPIKey;
87
88 // Set some default values
89 $this->apiPort = 80;
90 $this->akismetServer = 'rest.akismet.com';
91 $this->akismetVersion = '1.1';
92
93 // Start to populate the comment data
94 $this->comment['blog'] = $blogURL;
95 $this->comment['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
96 $this->comment['referrer'] = $_SERVER['HTTP_REFERER'];
97
98 // This is necessary if the server PHP5 is running on has been set up to run PHP4 and
99 // PHP5 concurently and is actually running through a separate proxy al a these instructions:
100 // http://www.schlitt.info/applications/blog/archives/83_How_to_run_PHP4_and_PHP_5_parallel.html
101 // and http://wiki.coggeshall.org/37.html
102 // Otherwise the user_ip appears as the IP address of the PHP4 server passing the requests to the
103 // PHP5 one...
104 $this->comment['user_ip'] = $_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR') ? $_SERVER['REMOTE_ADDR'] : getenv('HTTP_X_FORWARDED_FOR');
105
106 // Check to see if the key is valid
107 $response = $this->http_post('key=' . $this->wordPressAPIKey . '&blog=' . $this->blogURL, $this->akismetServer, '/' . $this->akismetVersion . '/verify-key');
108
109 if($response[1] != 'valid')
110 {
111 // Whoops, no it's not. Throw an exception as we can't proceed without a valid API key.
112 throw new Exception('Invalid API key. Please obtain one from http://wordpress.com/api-keys/');
113 }
114 }
115
116 private function http_post($request, $host, $path)
117 {
118 $http_request = "POST " . $path . " HTTP/1.1\r\n";
119 $http_request .= "Host: " . $host . "\r\n";
120 $http_request .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n";
121 $http_request .= "Content-Length: " . strlen($request) . "\r\n";
122 $http_request .= "User-Agent: Akismet PHP5 Class " . $this->version . " | Akismet/1.11\r\n";
123 $http_request .= "\r\n";
124 $http_request .= $request;
125
126 $socketWriteRead = new SocketWriteRead($host, $this->apiPort, $http_request);
127 $socketWriteRead->send();
128
129 return explode("\r\n\r\n", $socketWriteRead->getResponse(), 2);
130 }
131
132 // Formats the data for transmission echo $sql;
133 private function getQueryString()
134 {
135 foreach($_SERVER as $key => $value)
136 {
137 if(!in_array($key, $this->ignore))
138 {
139 if($key == 'REMOTE_ADDR')
140 {
141 $this->comment[$key] = $this->comment['user_ip'];
142 }
143 else
144 {
145 $this->comment[$key] = $value;
146 }
147 }
148 }
149
150 $query_string = '';
151
152 foreach($this->comment as $key => $data)
153 {
154 @$query_string .= $key . '=' . urlencode(stripslashes($data)) . '&';
155 }
156
157 return $query_string;
158 }
159
160 /**
161 * Tests for spam.
162 *
163 * Uses the web service provided by {@link http://www.akismet.com Akismet} to see whether or not the submitted comment is spam. Returns a boolean value.
164 *
165 * @return bool True if the comment is spam, false if not
166 */
167 public function isCommentSpam()
168 {
169 $response = $this->http_post($this->getQueryString(), $this->wordPressAPIKey . '.rest.akismet.com', '/' . $this->akismetVersion . '/comment-check');
170
171 return ($response[1] == 'true');
172 }
173
174 /**
175 * Submit spam that is incorrectly tagged as ham.
176 *
177 * Using this function will make you a good citizen as it helps Akismet to learn from its mistakes. This will improve the service for everybody.
178 */
179 public function submitSpam()
180 {
181 $this->http_post($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-spam');
182 }
183
184 /**
185 * Submit ham that is incorrectly tagged as spam.
186 *
187 * Using this function will make you a good citizen as it helps Akismet to learn from its mistakes. This will improve the service for everybody.
188 */
189 public function submitHam()
190 {
191 $this->http_post($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-ham');
192 }
193
194 /**
195 * To override the user IP address when submitting spam/ham later on
196 *
197 * @param string $userip An IP address. Optional.
198 */
199 public function setUserIP($userip)
200 {
201 $this->comment['user_ip'] = $userip;
202 }
203
204 /**
205 * To override the referring page when submitting spam/ham later on
206 *
207 * @param string $referrer The referring page. Optional.
208 */
209 public function setReferrer($referrer)
210 {
211 $this->comment['referrer'] = $referrer;
212 }
213
214 /**
215 * A permanent URL referencing the blog post the comment was submitted to.
216 *
217 * @param string $permalink The URL. Optional.
218 */
219 public function setPermalink($permalink)
220 {
221 $this->comment['permalink'] = $permalink;
222 }
223
224 /**
225 * The type of comment being submitted.
226 *
227 * May be blank, comment, trackback, pingback, or a made up value like "registration" or "wiki".
228 */
229 public function setCommentType($commentType)
230 {
231 $this->comment['comment_type'] = $commentType;
232 }
233
234 /**
235 * The name that the author submitted with the comment.
236 */
237 public function setCommentAuthor($commentAuthor)
238 {
239 $this->comment['comment_author'] = $commentAuthor;
240 }
241
242 /**
243 * The email address that the author submitted with the comment.
244 *
245 * The address is assumed to be valid.
246 */
247 public function setCommentAuthorEmail($authorEmail)
248 {
249 $this->comment['comment_author_email'] = $authorEmail;
250 }
251
252 /**
253 * The URL that the author submitted with the comment.
254 */
255 public function setCommentAuthorURL($authorURL)
256 {
257 $this->comment['comment_author_url'] = $authorURL;
258 }
259
260 /**
261 * The comment's body text.
262 */
263 public function setCommentContent($commentBody)
264 {
265 $this->comment['comment_content'] = $commentBody;
266 }
267
268 /**
269 * Defaults to 80
270 */
271 public function setAPIPort($apiPort)
272 {
273 $this->apiPort = $apiPort;
274 }
275
276 /**
277 * Defaults to rest.akismet.com
278 */
279 public function setAkismetServer($akismetServer)
280 {
281 $this->akismetServer = $akismetServer;
282 }
283
284 /**
285 * Defaults to '1.1'
286 */
287 public function setAkismetVersion($akismetVersion)
288 {
289 $this->akismetVersion = $akismetVersion;
290 }
291 }
292
293 /**
294 * Utility class used by Akismet
295 *
296 * This class is used by Akismet to do the actual sending and receiving of data. It opens a connection to a remote host, sends some data and the reads the response and makes it available to the calling program.
297 *
298 * The code that makes up this class originates in the Akismet WordPress plugin, which is {@link http://akismet.com/download/ available on the Akismet website}.
299 *
300 * N.B. It is not necessary to call this class directly to use the Akismet class. This is included here mainly out of a sense of completeness.
301 *
302 * @name SocketWriteRead
303 * @version 0.1
304 * @author Alex Potsides
305 * @link http://www.achingbrain.net/
306 * @package cms
307 * @subpackage comments
308 */
309 class SocketWriteRead
310 {
311 private $host;
312 private $port;
313 private $request;
314 private $response;
315 private $responseLength;
316 private $errorNumber;
317 private $errorString;
318
319 /**
320 * @param string $host The host to send/receive data.
321 * @param int $port The port on the remote host.
322 * @param string $request The data to send.
323 * @param int $responseLength The amount of data to read. Defaults to 1160 bytes.
324 */
325 public function __construct($host, $port, $request, $responseLength = 1160)
326 {
327 $this->host = $host;
328 $this->port = $port;
329 $this->request = $request;
330 $this->responseLength = $responseLength;
331 $this->errorNumber = 0;
332 $this->errorString = '';
333 }
334
335 /**
336 * Sends the data to the remote host.
337 *
338 * @throws An exception is thrown if a connection cannot be made to the remote host.
339 */
340 public function send()
341 {
342 $this->response = '';
343
344 $fs = fsockopen($this->host, $this->port, $this->errorNumber, $this->errorString, 3);
345
346 if($this->errorNumber != 0)
347 {
348 throw new Exception('Error connecting to host: ' . $this->host . ' Error number: ' . $this->errorNumber . ' Error message: ' . $this->errorString);
349 }
350
351 if($fs !== false)
352 {
353 @fwrite($fs, $this->request);
354
355 while(!feof($fs))
356 {
357 $this->response .= fgets($fs, $this->responseLength);
358 }
359
360 fclose($fs);
361 }
362
363 }
364
365 /**
366 * Returns the server response text
367 *
368 * @return string
369 */
370 public function getResponse()
371 {
372 return $this->response;
373 }
374
375 /**
376 * Returns the error number
377 *
378 * If there was no error, 0 will be returned.
379 *
380 * @return int
381 */
382 public function getErrorNumner()
383 {
384 return $this->errorNumber;
385 }
386
387 /**
388 * Returns the error string
389 *
390 * If there was no error, an empty string will be returned.
391 *
392 * @return string
393 */
394 public function getErrorString()
395 {
396 return $this->errorString;
397 }
398 }
399 ?>