17 define(
'SQ_MOLLOM_SPAM', 0);
18 define(
'SQ_MOLLOM_UNSURE', 1);
19 define(
'SQ_MOLLOM_HAM', 2);
20 define(
'SQ_MOLLOM_UNKNOWN', 3);
21 define(
'SQ_MOLLOM_INCORRECT_CAPTCHA', 4);
23 require_once SQ_DATA_PATH.
'/private/conf/tools.inc';
86 private static $allowedReverseProxyAddresses = array();
96 private static $privateKey;
106 private static $publicKey;
114 private static $reverseProxy =
false;
124 private static $serverHost =
'xmlrpc.mollom.com';
134 private static $serverList = array();
142 private static $timeout = 10;
152 private static $userAgent =
'MollomPHP/1.1.3';
162 private static $version =
'1.0';
173 private static $session_expire_time = 30;
180 public static $default_server_list = array(
'http://xmlrpc3.mollom.com',
'http://xmlrpc2.mollom.com',
'http://xmlrpc1.mollom.com');
188 private function buildValue($value)
191 $type = gettype($value);
198 $value = htmlspecialchars($value, ENT_QUOTES,
'ISO-8859-15');
199 return '<value><string>'. $value .
'</string></value>'.
"\n";
203 $struct =
'<value>'.
"\n";
204 $struct .=
' <struct>'.
"\n";
207 foreach ($value as $key => $value) $struct .= str_replace(
"\n",
'',
'<member>'.
"\n" .
'<name>'. $key .
'</name>'. self::buildValue($value) .
'</member>') .
"\n";
209 $struct .=
' </struct>'.
"\n";
210 $struct .=
'</value>'.
"\n";
216 return '<value>'. $value .
'</value>'.
"\n";
234 $sessionId = (string) $sessionId;
235 $solution = (string) $solution;
238 $authorIp = self::getIpAddress();
241 $parameters[
'session_id'] = $sessionId;
242 $parameters[
'solution'] = $solution;
243 if($authorIp != null) $parameters[
'author_ip'] = (string) $authorIp;
246 $responseString = self::doCall(
'checkCaptcha', $parameters);
248 if(!isset($responseString->params->param->value->boolean))
throw new Exception(
'Invalid response in checkCapthca.');
251 if((
string) $responseString->params->param->value->boolean ==
'1')
return true;
281 public static function checkContent($sessionId = null, $postTitle = null, $postBody = null, $authorName = null, $authorUrl = null, $authorEmail = null, $authorOpenId = null, $authorId = null)
284 if($sessionId === null && $postTitle === null && $postBody === null && $authorName === null && $authorUrl === null && $authorEmail === null && $authorOpenId === null && $authorId === null)
throw new Exception(
'Specify at least on argument');
287 $parameters = array();
291 if($sessionId !== null) $parameters[
'session_id'] = (string) $sessionId;
292 if($postTitle !== null) $parameters[
'post_title'] = (string) $postTitle;
293 if($postBody !== null) $parameters[
'post_body'] = (string) $postBody;
294 if($authorName !== null) $parameters[
'author_name'] = (string) $authorName;
295 if($authorUrl !== null) $parameters[
'author_url'] = (string) $authorUrl;
296 if($authorEmail !== null) $parameters[
'author_mail'] = (string) $authorEmail;
297 if($authorOpenId != null) $parameters[
'author_openid'] = (string) $authorOpenId;
298 if($authorId != null) $parameters[
'author_id'] = (string) $authorId;
301 $authorIp = self::getIpAddress();
302 if($authorIp != null) $parameters[
'author_ip'] = (string) $authorIp;
304 if (SQ_TOOL_MOLLOM_TEST_MODE) {
305 $parameters[
'testing'] = TRUE;
309 $responseString = self::doCall(
'checkContent', $parameters);
312 if(!isset($responseString->params->param->value->struct->member))
throw new Exception(
'Invalid response in checkContent.');
315 foreach ($responseString->params->param->value->struct->member as $part)
324 $value = (string) $part->value->int;
329 $aReturn[
'spam'] =
'unknown';
333 $aReturn[
'spam'] =
'ham';
337 $aReturn[
'spam'] =
'spam';
341 $aReturn[
'spam'] =
'unsure';
347 $aReturn[
'quality'] = (float) $part->value->double;
351 $aReturn[
'session_id'] = (
string) $part->value->string;
368 private static function doCall($method, $parameters = array(), $server = null, $counter = 0, $test_mode =
true)
371 $countServerList = count(self::$serverList);
373 if($server === null && $countServerList == 0)
throw new Exception(
'No servers found, populate the serverlist. See setServerList().');
376 $method = (string) $method;
377 $parameters = (array) $parameters;
380 $aPossibleMethods = array(
'checkCaptcha',
'checkContent',
'getAudioCaptcha',
'getImageCaptcha',
'getServerList',
'getStatistics',
'sendFeedback',
'verifyKey');
383 if(!in_array($method, $aPossibleMethods))
throw new Exception(
'Invalid method. Only '. implode(
', ', $aPossibleMethods) .
' are possible methods.');
386 if(self::$publicKey === null)
throw new Exception(
'Public key wasn\'t set.');
389 if(self::$privateKey === null)
throw new Exception(
'Private key wasn\'t set.');
392 if($server === null) $server = self::$serverList[$counter];
395 $server = str_replace(array(
'http://',
'https://'),
'', $server);
398 $time = gmdate(
"Y-m-d\TH:i:s.\\0\\0\\0O", time());
401 $nonce = md5(time());
404 $hash = base64_encode(
405 pack(
"H*", sha1((str_pad(self::$privateKey, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
406 pack(
"H*", sha1((str_pad(self::$privateKey, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) .
407 $time .
':'. $nonce .
':'. self::$privateKey))))
411 $parameters[
'public_key'] = self::$publicKey;
412 $parameters[
'time'] = $time;
413 $parameters[
'hash'] = $hash;
414 $parameters[
'nonce'] = $nonce;
418 $requestBody =
'<?xml version="1.0"?>' .
"\n";
419 $requestBody .=
'<methodCall>' .
"\n";
420 $requestBody .=
' <methodName>mollom.'. $method .
'</methodName>' .
"\n";
421 $requestBody .=
' <params>' .
"\n";
422 $requestBody .=
' <param>'.
"\n";
423 $requestBody .=
' '. self::buildValue($parameters) .
"\n";
424 $requestBody .=
' </param>'.
"\n";
425 $requestBody .=
' </params>' .
"\n";
426 $requestBody .=
'</methodCall>' .
"\n";
429 'USERAGENT' => self::$userAgent,
430 'HTTP_VERSION' => CURL_HTTP_VERSION_1_0,
432 'FOLLOWLOCATION' =>
true,
434 'RETURNTRANSFER' =>
true,
435 'CONNECTTIMEOUT' => self::$timeout,
436 'TIMEOUT' => self::$timeout,
437 'POSTFIELDS' => $requestBody,
440 $details = fetch_url($server.
'/'.self::$version, $options);
441 $response = $details[
'response'];
442 $errorNumber = $details[
'errornumber'];
443 $errorString = $details[
'errorstring'];
446 if($response ===
false || $errorNumber != 0)
452 if($errorNumber == 28 && !isset(self::$serverList[$counter]) && $countServerList != 0)
throw new Exception(
'No more servers available, try to increase the timeout.');
455 elseif($errorNumber == 28 && isset(self::$serverList[$counter]))
return self::doCall($method, $parameters, self::$serverList[$counter], $counter);
458 else throw new Exception(
'Something went wrong. Maybe the following message can be handy.<br />'. $errorString, $errorNumber);
462 $parts = explode(
"\r\n\r\n", $response);
465 if(!isset($parts[0]) || !isset($parts[1]))
throw new Exception(
'Invalid response in doCall.');
468 $headers = $parts[0];
472 $body = implode(
'', $parts);
475 $aValidHeaders = array(
'HTTP/1.0 200',
'HTTP/1.1 200');
476 if(!in_array(substr($headers, 0, 12), $aValidHeaders))
throw new Exception(
'Invalid headers.');
479 $responseXML = @simplexml_load_string($body);
480 if($responseXML ===
false)
throw new Exception(
'Invalid body.');
482 if(isset($responseXML->fault))
484 $code = (isset($responseXML->fault->value->struct->member[0]->value->int)) ? (
int) $responseXML->fault->value->struct->member[0]->value->int :
'unknown';
485 $message = (isset($responseXML->fault->value->struct->member[1]->value->string)) ? (
string) $responseXML->fault->value->struct->member[1]->value->string :
'unknown';
492 if(self::$serverList === null) self::getServerList();
495 return self::doCall($method, $parameters, self::$serverList[$counter], $counter++);
505 log_dump(
'Mollom Error '.$code .
': '. $message);
535 $parameters = array();
538 $authorIp = self::getIpAddress();
541 if($sessionId != null) $parameters[
'session_id'] = (string) $sessionId;
542 if($authorIp != null) $parameters[
'author_ip'] = (string) $authorIp;
545 $responseString = self::doCall(
'getAudioCaptcha', $parameters);
548 if(!isset($responseString->params->param->value->struct->member))
throw new Exception(
'Invalid response in getAudioCaptcha.');
551 foreach ($responseString->params->param->value->struct->member as $part) $aReturn[(string) $part->name] = (
string) $part->value->string;
554 $aReturn[
'html'] =
'<object type="audio/mpeg" data="'. $aReturn[
'url'] .
'" width="50" height="16">'.
"\n"
555 .
"\t".
'<param name="autoplay" value="false" />'.
"\n"
556 .
"\t".
'<param name="controller" value="true" />'.
"\n"
584 $parameters = array();
587 $authorIp = self::getIpAddress();
590 if($sessionId !== null) $parameters[
'session_id'] = (string) $sessionId;
591 if($authorIp !== null) $parameters[
'author_ip'] = (string) $authorIp;
594 $responseString = self::doCall(
'getImageCaptcha', $parameters);
597 if(!isset($responseString->params->param->value->struct->member))
throw new Exception(
'Invalid response in getImageCaptcha.');
600 foreach ($responseString->params->param->value->struct->member as $part) $aReturn[(string) $part->name] = (
string) $part->value->string;
603 $aReturn[
'html'] =
'<img src="'. $aReturn[
'url'] .
'" alt="Mollom CAPTCHA" />';
618 if(!isset($_SERVER[
'REMOTE_ADDR']))
return null;
621 $ipAddress = $_SERVER[
'REMOTE_ADDR'];
623 if(self::$reverseProxy)
625 if(isset($_SERVER[
'HTTP_X_FORWARDED_FOR']))
627 if(!empty(self::$allowedReverseProxyAddresses) && in_array($ipAddress, self::$allowedProxyAddresses,
true))
629 return array_pop(explode(
',', $_SERVER[
'HTTP_X_FORWARDED_FOR']));
634 if(isset($_SERVER[
'HTTP_X_CLUSTER_CLIENT_IP']))
return $_SERVER[
'HTTP_X_CLUSTER_CLIENT_IP'];
650 self::$serverList = self::$default_server_list;
653 $responseString = self::doCall(
'getServerList', array(), self::$serverHost, $counter);
656 if(!isset($responseString->params->param->value->array->data->value))
throw new Exception(
'Invalid response in getServerList.');
659 self::$serverList = array();
662 foreach ($responseString->params->param->value->array->data->value as $server) self::$serverList[] = (string) $server->string;
664 if(count(self::$serverList) == 0) self::$serverList = self::$default_server_list;
667 return self::$serverList;
689 $aPossibleTypes = array(
'total_days',
'total_accepted',
'total_rejected',
'yesterday_accepted',
'yesterday_rejected',
'today_accepted',
'today_rejected');
692 $type = (string) $type;
695 if(!in_array($type, $aPossibleTypes))
throw new Exception(
'Invalid type. Only '. implode(
', ', $aPossibleTypes) .
' are possible types.');
698 $responseString = self::doCall(
'getStatistics', array(
'type' => $type));
701 if(!isset($responseString->params->param->value->int))
throw new Exception(
'Invalid response in getStatistics.');
704 return (
int) $responseString->params->param->value->int;
727 $aPossibleFeedback = array(
'spam',
'profanity',
'low-quality',
'unwanted');
730 $sessionId = (string) $sessionId;
731 $feedback = (string) $feedback;
734 if(!in_array($feedback, $aPossibleFeedback))
throw new Exception(
'Invalid feedback. Only '. implode(
', ', $aPossibleFeedback) .
' are possible feedback-strings.');
737 $parameters[
'session_id'] = $sessionId;
738 $parameters[
'feedback'] = $feedback;
741 $responseString = self::doCall(
'sendFeedback', $parameters);
744 if(!isset($responseString->params->param->value->boolean))
throw new Exception(
'Invalid response in sendFeedback.');
747 if((
string) $responseString->params->param->value->boolean == 1)
return true;
763 self::$allowedReverseProxyAddresses = (array) $addresses;
766 self::$reverseProxy = (!empty($addresses)) ?
true :
false;
778 self::$privateKey = (string) $key;
790 self::$publicKey = (string) $key;
803 $server = (array) $servers;
806 foreach ($servers as $server) self::$serverList[] = $server;
819 $timeout = (int) $timeout;
822 if($timeout == 0)
throw new Exception(
'Invalid timeout. Timeout shouldn\'t be 0.');
825 self::$timeout = $timeout;
837 self::$userAgent .=
' '. (string) $newUserAgent;
852 $responseString = self::doCall(
'verifyKey');
855 if(!isset($responseString->params->param->value->boolean))
return false;
858 if((
string) $responseString->params->param->value->boolean ==
'1')
return true;
881 5 =>
"Author OpenId",
892 public static function loadMollom()
894 if (!SQ_TOOL_MOLLOM_PUBLIC_KEY || !SQ_TOOL_MOLLOM_PRIVATE_KEY) {
898 self::setPublicKey(SQ_TOOL_MOLLOM_PUBLIC_KEY);
899 self::setPrivateKey(SQ_TOOL_MOLLOM_PRIVATE_KEY);
901 $server_list = Array();
902 if (!empty($_SESSION[
'SQ_MOLLOM_SERVER_LIST'])) {
903 $server_list = unserialize($_SESSION[
'SQ_MOLLOM_SERVER_LIST']);
904 self::setServerList($server_list);
906 $server_list = self::getServerList();
907 $_SESSION[
'SQ_MOLLOM_SERVER_LIST'] = serialize($server_list);
910 return !empty($server_list);
926 $unknown_submission = SQ_TOOL_MOLLOM_ACCEPT_BYPASSED_SUBMISSION ? SQ_MOLLOM_HAM : SQ_MOLLOM_SPAM;
929 self::refreshMollomSession($assetid);
931 if (!self::loadMollom()) {
933 return $unknown_submission;
936 if (self::checkCaptchaResponse($assetid)) {
938 unset($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid]);
939 return SQ_MOLLOM_HAM;
941 }
else if (isset($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid])) {
942 return SQ_MOLLOM_INCORRECT_CAPTCHA;
945 $response = self::checkContent(
947 isset($parameters[0]) ? $parameters[0] : null,
948 isset($parameters[1]) ? $parameters[1] : null,
949 isset($parameters[2]) ? $parameters[2] : null,
950 isset($parameters[3]) ? $parameters[3] : null,
951 isset($parameters[4]) ? $parameters[4] : null,
952 isset($parameters[5]) ? $parameters[5] : null,
953 isset($parameters[6]) ? $parameters[6] : null
956 if (empty($response)) {
959 return $unknown_submission;
962 $spam_status = array_get_index($response,
'spam',
'unknown');
963 $quality = array_get_index($response,
'quality', 0);
964 $session_id = array_get_index($response,
'session_id',
'');
966 switch ($spam_status) {
969 $mollom_captcha = Array();
970 $image_captcha = self::getImageCaptcha($session_id);
971 $mollom_captcha[
'session_id'] = $session_id;
972 $mollom_captcha[
'image_url'] = array_get_index($image_captcha,
'url',
'');
973 $mollom_captcha[
'session_time'] = time();
975 $_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid] = $mollom_captcha;
976 return SQ_MOLLOM_UNSURE;
980 return SQ_MOLLOM_SPAM;
984 return SQ_MOLLOM_HAM;
986 }
catch (Exception $e) {
987 $add_msg = SQ_TOOL_MOLLOM_ACCEPT_BYPASSED_SUBMISSION ?
'Submission accepted anyway.' :
'Submission not accepted.';
988 trigger_error(
"Error occured when trying to verify the submission with the Mollom: ".$e->getMessage().
"\n".$add_msg, E_USER_WARNING);
991 return $unknown_submission;
1009 self::refreshMollomSession($assetid);
1011 if (empty($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'session_id'])) {
1015 $captcha = self::getMollomCaptchaImageKeywordReplacement($assetid).
1016 self::getMollomCaptchaRegenLinkKeywordReplacement($assetid).
1017 "<br />".self::getMollomCaptchaFieldKeywordReplacement($assetid);
1033 self::refreshMollomSession($assetid);
1035 if (empty($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'session_id']) || (empty($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'image_url']))) {
1039 $name =
'sq_mollom_captcha_'.$assetid.
'_input';
1044 text_box($name,
'', $width, $max, FALSE,
'');
1045 hidden_field($name.
'_session', $_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'session_id']);
1046 $input_box = ob_get_contents();
1064 self::refreshMollomSession($assetid);
1066 if (!empty($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'session_id'])) {
1067 $image_id =
'sq_mollom_captcha_'.$assetid.
'_image';
1068 $image_url = $_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'image_url'];
1070 return '<a href="#" onClick="var img=document.getElementById(\''.$image_id.
'\'); img.src=\
''.$image_url.
'?SQ_MOLLOM_REGEN_PARAM=\'+Math.random(); return false;">'.$regen_text.
'</a>';
1087 self::refreshMollomSession($assetid);
1089 if (!empty($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'session_id'])) {
1090 if (empty($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'audio_url'])) {
1091 if (!self::loadMollom()) {
1094 $audio_captcha = self::getAudioCaptcha($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'session_id']);
1095 $_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'audio_url'] = $audio_captcha[
'url'];
1098 return '<object type="audio/mpeg" data="'.$_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'audio_url'].
'" width="50" height="16">'.
"\n"
1099 .
"\t".
'<param name="autoplay" value="false" />'.
"\n"
1100 .
"\t".
'<param name="controller" value="true" />'.
"\n"
1118 self::refreshMollomSession($assetid);
1120 if (!empty($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'image_url'])) {
1121 $image_id =
'sq_mollom_captcha_'.$assetid.
'_image';
1122 return '<img id="'.$image_id.
'" src="'.$_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'image_url'].
'" alt="CAPTCHA Image" />';
1139 if (!self::loadMollom()) {
1143 $name =
'sq_mollom_captcha_'.$assetid.
'_input';
1144 $captcha_solution = array_get_index($_POST, $name,
'');
1145 $captcha_session_id = array_get_index($_POST, $name.
'_session',
'');
1147 if (empty($captcha_session_id) || empty($captcha_solution)) {
1151 return self::checkCaptcha($captcha_session_id, $captcha_solution);
1166 if (isset($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid][
'session_id'])) {
1168 $session_time = array_get_index($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid],
'session_time', 0);
1169 $current_time = time();
1170 if (($current_time - $session_time) > (60 * self::$session_expire_time)) {
1171 unset($_SESSION[
'SQ_MOLLOM_CAPTCHA_'.$assetid]);