https://www.mediawiki.org/wiki/Special:Code/MediaWiki/114928
Revision: 114928 Author: reedy Date: 2012-04-16 19:05:24 +0000 (Mon, 16 Apr 2012) Log Message: ----------- Importing https://www.mediawiki.org/wiki/Extension:AkismetKlik/source Also bringing in https://github.com/achingbrain/php5-akismet/ Needs some love! Added Paths: ----------- trunk/extensions/AkismetKlik/ trunk/extensions/AkismetKlik/Akismet.class.php trunk/extensions/AkismetKlik/AkismetKlik.php Added: trunk/extensions/AkismetKlik/Akismet.class.php =================================================================== --- trunk/extensions/AkismetKlik/Akismet.class.php (rev 0) +++ trunk/extensions/AkismetKlik/Akismet.class.php 2012-04-16 19:05:24 UTC (rev 114928) @@ -0,0 +1,474 @@ +<?php + +/** + * Akismet anti-comment spam service + * + * The class in this package allows use of the {@link http://akismet.com Akismet} anti-comment spam service in any PHP5 application. + * + * This service performs a number of checks on submitted data and returns whether or not the data is likely to be spam. + * + * 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. + * + * For commercial use, please {@link http://akismet.com/commercial/ visit the Akismet commercial licensing page}. + * + * Please be aware that this class is PHP5 only. Attempts to run it under PHP4 will most likely fail. + * + * See the Akismet class documentation page linked to below for usage information. + * + * @package akismet + * @author Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net} + * @version 0.5 + * @copyright Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net} + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +/** + * The Akismet PHP5 Class + * + * 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. + * + * The original plugin is {@link http://akismet.com/download/ available on the Akismet website}. + * + * <code> + * $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue'); + * $akismet->setCommentAuthor($name); + * $akismet->setCommentAuthorEmail($email); + * $akismet->setCommentAuthorURL($url); + * $akismet->setCommentContent($comment); + * $akismet->setPermalink('http://www.example.com/blog/alex/someurl/'); + * + * if($akismet->isCommentSpam()) + * // store the comment but mark it as spam (in case of a mis-diagnosis) + * else + * // store the comment normally + * </code> + * + * Optionally you may wish to check if your WordPress API key is valid as in the example below. + * + * <code> + * $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue'); + * + * if($akismet->isKeyValid()) { + * // api key is okay + * } else { + * // api key is invalid + * } + * </code> + * + * @package akismet + * @name Akismet + * @version 0.5 + * @author Alex Potsides + * @link http://www.achingbrain.net/ + */ +class Akismet { + private $version = '0.5'; + private $wordPressAPIKey; + private $blogURL; + private $comment; + private $apiPort; + private $akismetServer; + private $akismetVersion; + private $requestFactory; + + // This prevents some potentially sensitive information from being sent accross the wire. + private $ignore = array('HTTP_COOKIE', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED_HOST', + 'HTTP_MAX_FORWARDS', + 'HTTP_X_FORWARDED_SERVER', + 'REDIRECT_STATUS', + 'SERVER_PORT', + 'PATH', + 'DOCUMENT_ROOT', + 'SERVER_ADMIN', + 'QUERY_STRING', + 'PHP_SELF' ); + + /** + * @param string $blogURL The URL of your blog. + * @param string $wordPressAPIKey WordPress API key. + */ + public function __construct($blogURL, $wordPressAPIKey) { + $this->blogURL = $blogURL; + $this->wordPressAPIKey = $wordPressAPIKey; + + // Set some default values + $this->apiPort = 80; + $this->akismetServer = 'rest.akismet.com'; + $this->akismetVersion = '1.1'; + $this->requestFactory = new SocketWriteReadFactory(); + + // Start to populate the comment data + $this->comment['blog'] = $blogURL; + + if(isset($_SERVER['HTTP_USER_AGENT'])) { + $this->comment['user_agent'] = $_SERVER['HTTP_USER_AGENT']; + } + + if(isset($_SERVER['HTTP_REFERER'])) { + $this->comment['referrer'] = $_SERVER['HTTP_REFERER']; + } + + /* + * This is necessary if the server PHP5 is running on has been set up to run PHP4 and + * PHP5 concurently and is actually running through a separate proxy al a these instructions: + * http://www.schlitt.info/applications/blog/archives/83_How_to_run_PHP4_and_PHP_5_parallel.html + * and http://wiki.coggeshall.org/37.html + * Otherwise the user_ip appears as the IP address of the PHP4 server passing the requests to the + * PHP5 one... + */ + if(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR')) { + $this->comment['user_ip'] = $_SERVER['REMOTE_ADDR']; + } else { + $this->comment['user_ip'] = getenv('HTTP_X_FORWARDED_FOR'); + } + } + + /** + * Makes a request to the Akismet service to see if the API key passed to the constructor is valid. + * + * Use this method if you suspect your API key is invalid. + * + * @return bool True is if the key is valid, false if not. + */ + public function isKeyValid() { + // Check to see if the key is valid + $response = $this->sendRequest('key=' . $this->wordPressAPIKey . '&blog=' . $this->blogURL, $this->akismetServer, '/' . $this->akismetVersion . '/verify-key'); + return $response[1] == 'valid'; + } + + // makes a request to the Akismet service + private function sendRequest($request, $host, $path) { + $http_request = "POST " . $path . " HTTP/1.0\r\n"; + $http_request .= "Host: " . $host . "\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n"; + $http_request .= "Content-Length: " . strlen($request) . "\r\n"; + $http_request .= "User-Agent: Akismet PHP5 Class " . $this->version . " | Akismet/1.11\r\n"; + $http_request .= "\r\n"; + $http_request .= $request; + + $requestSender = $this->requestFactory->createRequestSender(); + $response = $requestSender->send($host, $this->apiPort, $http_request); + + return explode("\r\n\r\n", $response, 2); + } + + // Formats the data for transmission + private function getQueryString() { + foreach($_SERVER as $key => $value) { + if(!in_array($key, $this->ignore)) { + if($key == 'REMOTE_ADDR') { + $this->comment[$key] = $this->comment['user_ip']; + } else { + $this->comment[$key] = $value; + } + } + } + + $query_string = ''; + + foreach($this->comment as $key => $data) { + if(!is_array($data)) { + $query_string .= $key . '=' . urlencode(stripslashes($data)) . '&'; + } + } + + return $query_string; + } + + /** + * Tests for spam. + * + * 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. + * + * @return bool True if the comment is spam, false if not + * @throws Will throw an exception if the API key passed to the constructor is invalid. + */ + public function isCommentSpam() { + $response = $this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.rest.akismet.com', '/' . $this->akismetVersion . '/comment-check'); + + if($response[1] == 'invalid' && !$this->isKeyValid()) { + throw new exception('The Wordpress API key passed to the Akismet constructor is invalid. Please obtain a valid one from http://wordpress.com/api-keys/'); + } + + return ($response[1] == 'true'); + } + + /** + * Submit spam that is incorrectly tagged as ham. + * + * 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. + */ + public function submitSpam() { + $this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-spam'); + } + + /** + * Submit ham that is incorrectly tagged as spam. + * + * 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. + */ + public function submitHam() { + $this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-ham'); + } + + /** + * To override the user IP address when submitting spam/ham later on + * + * @param string $userip An IP address. Optional. + */ + public function setUserIP($userip) { + $this->comment['user_ip'] = $userip; + } + + /** + * To override the referring page when submitting spam/ham later on + * + * @param string $referrer The referring page. Optional. + */ + public function setReferrer($referrer) { + $this->comment['referrer'] = $referrer; + } + + /** + * A permanent URL referencing the blog post the comment was submitted to. + * + * @param string $permalink The URL. Optional. + */ + public function setPermalink($permalink) { + $this->comment['permalink'] = $permalink; + } + + /** + * The type of comment being submitted. + * + * May be blank, comment, trackback, pingback, or a made up value like "registration" or "wiki". + */ + public function setCommentType($commentType) { + $this->comment['comment_type'] = $commentType; + } + + /** + * The name that the author submitted with the comment. + */ + public function setCommentAuthor($commentAuthor) { + $this->comment['comment_author'] = $commentAuthor; + } + + /** + * The email address that the author submitted with the comment. + * + * The address is assumed to be valid. + */ + public function setCommentAuthorEmail($authorEmail) { + $this->comment['comment_author_email'] = $authorEmail; + } + + /** + * The URL that the author submitted with the comment. + */ + public function setCommentAuthorURL($authorURL) { + $this->comment['comment_author_url'] = $authorURL; + } + + /** + * The comment's body text. + */ + public function setCommentContent($commentBody) { + $this->comment['comment_content'] = $commentBody; + } + + /** + * Lets you override the user agent used to submit the comment. + * you may wish to do this when submitting ham/spam. + * Defaults to $_SERVER['HTTP_USER_AGENT'] + */ + public function setCommentUserAgent($userAgent) { + $this->comment['user_agent'] = $userAgent; + } + + /** + * Defaults to 80 + */ + public function setAPIPort($apiPort) { + $this->apiPort = $apiPort; + } + + /** + * Defaults to rest.akismet.com + */ + public function setAkismetServer($akismetServer) { + $this->akismetServer = $akismetServer; + } + + /** + * Defaults to '1.1' + * + * @param string $akismetVersion + */ + public function setAkismetVersion($akismetVersion) { + $this->akismetVersion = $akismetVersion; + } + + /** + * Used by unit tests to mock transport layer + * + * @param AkismetRequestFactory $requestFactory + */ + public function setRequestFactory($requestFactory) { + $this->requestFactory = $requestFactory; + } +} + +/** + * Used internally by Akismet + * + * 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. + * + * 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}. + * + * N.B. It is not necessary to call this class directly to use the Akismet class. + * + * @package akismet + * @name SocketWriteRead + * @version 0.5 + * @author Alex Potsides + * @link http://www.achingbrain.net/ + */ +class SocketWriteRead implements AkismetRequestSender { + private $response; + private $errorNumber; + private $errorString; + + public function __construct() { + $this->errorNumber = 0; + $this->errorString = ''; + } + + /** + * Sends the data to the remote host. + * + * @param string $host The host to send/receive data. + * @param int $port The port on the remote host. + * @param string $request The data to send. + * @param int $responseLength The amount of data to read. Defaults to 1160 bytes. + * @throws An exception is thrown if a connection cannot be made to the remote host. + * @returns The server response + */ + public function send($host, $port, $request, $responseLength = 1160) { + $response = ''; + + $fs = fsockopen($host, $port, $this->errorNumber, $this->errorString, 3); + + if($this->errorNumber != 0) { + throw new Exception('Error connecting to host: ' . $host . ' Error number: ' . $this->errorNumber . ' Error message: ' . $this->errorString); + } + + if($fs !== false) { + @fwrite($fs, $request); + + while(!feof($fs)) { + $response .= fgets($fs, $responseLength); + } + + fclose($fs); + } + + return $response; + } + + /** + * Returns the server response text + * + * @return string + */ + public function getResponse() { + return $this->response; + } + + /** + * Returns the error number + * + * If there was no error, 0 will be returned. + * + * @return int + */ + public function getErrorNumner() { + return $this->errorNumber; + } + + /** + * Returns the error string + * + * If there was no error, an empty string will be returned. + * + * @return string + */ + public function getErrorString() { + return $this->errorString; + } +} + +/** + * Used internally by the Akismet class and to mock the Akismet anti spam service in + * the unit tests. + * + * N.B. It is not necessary to call this class directly to use the Akismet class. + * + * @package akismet + * @name SocketWriteReadFactory + * @version 0.5 + * @author Alex Potsides + * @link http://www.achingbrain.net/ + */ +class SocketWriteReadFactory implements AkismetRequestFactory { + + public function createRequestSender() { + return new SocketWriteRead(); + } +} + +/** + * Used internally by the Akismet class and to mock the Akismet anti spam service in + * the unit tests. + * + * N.B. It is not necessary to implement this class to use the Akismet class. + * + * @package akismet + * @name AkismetRequestSender + * @version 0.5 + * @author Alex Potsides + * @link http://www.achingbrain.net/ + */ +interface AkismetRequestSender { + + /** + * Sends the data to the remote host. + * + * @param string $host The host to send/receive data. + * @param int $port The port on the remote host. + * @param string $request The data to send. + * @param int $responseLength The amount of data to read. Defaults to 1160 bytes. + * @throws An exception is thrown if a connection cannot be made to the remote host. + * @returns The server response + */ + public function send($host, $port, $request, $responseLength = 1160); +} + +/** + * Used internally by the Akismet class and to mock the Akismet anti spam service in + * the unit tests. + * + * N.B. It is not necessary to implement this class to use the Akismet class. + * + * @package akismet + * @name AkismetRequestFactory + * @version 0.5 + * @author Alex Potsides + * @link http://www.achingbrain.net/ + */ +interface AkismetRequestFactory { + + public function createRequestSender(); +} Property changes on: trunk/extensions/AkismetKlik/Akismet.class.php ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/AkismetKlik/AkismetKlik.php =================================================================== --- trunk/extensions/AkismetKlik/AkismetKlik.php (rev 0) +++ trunk/extensions/AkismetKlik/AkismetKlik.php 2012-04-16 19:05:24 UTC (rev 114928) @@ -0,0 +1,151 @@ +<?php +if ( !defined( 'MEDIAWIKI' ) ) { + exit; +} + +# +# Include PHP5 Akismet class from http://www.achingbrain.net/stuff/akismet (GPL) +# +require_once('Akismet.class.php'); + +#Extension credits +$wgExtensionCredits['other'][] = array( + 'name' => 'AkismetKlik', + 'author' => 'Carl Austin Bennett', + 'url' => 'http://www.mediawiki.org/wiki/Extension:AkismetKlik', + 'description' => 'Rejects edits from suspected comment spammers on Akismet\'s blacklist.', +); + +# Set site-specific configuration values +#$wgAKkey='867-5309'; +#$siteURL='http://wiki.example.org'; + +# +# MediaWiki hooks +# +# Loader for spam blacklist feature +# Include this from LocalSettings.php + +global $wgAkismetFilterCallback, $wgPreAkismetFilterCallback, $wgUser; +$wgPreAkismetFilterCallback = false; + +if ( defined( 'MW_SUPPORTS_EDITFILTERMERGED' ) ) { + $wgHooks['EditFilterMerged'][] = 'wfAkismetFilterMerged'; +} else { + if ( $wgFilterCallback ) { + $wgPreAkismetFilterCallback = $wgFilterCallback; + } + $wgFilterCallback = 'wfAkismetFilter'; +} + +#$wgHooks['EditFilter'][] = 'wfAkismetFilter'; + +/** + * Get an instance of AkismetKlik and do some first-call initialisation. + * All actual functionality is implemented in that object + */ +function wfAkismetKlikObject() { + global $wgSpamBlacklistSettings, $wgPreSpamFilterCallback; + static $spamObj; + if ( !$spamObj ) { + $spamObj = new AkismetKlik ( $wgSpamBlacklistSettings ); + $spamObj->previousFilter = $wgPreSpamFilterCallback; + } + return $spamObj; +} + +/** + * Hook function for $wgFilterCallback + */ +function wfAkismetFilter( &$title, $text, $section ) { + $spamObj = wfAkismetKlikObject(); + return $spamObj->filter( $title, $text, $section ); +} + +/** + * Hook function for EditFilterMerged, replaces wfAkismetFilter + */ +function wfAkismetFilterMerged( $editPage, $text ) { + $spamObj = new AkismetKlik(); + $ret = $spamObj->filter( $editPage->mArticle->getTitle(), $text, '', $editPage ); + // Return convention for hooks is the inverse of $wgAkismetFilterCallback + return !$ret; +} + +# +# This class provides the interface to the filters +# +class AkismetKlik { + + function AkismetKlik( $settings = array() ) { + foreach ( $settings as $name => $value ) { + $this->$name = $value; + echo $value; + } + } + + /** + * @param Title $title + * @param string $text Text of section, or entire text if $editPage!=false + * @param string $section Section number or name + * @param EditPage $editPage EditPage if EditFilterMerged was called, false otherwise + * @return True if the edit should not be allowed, false otherwise + * If the return value is true, an error will have been sent to $wgOut + */ + function filter( &$title, $text, $section, $editPage = false ) { + global $wgArticle, $wgVersion, $wgOut, $wgParser, $wgUser; + global $siteURL, $wgAKkey; + + $fname = 'wfAkismetKlikFilter'; + wfProfileIn( $fname ); + + # Call the rest of the hook chain first + if ( $this->previousFilter ) { + $f = $this->previousFilter; + if ( $f( $title, $text, $section ) ) { + wfProfileOut( $fname ); + return true; + } + } + + $this->title = $title; + $this->text = $text; + $this->section = $section; + $text = str_replace( '.', '.', $text ); + + # Run parser to strip SGML comments and such out of the markup + if ( $editPage ) { + $editInfo = $editPage->mArticle->prepareTextForEdit( $text ); + $out = $editInfo->output; + $pgtitle = $title; + } else { + $options = new ParserOptions(); + $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options ); + $out = $wgParser->parse( $text, $title, $options ); + $pgtitle = ""; + } + $links = implode( "\n", array_keys( $out->getExternalLinks())); + + # Do the match + if ($wgUser->mName == "") $user = $IP; + else $user = $wgUser->mName; + $akismet = new Akismet($siteURL, $wgAKkey); + $akismet->setCommentAuthor($user); + $akismet->setCommentAuthorEmail($wgUser->mEmail); + $akismet->setCommentAuthorURL($links); + $akismet->setCommentContent($text); + $akismet->setCommentType("wiki"); + $akismet->setPermalink($siteURL . '/wiki/' . $pgtitle); + if($akismet->isCommentSpam()&&!$wgUser->isAllowed( 'bypassakismet' )) + { + wfDebugLog( 'AkismetKlik', "Match!\n" ); + if ( $editPage ) { + $editPage->spamPage( "http://akismet.com blacklist error" ); + } else { + EditPage::spamPage( "http://akismet.com blacklist error" ); + } + return true; + } + return false; + } +} Property changes on: trunk/extensions/AkismetKlik/AkismetKlik.php ___________________________________________________________________ Added: svn:eol-style + native _______________________________________________ MediaWiki-CVS mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs
