PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/mem_akismet/mem_akismet.php

https://bitbucket.org/Manfre/txp-plugins
PHP | 545 lines | 315 code | 99 blank | 131 comment | 56 complexity | 33e0d1a1dcd6d4617ac5dbf022091978 MD5 | raw file
  1. <?php
  2. // This is a PLUGIN TEMPLATE.
  3. // Copy this file to a new name like abc_myplugin.php. Edit the code, then
  4. // run this file at the command line to produce a plugin for distribution:
  5. // $ php abc_myplugin.php > abc_myplugin-0.1.txt
  6. // Plugin name is optional. If unset, it will be extracted from the current
  7. // file name. Uncomment and edit this line to override:
  8. $plugin['name'] = 'mem_akismet';
  9. // 0 = Plugin help is in Textile format, no raw HTML allowed (default).
  10. // 1 = Plugin help is in raw HTML. Not recommended.
  11. # $plugin['allow_html_help'] = 1;
  12. $plugin['version'] = '0.4';
  13. $plugin['author'] = 'Michael Manfre';
  14. $plugin['author_uri'] = 'http://manfre.net/';
  15. $plugin['description'] = 'Akismet/TypePad AntiSpam comment filtering for Textpattern';
  16. // Plugin types:
  17. // 0 = regular plugin; loaded on the public web side only
  18. // 1 = admin plugin; loaded on both the public and admin side
  19. // 2 = library; loaded only when include_plugin() or require_plugin() is called
  20. $plugin['type'] = 1;
  21. if (!defined('txpinterface'))
  22. @include_once('../zem_tpl.php');
  23. if (0) {
  24. ?>
  25. # --- BEGIN PLUGIN HELP ---
  26. h1. Akismet/TypePad AntiSpam Comment Filtering for Textpattern
  27. This is a anti-spam plugin that uses "Akismet":http://www.akismet.com or "TypePad AntiSpam":http://antispam.typepad.com/.
  28. h2. Settings
  29. *mem_akismet_spam_server* - The URI of the spam filter. For TypePad AntiSpam use "api.antispam.typepad.com" and for Akismet use "rest.akismet.com".
  30. *mem_akismet_api_key* - The API key that you obtained from "Akismet.com":http://www.akismet.com or "TypePad":http://antispam.typepad.com/info/get-api-key.html
  31. *mem_akismet_submit_ham_spam* - Determines whether Akismet is sent information whenever a comment has its status manually changed. This is disabled by default. Submitting content to Akismet allows them to improve their spam detection.
  32. h2. Uninstalling
  33. If you would like to remove this plugin and all of its settings, "click here":./index.php?event=mem_akismet&step=uninstall.
  34. h2. License
  35. This plugin includes (inlined) the akismet php 4 classes created by "Bret Kuhns":http://www.miphp.net/blog/view/new_akismet_class/ (Version 0.3.4 "MIT license":http://www.opensource.org/licenses/mit-license.php)
  36. The rest of the plugin was authored by "Michael Manfre":http://manfre.net and is dual licensed with the GPLv2 and MIT licenses.
  37. # --- END PLUGIN HELP ---
  38. <?php
  39. }
  40. # --- BEGIN PLUGIN CODE ---
  41. global $prefs, $event;
  42. if (!isset($prefs['mem_akismet_submit_ham_spam'])) {
  43. set_pref('mem_akismet_submit_ham_spam', '0', 'comments', 0, 'yesnoradio');
  44. }
  45. if (!isset($prefs['mem_akismet_spam_server'])) {
  46. set_pref('mem_akismet_spam_server', 'rest.akismet.com', 'comments', 0);
  47. }
  48. if (!isset($prefs['mem_akismet_api_key'])) {
  49. set_pref('mem_akismet_api_key','','comments');
  50. } else if (!empty($prefs['mem_akismet_api_key'])) {
  51. // This function gets called when Txp is about to save.
  52. register_callback('mem_akismet_check','comment.save');
  53. if (@$prefs['mem_akismet_submit_ham_spam'] == 1 && @txpinterface == 'admin') {
  54. register_callback('mem_akismet_discuss_save', 'discuss', 'discuss_save', 1);
  55. }
  56. }
  57. if (@txpinterface == 'admin') {
  58. add_privs('mem_akismet','1');
  59. register_callback('mem_akismet_uninstall', 'mem_akismet','uninstall');
  60. function mem_akismet_uninstall()
  61. {
  62. global $event,$siteurl;
  63. safe_delete('txp_prefs', "`name` LIKE 'mem_akismet%'");
  64. $event = 'plugin';
  65. pagetop('','');
  66. echo '<div style="text-align:center;"><h1>mem_akismet Uninstallation</h1>' .
  67. '<div>Settings deleted.</div><br /><br />' .
  68. '<form method="post" action="index.php" onsubmit="' ."return confirm('Really delete?');". '"><input type="submit" name="" value="Remove mem_akismet plugin" class="smallerbox" /><input type="hidden" name="event" value="plugin" /><input type="hidden" name="step" value="plugin_delete" /><input type="hidden" name="name" value="mem_akismet" /></form></div>';
  69. }
  70. }
  71. function mem_akismet_get_err_text($akismet)
  72. {
  73. if ($akismet->errorsExist())
  74. {
  75. if($akismet->isError('AKISMET_INVALID_KEY'))
  76. {
  77. return 'Invalid Akismet key';
  78. }
  79. elseif($akismet->isError('AKISMET_RESPONSE_FAILED'))
  80. {
  81. return 'Akismet response failed';
  82. }
  83. elseif($akismet->isError('AKISMET_SERVER_NOT_FOUND'))
  84. {
  85. return 'Akismet server not found';
  86. }
  87. }
  88. return '';
  89. }
  90. function mem_akismet_discuss_save()
  91. {
  92. global $prefs, $siteurl;
  93. extract(doSlash(gpsa(array('email','name','web','message','ip'))));
  94. extract(array_map('assert_int',gpsa(array('discussid','visible','parentid'))));
  95. $comment = array(
  96. 'author' => $name,
  97. 'email' => $email,
  98. 'website' => $web,
  99. 'body' => $message,
  100. 'user_ip' => $ip,
  101. 'referrer' => '',
  102. //'user_agent' => '', this is required
  103. );
  104. $apikey = @$prefs['mem_akismet_api_key'];
  105. $akismetServer = @$prefs['mem_akismet_spam_server'];
  106. $akismet = new Akismet($siteurl, $apikey, $comment, $akismetServer);
  107. if (!$akismet->errorsExist())
  108. {
  109. $rs = safe_row('email, name, visible', 'txp_discuss', "discussid = $discussid");
  110. if ($rs)
  111. {
  112. // moderated || visible --> spam, notify akismet of uncaught spam
  113. if ($visible == SPAM && ($rs['visible'] == VISIBLE || $rs['visible'] == MODERATE)) {
  114. $akismet->submitSpam();
  115. }
  116. // moderated || spam --> visible, notify akismet of false positive
  117. if ($visible == VISIBLE && ($rs['visible'] == SPAM || $rs['visible'] == MODERATE)) {
  118. $akismet->submitHam();
  119. }
  120. }
  121. }
  122. else
  123. {
  124. $err = mem_akismet_get_err_text($akismet);
  125. trigger_error("mem_akismet: " + $err, E_USER_WARNING);
  126. }
  127. }
  128. function mem_akismet_check()
  129. {
  130. global $siteurl,$prefs;
  131. // If you wanted to check the regular comment-form variables, we we would use:
  132. extract(getComment());
  133. $comment = array(
  134. 'author' => $name,
  135. 'email' => $email,
  136. 'website' => $web,
  137. 'body' => $message,
  138. 'permalink' => $backpage
  139. );
  140. $apikey = @$prefs['mem_akismet_api_key'];
  141. $akismet = new Akismet($siteurl, $apikey, $comment);
  142. // We get the evaluator instance. You always need this.
  143. $evaluator =& get_comment_evaluator();
  144. // moderate spam on akismet error
  145. if ($akismet->errorsExist())
  146. {
  147. $evaluator->add_estimate(MODERATE, 0.2, 'Akismet error.');
  148. }
  149. else
  150. {
  151. if ($akismet->isSpam()) {
  152. $evaluator->add_estimate(SPAM, 0.75, 'Akismet says spam');
  153. } else {
  154. $evaluator->add_estimate(VISIBLE, 0.75, 'Akismet didn\'t flag');
  155. }
  156. }
  157. }
  158. if (!class_exists('AkismetObject')) {
  159. /* Borrowed Akismet php class mentioned in help
  160. * @author Bret Kuhns {@link www.miphp.net}
  161. * @link http://www.miphp.net/blog/view/new_akismet_class/
  162. * @version 0.3.4
  163. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  164. */
  165. // Error constants
  166. define("AKISMET_SERVER_NOT_FOUND", 0);
  167. define("AKISMET_RESPONSE_FAILED", 1);
  168. define("AKISMET_INVALID_KEY", 2);
  169. // Base class to assist in error handling between Akismet classes
  170. class AkismetObject {
  171. var $errors = array();
  172. /**
  173. * Add a new error to the errors array in the object
  174. *
  175. * @param String $name A name (array key) for the error
  176. * @param String $string The error message
  177. * @return void
  178. */
  179. // Set an error in the object
  180. function setError($name, $message) {
  181. $this->errors[$name] = $message;
  182. }
  183. /**
  184. * Return a specific error message from the errors array
  185. *
  186. * @param String $name The name of the error you want
  187. * @return mixed Returns a String if the error exists, a false boolean if it does not exist
  188. */
  189. function getError($name) {
  190. if($this->isError($name)) {
  191. return $this->errors[$name];
  192. } else {
  193. return false;
  194. }
  195. }
  196. /**
  197. * Return all errors in the object
  198. *
  199. * @return String[]
  200. */
  201. function getErrors() {
  202. return (array)$this->errors;
  203. }
  204. /**
  205. * Check if a certain error exists
  206. *
  207. * @param String $name The name of the error you want
  208. * @return boolean
  209. */
  210. function isError($name) {
  211. return isset($this->errors[$name]);
  212. }
  213. /**
  214. * Check if any errors exist
  215. *
  216. * @return boolean
  217. */
  218. function errorsExist() {
  219. return (count($this->errors) > 0);
  220. }
  221. }
  222. // Used by the Akismet class to communicate with the Akismet service
  223. class AkismetHttpClient extends AkismetObject {
  224. var $akismetVersion = '1.1';
  225. var $con;
  226. var $host;
  227. var $port;
  228. var $apiKey;
  229. var $blogUrl;
  230. var $errors = array();
  231. // Constructor
  232. function AkismetHttpClient($host, $blogUrl, $apiKey, $port = 80) {
  233. $this->host = $host;
  234. $this->port = $port;
  235. $this->blogUrl = $blogUrl;
  236. $this->apiKey = $apiKey;
  237. }
  238. // Use the connection active in $con to get a response from the server and return that response
  239. function getResponse($request, $path, $type = "post", $responseLength = 1160) {
  240. $this->_connect();
  241. if($this->con && !$this->isError(AKISMET_SERVER_NOT_FOUND)) {
  242. $request =
  243. strToUpper($type)." /{$this->akismetVersion}/$path HTTP/1.1\r\n" .
  244. "Host: ".((!empty($this->apiKey)) ? $this->apiKey."." : null)."{$this->host}\r\n" .
  245. "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n" .
  246. "Content-Length: ".strlen($request)."\r\n" .
  247. "User-Agent: Akismet PHP4 Class\r\n" .
  248. "\r\n" .
  249. $request
  250. ;
  251. $response = "";
  252. @fwrite($this->con, $request);
  253. while(!feof($this->con)) {
  254. $response .= @fgets($this->con, $responseLength);
  255. }
  256. $response = explode("\r\n\r\n", $response, 2);
  257. return $response[1];
  258. } else {
  259. $this->setError(AKISMET_RESPONSE_FAILED, "The response could not be retrieved.");
  260. }
  261. $this->_disconnect();
  262. }
  263. // Connect to the Akismet server and store that connection in the instance variable $con
  264. function _connect() {
  265. if(!($this->con = @fsockopen($this->host, $this->port))) {
  266. $this->setError(AKISMET_SERVER_NOT_FOUND, "Could not connect to akismet server.");
  267. }
  268. }
  269. // Close the connection to the Akismet server
  270. function _disconnect() {
  271. @fclose($this->con);
  272. }
  273. }
  274. // The controlling class. This is the ONLY class the user should instantiate in
  275. // order to use the Akismet service!
  276. class Akismet extends AkismetObject {
  277. var $apiPort = 80;
  278. var $akismetServer = 'rest.akismet.com';
  279. var $akismetVersion = '1.1';
  280. var $http;
  281. var $ignore = array(
  282. 'HTTP_COOKIE',
  283. 'HTTP_X_FORWARDED_FOR',
  284. 'HTTP_X_FORWARDED_HOST',
  285. 'HTTP_MAX_FORWARDS',
  286. 'HTTP_X_FORWARDED_SERVER',
  287. 'REDIRECT_STATUS',
  288. 'SERVER_PORT',
  289. 'PATH',
  290. 'DOCUMENT_ROOT',
  291. 'SERVER_ADMIN',
  292. 'QUERY_STRING',
  293. 'PHP_SELF',
  294. 'argv'
  295. );
  296. var $blogUrl = "";
  297. var $apiKey = "";
  298. var $comment = array();
  299. /**
  300. * Constructor
  301. *
  302. * Set instance variables, connect to Akismet, and check API key
  303. *
  304. * @param String $blogUrl The URL to your own blog
  305. * @param String $apiKey Your wordpress API key
  306. * @param String[] $comment A formatted comment array to be examined by the Akismet service
  307. * @return Akismet
  308. */
  309. function Akismet($blogUrl, $apiKey, $comment = array(), $akismetServer = 'rest.akismet.com') {
  310. $this->blogUrl = $blogUrl;
  311. $this->apiKey = $apiKey;
  312. $this->setComment($comment);
  313. $this->akismetServer = empty($akismetServer) ? $this->akismetServer : $akismetServer;
  314. // Connect to the Akismet server and populate errors if they exist
  315. $this->http = new AkismetHttpClient($this->akismetServer, $blogUrl, $apiKey);
  316. if($this->http->errorsExist()) {
  317. $this->errors = array_merge($this->errors, $this->http->getErrors());
  318. }
  319. // Check if the API key is valid
  320. if(!$this->_isValidApiKey($apiKey)) {
  321. $this->setError(AKISMET_INVALID_KEY, "Your Akismet API key is not valid.");
  322. }
  323. }
  324. /**
  325. * Query the Akismet and determine if the comment is spam or not
  326. *
  327. * @return boolean
  328. */
  329. function isSpam() {
  330. $response = $this->http->getResponse($this->_getQueryString(), 'comment-check');
  331. return ($response == "true");
  332. }
  333. /**
  334. * Submit this comment as an unchecked spam to the Akismet server
  335. *
  336. * @return void
  337. */
  338. function submitSpam() {
  339. $this->http->getResponse($this->_getQueryString(), 'submit-spam');
  340. }
  341. /**
  342. * Submit a false-positive comment as "ham" to the Akismet server
  343. *
  344. * @return void
  345. */
  346. function submitHam() {
  347. $this->http->getResponse($this->_getQueryString(), 'submit-ham');
  348. }
  349. /**
  350. * Manually set the comment value of the instantiated object.
  351. *
  352. * @param Array $comment
  353. * @return void
  354. */
  355. function setComment($comment) {
  356. $this->comment = $comment;
  357. if(!empty($comment)) {
  358. $this->_formatCommentArray();
  359. $this->_fillCommentValues();
  360. }
  361. }
  362. /**
  363. * Returns the current value of the object's comment array.
  364. *
  365. * @return Array
  366. */
  367. function getComment() {
  368. return $this->comment;
  369. }
  370. /**
  371. * Check with the Akismet server to determine if the API key is valid
  372. *
  373. * @access Protected
  374. * @param String $key The Wordpress API key passed from the constructor argument
  375. * @return boolean
  376. */
  377. function _isValidApiKey($key) {
  378. $keyCheck = $this->http->getResponse("key=".$this->apiKey."&blog=".$this->blogUrl, 'verify-key');
  379. return ($keyCheck == "valid");
  380. }
  381. /**
  382. * Format the comment array in accordance to the Akismet API
  383. *
  384. * @access Protected
  385. * @return void
  386. */
  387. function _formatCommentArray() {
  388. $format = array(
  389. 'type' => 'comment_type',
  390. 'author' => 'comment_author',
  391. 'email' => 'comment_author_email',
  392. 'website' => 'comment_author_url',
  393. 'body' => 'comment_content'
  394. );
  395. foreach($format as $short => $long) {
  396. if(isset($this->comment[$short])) {
  397. $this->comment[$long] = $this->comment[$short];
  398. unset($this->comment[$short]);
  399. }
  400. }
  401. }
  402. /**
  403. * Fill any values not provided by the developer with available values.
  404. *
  405. * @return void
  406. */
  407. function _fillCommentValues() {
  408. if(!isset($this->comment['user_ip'])) {
  409. $this->comment['user_ip'] = ($_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR')) ? $_SERVER['REMOTE_ADDR'] : getenv('HTTP_X_FORWARDED_FOR');
  410. }
  411. if(!isset($this->comment['user_agent'])) {
  412. $this->comment['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
  413. }
  414. if(!isset($this->comment['referrer'])) {
  415. $this->comment['referrer'] = $_SERVER['HTTP_REFERER'];
  416. }
  417. if(!isset($this->comment['blog'])) {
  418. $this->comment['blog'] = $this->blogUrl;
  419. }
  420. }
  421. /**
  422. * Build a query string for use with HTTP requests
  423. *
  424. * @access Protected
  425. * @return String
  426. */
  427. function _getQueryString() {
  428. foreach($_SERVER as $key => $value) {
  429. if(!in_array($key, $this->ignore)) {
  430. if($key == 'REMOTE_ADDR') {
  431. $this->comment[$key] = $this->comment['user_ip'];
  432. } else {
  433. $this->comment[$key] = $value;
  434. }
  435. }
  436. }
  437. $query_string = '';
  438. foreach($this->comment as $key => $data) {
  439. $query_string .= $key . '=' . urlencode(stripslashes($data)) . '&';
  440. }
  441. return $query_string;
  442. }
  443. }
  444. } // end if class_defined
  445. # --- END PLUGIN CODE ---
  446. ?>