r114928 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r114927‎ | r114928 | r114929 >
Date:19:05, 16 April 2012
Author:reedy
Status:new (Comments)
Tags:
Comment:
Modified paths:
  • /trunk/extensions/AkismetKlik (added) (history)
  • /trunk/extensions/AkismetKlik/Akismet.class.php (added) (history)
  • /trunk/extensions/AkismetKlik/AkismetKlik.php (added) (history)

Diff [purge]

Index: trunk/extensions/AkismetKlik/Akismet.class.php
@@ -0,0 +1,474 @@
 2+<?php
 3+
 4+/**
 5+ * Akismet anti-comment spam service
 6+ *
 7+ * The class in this package allows use of the {@link http://akismet.com Akismet} anti-comment spam service in any PHP5 application.
 8+ *
 9+ * This service performs a number of checks on submitted data and returns whether or not the data is likely to be spam.
 10+ *
 11+ * 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.
 12+ *
 13+ * For commercial use, please {@link http://akismet.com/commercial/ visit the Akismet commercial licensing page}.
 14+ *
 15+ * Please be aware that this class is PHP5 only. Attempts to run it under PHP4 will most likely fail.
 16+ *
 17+ * See the Akismet class documentation page linked to below for usage information.
 18+ *
 19+ * @package akismet
 20+ * @author Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
 21+ * @version 0.5
 22+ * @copyright Alex Potsides, {@link http://www.achingbrain.net http://www.achingbrain.net}
 23+ * @license http://www.opensource.org/licenses/bsd-license.php BSD 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+ * <code>
 34+ * $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue');
 35+ * $akismet->setCommentAuthor($name);
 36+ * $akismet->setCommentAuthorEmail($email);
 37+ * $akismet->setCommentAuthorURL($url);
 38+ * $akismet->setCommentContent($comment);
 39+ * $akismet->setPermalink('http://www.example.com/blog/alex/someurl/');
 40+ *
 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+ * Optionally you may wish to check if your WordPress API key is valid as in the example below.
 48+ *
 49+ * <code>
 50+ * $akismet = new Akismet('http://www.example.com/blog/', 'aoeu1aoue');
 51+ *
 52+ * if($akismet->isKeyValid()) {
 53+ * // api key is okay
 54+ * } else {
 55+ * // api key is invalid
 56+ * }
 57+ * </code>
 58+ *
 59+ * @package akismet
 60+ * @name Akismet
 61+ * @version 0.5
 62+ * @author Alex Potsides
 63+ * @link http://www.achingbrain.net/
 64+ */
 65+class Akismet {
 66+ private $version = '0.5';
 67+ private $wordPressAPIKey;
 68+ private $blogURL;
 69+ private $comment;
 70+ private $apiPort;
 71+ private $akismetServer;
 72+ private $akismetVersion;
 73+ private $requestFactory;
 74+
 75+ // This prevents some potentially sensitive information from being sent accross the wire.
 76+ private $ignore = array('HTTP_COOKIE',
 77+ 'HTTP_X_FORWARDED_FOR',
 78+ 'HTTP_X_FORWARDED_HOST',
 79+ 'HTTP_MAX_FORWARDS',
 80+ 'HTTP_X_FORWARDED_SERVER',
 81+ 'REDIRECT_STATUS',
 82+ 'SERVER_PORT',
 83+ 'PATH',
 84+ 'DOCUMENT_ROOT',
 85+ 'SERVER_ADMIN',
 86+ 'QUERY_STRING',
 87+ 'PHP_SELF' );
 88+
 89+ /**
 90+ * @param string $blogURL The URL of your blog.
 91+ * @param string $wordPressAPIKey WordPress API key.
 92+ */
 93+ public function __construct($blogURL, $wordPressAPIKey) {
 94+ $this->blogURL = $blogURL;
 95+ $this->wordPressAPIKey = $wordPressAPIKey;
 96+
 97+ // Set some default values
 98+ $this->apiPort = 80;
 99+ $this->akismetServer = 'rest.akismet.com';
 100+ $this->akismetVersion = '1.1';
 101+ $this->requestFactory = new SocketWriteReadFactory();
 102+
 103+ // Start to populate the comment data
 104+ $this->comment['blog'] = $blogURL;
 105+
 106+ if(isset($_SERVER['HTTP_USER_AGENT'])) {
 107+ $this->comment['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
 108+ }
 109+
 110+ if(isset($_SERVER['HTTP_REFERER'])) {
 111+ $this->comment['referrer'] = $_SERVER['HTTP_REFERER'];
 112+ }
 113+
 114+ /*
 115+ * This is necessary if the server PHP5 is running on has been set up to run PHP4 and
 116+ * PHP5 concurently and is actually running through a separate proxy al a these instructions:
 117+ * http://www.schlitt.info/applications/blog/archives/83_How_to_run_PHP4_and_PHP_5_parallel.html
 118+ * and http://wiki.coggeshall.org/37.html
 119+ * Otherwise the user_ip appears as the IP address of the PHP4 server passing the requests to the
 120+ * PHP5 one...
 121+ */
 122+ if(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR')) {
 123+ $this->comment['user_ip'] = $_SERVER['REMOTE_ADDR'];
 124+ } else {
 125+ $this->comment['user_ip'] = getenv('HTTP_X_FORWARDED_FOR');
 126+ }
 127+ }
 128+
 129+ /**
 130+ * Makes a request to the Akismet service to see if the API key passed to the constructor is valid.
 131+ *
 132+ * Use this method if you suspect your API key is invalid.
 133+ *
 134+ * @return bool True is if the key is valid, false if not.
 135+ */
 136+ public function isKeyValid() {
 137+ // Check to see if the key is valid
 138+ $response = $this->sendRequest('key=' . $this->wordPressAPIKey . '&blog=' . $this->blogURL, $this->akismetServer, '/' . $this->akismetVersion . '/verify-key');
 139+ return $response[1] == 'valid';
 140+ }
 141+
 142+ // makes a request to the Akismet service
 143+ private function sendRequest($request, $host, $path) {
 144+ $http_request = "POST " . $path . " HTTP/1.0\r\n";
 145+ $http_request .= "Host: " . $host . "\r\n";
 146+ $http_request .= "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n";
 147+ $http_request .= "Content-Length: " . strlen($request) . "\r\n";
 148+ $http_request .= "User-Agent: Akismet PHP5 Class " . $this->version . " | Akismet/1.11\r\n";
 149+ $http_request .= "\r\n";
 150+ $http_request .= $request;
 151+
 152+ $requestSender = $this->requestFactory->createRequestSender();
 153+ $response = $requestSender->send($host, $this->apiPort, $http_request);
 154+
 155+ return explode("\r\n\r\n", $response, 2);
 156+ }
 157+
 158+ // Formats the data for transmission
 159+ private function getQueryString() {
 160+ foreach($_SERVER as $key => $value) {
 161+ if(!in_array($key, $this->ignore)) {
 162+ if($key == 'REMOTE_ADDR') {
 163+ $this->comment[$key] = $this->comment['user_ip'];
 164+ } else {
 165+ $this->comment[$key] = $value;
 166+ }
 167+ }
 168+ }
 169+
 170+ $query_string = '';
 171+
 172+ foreach($this->comment as $key => $data) {
 173+ if(!is_array($data)) {
 174+ $query_string .= $key . '=' . urlencode(stripslashes($data)) . '&';
 175+ }
 176+ }
 177+
 178+ return $query_string;
 179+ }
 180+
 181+ /**
 182+ * Tests for spam.
 183+ *
 184+ * 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.
 185+ *
 186+ * @return bool True if the comment is spam, false if not
 187+ * @throws Will throw an exception if the API key passed to the constructor is invalid.
 188+ */
 189+ public function isCommentSpam() {
 190+ $response = $this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.rest.akismet.com', '/' . $this->akismetVersion . '/comment-check');
 191+
 192+ if($response[1] == 'invalid' && !$this->isKeyValid()) {
 193+ 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/');
 194+ }
 195+
 196+ return ($response[1] == 'true');
 197+ }
 198+
 199+ /**
 200+ * Submit spam that is incorrectly tagged as ham.
 201+ *
 202+ * 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.
 203+ */
 204+ public function submitSpam() {
 205+ $this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-spam');
 206+ }
 207+
 208+ /**
 209+ * Submit ham that is incorrectly tagged as spam.
 210+ *
 211+ * 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.
 212+ */
 213+ public function submitHam() {
 214+ $this->sendRequest($this->getQueryString(), $this->wordPressAPIKey . '.' . $this->akismetServer, '/' . $this->akismetVersion . '/submit-ham');
 215+ }
 216+
 217+ /**
 218+ * To override the user IP address when submitting spam/ham later on
 219+ *
 220+ * @param string $userip An IP address. Optional.
 221+ */
 222+ public function setUserIP($userip) {
 223+ $this->comment['user_ip'] = $userip;
 224+ }
 225+
 226+ /**
 227+ * To override the referring page when submitting spam/ham later on
 228+ *
 229+ * @param string $referrer The referring page. Optional.
 230+ */
 231+ public function setReferrer($referrer) {
 232+ $this->comment['referrer'] = $referrer;
 233+ }
 234+
 235+ /**
 236+ * A permanent URL referencing the blog post the comment was submitted to.
 237+ *
 238+ * @param string $permalink The URL. Optional.
 239+ */
 240+ public function setPermalink($permalink) {
 241+ $this->comment['permalink'] = $permalink;
 242+ }
 243+
 244+ /**
 245+ * The type of comment being submitted.
 246+ *
 247+ * May be blank, comment, trackback, pingback, or a made up value like "registration" or "wiki".
 248+ */
 249+ public function setCommentType($commentType) {
 250+ $this->comment['comment_type'] = $commentType;
 251+ }
 252+
 253+ /**
 254+ * The name that the author submitted with the comment.
 255+ */
 256+ public function setCommentAuthor($commentAuthor) {
 257+ $this->comment['comment_author'] = $commentAuthor;
 258+ }
 259+
 260+ /**
 261+ * The email address that the author submitted with the comment.
 262+ *
 263+ * The address is assumed to be valid.
 264+ */
 265+ public function setCommentAuthorEmail($authorEmail) {
 266+ $this->comment['comment_author_email'] = $authorEmail;
 267+ }
 268+
 269+ /**
 270+ * The URL that the author submitted with the comment.
 271+ */
 272+ public function setCommentAuthorURL($authorURL) {
 273+ $this->comment['comment_author_url'] = $authorURL;
 274+ }
 275+
 276+ /**
 277+ * The comment's body text.
 278+ */
 279+ public function setCommentContent($commentBody) {
 280+ $this->comment['comment_content'] = $commentBody;
 281+ }
 282+
 283+ /**
 284+ * Lets you override the user agent used to submit the comment.
 285+ * you may wish to do this when submitting ham/spam.
 286+ * Defaults to $_SERVER['HTTP_USER_AGENT']
 287+ */
 288+ public function setCommentUserAgent($userAgent) {
 289+ $this->comment['user_agent'] = $userAgent;
 290+ }
 291+
 292+ /**
 293+ * Defaults to 80
 294+ */
 295+ public function setAPIPort($apiPort) {
 296+ $this->apiPort = $apiPort;
 297+ }
 298+
 299+ /**
 300+ * Defaults to rest.akismet.com
 301+ */
 302+ public function setAkismetServer($akismetServer) {
 303+ $this->akismetServer = $akismetServer;
 304+ }
 305+
 306+ /**
 307+ * Defaults to '1.1'
 308+ *
 309+ * @param string $akismetVersion
 310+ */
 311+ public function setAkismetVersion($akismetVersion) {
 312+ $this->akismetVersion = $akismetVersion;
 313+ }
 314+
 315+ /**
 316+ * Used by unit tests to mock transport layer
 317+ *
 318+ * @param AkismetRequestFactory $requestFactory
 319+ */
 320+ public function setRequestFactory($requestFactory) {
 321+ $this->requestFactory = $requestFactory;
 322+ }
 323+}
 324+
 325+/**
 326+ * Used internally by Akismet
 327+ *
 328+ * 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.
 329+ *
 330+ * 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}.
 331+ *
 332+ * N.B. It is not necessary to call this class directly to use the Akismet class.
 333+ *
 334+ * @package akismet
 335+ * @name SocketWriteRead
 336+ * @version 0.5
 337+ * @author Alex Potsides
 338+ * @link http://www.achingbrain.net/
 339+ */
 340+class SocketWriteRead implements AkismetRequestSender {
 341+ private $response;
 342+ private $errorNumber;
 343+ private $errorString;
 344+
 345+ public function __construct() {
 346+ $this->errorNumber = 0;
 347+ $this->errorString = '';
 348+ }
 349+
 350+ /**
 351+ * Sends the data to the remote host.
 352+ *
 353+ * @param string $host The host to send/receive data.
 354+ * @param int $port The port on the remote host.
 355+ * @param string $request The data to send.
 356+ * @param int $responseLength The amount of data to read. Defaults to 1160 bytes.
 357+ * @throws An exception is thrown if a connection cannot be made to the remote host.
 358+ * @returns The server response
 359+ */
 360+ public function send($host, $port, $request, $responseLength = 1160) {
 361+ $response = '';
 362+
 363+ $fs = fsockopen($host, $port, $this->errorNumber, $this->errorString, 3);
 364+
 365+ if($this->errorNumber != 0) {
 366+ throw new Exception('Error connecting to host: ' . $host . ' Error number: ' . $this->errorNumber . ' Error message: ' . $this->errorString);
 367+ }
 368+
 369+ if($fs !== false) {
 370+ @fwrite($fs, $request);
 371+
 372+ while(!feof($fs)) {
 373+ $response .= fgets($fs, $responseLength);
 374+ }
 375+
 376+ fclose($fs);
 377+ }
 378+
 379+ return $response;
 380+ }
 381+
 382+ /**
 383+ * Returns the server response text
 384+ *
 385+ * @return string
 386+ */
 387+ public function getResponse() {
 388+ return $this->response;
 389+ }
 390+
 391+ /**
 392+ * Returns the error number
 393+ *
 394+ * If there was no error, 0 will be returned.
 395+ *
 396+ * @return int
 397+ */
 398+ public function getErrorNumner() {
 399+ return $this->errorNumber;
 400+ }
 401+
 402+ /**
 403+ * Returns the error string
 404+ *
 405+ * If there was no error, an empty string will be returned.
 406+ *
 407+ * @return string
 408+ */
 409+ public function getErrorString() {
 410+ return $this->errorString;
 411+ }
 412+}
 413+
 414+/**
 415+ * Used internally by the Akismet class and to mock the Akismet anti spam service in
 416+ * the unit tests.
 417+ *
 418+ * N.B. It is not necessary to call this class directly to use the Akismet class.
 419+ *
 420+ * @package akismet
 421+ * @name SocketWriteReadFactory
 422+ * @version 0.5
 423+ * @author Alex Potsides
 424+ * @link http://www.achingbrain.net/
 425+ */
 426+class SocketWriteReadFactory implements AkismetRequestFactory {
 427+
 428+ public function createRequestSender() {
 429+ return new SocketWriteRead();
 430+ }
 431+}
 432+
 433+/**
 434+ * Used internally by the Akismet class and to mock the Akismet anti spam service in
 435+ * the unit tests.
 436+ *
 437+ * N.B. It is not necessary to implement this class to use the Akismet class.
 438+ *
 439+ * @package akismet
 440+ * @name AkismetRequestSender
 441+ * @version 0.5
 442+ * @author Alex Potsides
 443+ * @link http://www.achingbrain.net/
 444+ */
 445+interface AkismetRequestSender {
 446+
 447+ /**
 448+ * Sends the data to the remote host.
 449+ *
 450+ * @param string $host The host to send/receive data.
 451+ * @param int $port The port on the remote host.
 452+ * @param string $request The data to send.
 453+ * @param int $responseLength The amount of data to read. Defaults to 1160 bytes.
 454+ * @throws An exception is thrown if a connection cannot be made to the remote host.
 455+ * @returns The server response
 456+ */
 457+ public function send($host, $port, $request, $responseLength = 1160);
 458+}
 459+
 460+/**
 461+ * Used internally by the Akismet class and to mock the Akismet anti spam service in
 462+ * the unit tests.
 463+ *
 464+ * N.B. It is not necessary to implement this class to use the Akismet class.
 465+ *
 466+ * @package akismet
 467+ * @name AkismetRequestFactory
 468+ * @version 0.5
 469+ * @author Alex Potsides
 470+ * @link http://www.achingbrain.net/
 471+ */
 472+interface AkismetRequestFactory {
 473+
 474+ public function createRequestSender();
 475+}
Property changes on: trunk/extensions/AkismetKlik/Akismet.class.php
___________________________________________________________________
Added: svn:eol-style
1476 + native
Index: trunk/extensions/AkismetKlik/AkismetKlik.php
@@ -0,0 +1,151 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) {
 4+ exit;
 5+}
 6+
 7+#
 8+# Include PHP5 Akismet class from http://www.achingbrain.net/stuff/akismet (GPL)
 9+#
 10+require_once('Akismet.class.php');
 11+
 12+#Extension credits
 13+$wgExtensionCredits['other'][] = array(
 14+ 'name' => 'AkismetKlik',
 15+ 'author' => 'Carl Austin Bennett',
 16+ 'url' => 'http://www.mediawiki.org/wiki/Extension:AkismetKlik',
 17+ 'description' => 'Rejects edits from suspected comment spammers on Akismet\'s blacklist.',
 18+);
 19+
 20+# Set site-specific configuration values
 21+#$wgAKkey='867-5309';
 22+#$siteURL='http://wiki.example.org';
 23+
 24+#
 25+# MediaWiki hooks
 26+#
 27+# Loader for spam blacklist feature
 28+# Include this from LocalSettings.php
 29+
 30+global $wgAkismetFilterCallback, $wgPreAkismetFilterCallback, $wgUser;
 31+$wgPreAkismetFilterCallback = false;
 32+
 33+if ( defined( 'MW_SUPPORTS_EDITFILTERMERGED' ) ) {
 34+ $wgHooks['EditFilterMerged'][] = 'wfAkismetFilterMerged';
 35+} else {
 36+ if ( $wgFilterCallback ) {
 37+ $wgPreAkismetFilterCallback = $wgFilterCallback;
 38+ }
 39+ $wgFilterCallback = 'wfAkismetFilter';
 40+}
 41+
 42+#$wgHooks['EditFilter'][] = 'wfAkismetFilter';
 43+
 44+/**
 45+ * Get an instance of AkismetKlik and do some first-call initialisation.
 46+ * All actual functionality is implemented in that object
 47+ */
 48+function wfAkismetKlikObject() {
 49+ global $wgSpamBlacklistSettings, $wgPreSpamFilterCallback;
 50+ static $spamObj;
 51+ if ( !$spamObj ) {
 52+ $spamObj = new AkismetKlik ( $wgSpamBlacklistSettings );
 53+ $spamObj->previousFilter = $wgPreSpamFilterCallback;
 54+ }
 55+ return $spamObj;
 56+}
 57+
 58+/**
 59+ * Hook function for $wgFilterCallback
 60+ */
 61+function wfAkismetFilter( &$title, $text, $section ) {
 62+ $spamObj = wfAkismetKlikObject();
 63+ return $spamObj->filter( $title, $text, $section );
 64+}
 65+
 66+/**
 67+ * Hook function for EditFilterMerged, replaces wfAkismetFilter
 68+ */
 69+function wfAkismetFilterMerged( $editPage, $text ) {
 70+ $spamObj = new AkismetKlik();
 71+ $ret = $spamObj->filter( $editPage->mArticle->getTitle(), $text, '', $editPage );
 72+ // Return convention for hooks is the inverse of $wgAkismetFilterCallback
 73+ return !$ret;
 74+}
 75+
 76+#
 77+# This class provides the interface to the filters
 78+#
 79+class AkismetKlik {
 80+
 81+ function AkismetKlik( $settings = array() ) {
 82+ foreach ( $settings as $name => $value ) {
 83+ $this->$name = $value;
 84+ echo $value;
 85+ }
 86+ }
 87+
 88+ /**
 89+ * @param Title $title
 90+ * @param string $text Text of section, or entire text if $editPage!=false
 91+ * @param string $section Section number or name
 92+ * @param EditPage $editPage EditPage if EditFilterMerged was called, false otherwise
 93+ * @return True if the edit should not be allowed, false otherwise
 94+ * If the return value is true, an error will have been sent to $wgOut
 95+ */
 96+ function filter( &$title, $text, $section, $editPage = false ) {
 97+ global $wgArticle, $wgVersion, $wgOut, $wgParser, $wgUser;
 98+ global $siteURL, $wgAKkey;
 99+
 100+ $fname = 'wfAkismetKlikFilter';
 101+ wfProfileIn( $fname );
 102+
 103+ # Call the rest of the hook chain first
 104+ if ( $this->previousFilter ) {
 105+ $f = $this->previousFilter;
 106+ if ( $f( $title, $text, $section ) ) {
 107+ wfProfileOut( $fname );
 108+ return true;
 109+ }
 110+ }
 111+
 112+ $this->title = $title;
 113+ $this->text = $text;
 114+ $this->section = $section;
 115+ $text = str_replace( '.', '.', $text );
 116+
 117+ # Run parser to strip SGML comments and such out of the markup
 118+ if ( $editPage ) {
 119+ $editInfo = $editPage->mArticle->prepareTextForEdit( $text );
 120+ $out = $editInfo->output;
 121+ $pgtitle = $title;
 122+ } else {
 123+ $options = new ParserOptions();
 124+ $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options );
 125+ $out = $wgParser->parse( $text, $title, $options );
 126+ $pgtitle = "";
 127+ }
 128+ $links = implode( "\n", array_keys( $out->getExternalLinks()));
 129+
 130+ # Do the match
 131+ if ($wgUser->mName == "") $user = $IP;
 132+ else $user = $wgUser->mName;
 133+ $akismet = new Akismet($siteURL, $wgAKkey);
 134+ $akismet->setCommentAuthor($user);
 135+ $akismet->setCommentAuthorEmail($wgUser->mEmail);
 136+ $akismet->setCommentAuthorURL($links);
 137+ $akismet->setCommentContent($text);
 138+ $akismet->setCommentType("wiki");
 139+ $akismet->setPermalink($siteURL . '/wiki/' . $pgtitle);
 140+ if($akismet->isCommentSpam()&&!$wgUser->isAllowed( 'bypassakismet' ))
 141+ {
 142+ wfDebugLog( 'AkismetKlik', "Match!\n" );
 143+ if ( $editPage ) {
 144+ $editPage->spamPage( "http://akismet.com blacklist error" );
 145+ } else {
 146+ EditPage::spamPage( "http://akismet.com blacklist error" );
 147+ }
 148+ return true;
 149+ }
 150+ return false;
 151+ }
 152+}
Property changes on: trunk/extensions/AkismetKlik/AkismetKlik.php
___________________________________________________________________
Added: svn:eol-style
1153 + native

Comments

#Comment by 😂 (talk | contribs)   20:06, 16 April 2012

Ugh, why why why would you create a new extension in Subversion that now has to have its history exported to Git when we can create a new repo in Gerrit in less than 10 minutes?

No, don't bother deleting it now, it's too late.