Version: current

      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 $akismetServer;
     61     private $akismetVersion;
     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 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -