mollom.module

Tracking 5.x-1.x branch
  1. drupal
    1. 5 contributions/mollom/mollom.module
    2. 6 contributions/mollom/mollom.module
    3. 7 contributions/mollom/mollom.module

Main file for the Mollom module, which protects against comment and contact form spam..

Functions & methods

NameDescription
mollomCall a remote procedure at the Mollom server. This function will automatically add the information required to authenticate against Mollom.
mollom_admin_settings
mollom_ajax_callbackAn AJAX callback to retrieve a CAPTCHA.
mollom_captcha_prerender
mollom_commentThis function is called when a comment is inserted.
mollom_comment_admin_overview_submit
mollom_data_comment_formThis function will be called by mollom_validate to prepare the XML-RPC data from the comment submission form's $form_values ...
mollom_data_contact_mail_pageThis function will be called by mollom_validate to prepare the XML-RPC data from the comment submission form's $form_values ...
mollom_data_contact_mail_userThis function will be called by mollom_validate to prepare the XML-RPC data from the comment submission form's $form_values ...
mollom_data_node_formThis function will be called by mollom_validate to prepare the XML-RPC data from the comment submission form's $form_values ...
mollom_form_alterThis function intercepts all forms in Drupal and Mollom enables them if necessary.
mollom_get_dataHelper function used to load a Mollom session ID.
mollom_helpImplementation of hook_help().
mollom_linkImplementation of hook_link().
mollom_mail_alterImplementation of hook_mail_alter().
mollom_menuImplementation of hook_menu().
mollom_nodeapiThis function is called when a node is inserted.
mollom_node_admin_overview_submit
mollom_permImplementation of hook_perm().
mollom_protectable_formsThis function lists all the forms that you can protect with Mollom. If you want to protect additional forms with Mollom add the form ID to this list.
mollom_protect_form_analysis
mollom_protect_form_captcha
mollom_report_commentThis function is used to report a comment as feedback and to delete it.
mollom_report_comment_submitThis function is used to delete a comment and to optionally send feedback to Mollom.
mollom_report_contactThis function is used to report a contact form message as inappropriate.
mollom_report_contact_submitThis function is used to report a contact form message as inappropriate.
mollom_report_nodeThis function is used to delete a node and to optionally send feedback to Mollom.
mollom_report_node_submitThis function is used to delete a node and to optionally send feedback to Mollom.
mollom_set_dataHelper function used to store a Mollom session ID.
mollom_validateThis function is a generic validate function that will protect any given form as long there is a variable for it (i.e. as long it is configured to be protected through Mollom). To protect a form in Drupal with Mollom, just variable_set() the…
_mollom_authenticationThis function generate an array with all the information required to authenticate against Mollom. To prevent that requests are forged and that you are impersonated, each request is signed with a hash computed based on a private key and a timestamp.
_mollom_fallback
_mollom_feedback_optionsReturn a list of the possible feedback options for content.
_mollom_get_modeGiven a form ID, this function will return the strategy that is used to protect this form. Could be MOLLOM_MODE_DISABLED (none), MOLLOM_MODE_CAPTCHA (CAPTCHAs only) or MOLLOM_MODE_ANALYSIS (text analysis with smart CAPTCHA support).
_mollom_ip_address
_mollom_retrieve_server_listThis function is used to refresh the list of servers that can be used to contact Mollom.
_mollom_verify_key

Constants

NameDescription
MOLLOM_ANALYSIS_HAM
MOLLOM_ANALYSIS_SPAM
MOLLOM_ANALYSIS_UNSURE
MOLLOM_API_VERSION
MOLLOM_ERROR
MOLLOM_FALLBACK_ACCEPT
MOLLOM_FALLBACK_BLOCK
MOLLOM_MODE_ANALYSIS
MOLLOM_MODE_CAPTCHA
MOLLOM_MODE_DISABLED
MOLLOM_REDIRECT
MOLLOM_REFRESH

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Main file for the Mollom module, which protects against comment and contact form spam..
  5. */
  6. define('MOLLOM_API_VERSION', '1.0');
  7. define('MOLLOM_ANALYSIS_HAM' , 1);
  8. define('MOLLOM_ANALYSIS_SPAM' , 2);
  9. define('MOLLOM_ANALYSIS_UNSURE' , 3);
  10. define('MOLLOM_MODE_DISABLED', 0);
  11. define('MOLLOM_MODE_CAPTCHA' , 1);
  12. define('MOLLOM_MODE_ANALYSIS', 2);
  13. define('MOLLOM_FALLBACK_BLOCK' , 0);
  14. define('MOLLOM_FALLBACK_ACCEPT', 1);
  15. define('MOLLOM_ERROR' , 1000);
  16. define('MOLLOM_REFRESH' , 1100);
  17. define('MOLLOM_REDIRECT', 1200);
  18. /**
  19. * Implementation of hook_help().
  20. */
  21. function mollom_help($section) {
  22. if ($section == 'admin/settings/mollom') {
  23. return t("Allowing users to react, participate and contribute while still keeping your site's content under control can be a huge challenge. Mollom is a web service that helps you identify content quality and, more importantly, helps you stop spam. When content moderation becomes easier, you have more time and energy to interact with your web community. More information about Mollom is available on the <a href=\"@mollom-website\">Mollom website</a> or in the <a href=\"@mollom-faq\">Mollom FAQ</a>. For support, please consult the <a href=\"@mollom-support\">Mollom support page</a>.",
  24. array('@mollom-website' => 'http://mollom.com',
  25. '@mollom-faq' => 'http://mollom.com/faq',
  26. '@mollom-support' => 'http://mollom.com/support'));
  27. }
  28. }
  29. /**
  30. * Implementation of hook_link().
  31. */
  32. function mollom_link($type, $object = NULL, $teaser = NULL) {
  33. $links = array();
  34. // Only show the links if the module is configured.
  35. if (variable_get('mollom_public_key', '') && variable_get('mollom_private_key', '')) {
  36. if ($type == 'comment' && user_access('administer comments') && _mollom_get_mode('comment_form')) {
  37. $links['mollom_comment_report'] = array(
  38. 'title' => t('Report to Mollom'),
  39. 'href' => 'mollom/comment/'. $object->cid,
  40. );
  41. }
  42. elseif ($type == 'node' && user_access('administer nodes') && _mollom_get_mode($object->type .'_node_form')) {
  43. $links['mollom_node_report'] = array(
  44. 'title' => t('Report to Mollom'),
  45. 'href' => 'mollom/node/'. $object->nid,
  46. );
  47. }
  48. }
  49. return $links;
  50. }
  51. /**
  52. * Implementation of hook_menu().
  53. */
  54. function mollom_menu($may_cache) {
  55. $items = array();
  56. if ($may_cache) {
  57. $items[] = array(
  58. 'path' => 'mollom/comment',
  59. 'title' => t('Report and delete'),
  60. 'callback' => 'drupal_get_form',
  61. 'callback arguments' => array('mollom_report_comment'),
  62. 'access' => user_access('administer comments'),
  63. 'type' => MENU_CALLBACK,
  64. );
  65. $items[] = array(
  66. 'path' => 'mollom/node',
  67. 'title' => t('Report and delete'),
  68. 'callback' => 'drupal_get_form',
  69. 'callback arguments' => array('mollom_report_node'),
  70. 'access' => user_access('administer nodes'),
  71. 'type' => MENU_CALLBACK,
  72. );
  73. $items[] = array(
  74. 'path' => 'mollom/contact',
  75. 'title' => t('Report'),
  76. 'callback' => 'drupal_get_form',
  77. 'callback arguments' => array('mollom_report_contact'),
  78. 'access' => TRUE, // Everyone can report contact form feedback
  79. 'type' => MENU_CALLBACK,
  80. );
  81. $items[] = array(
  82. 'path' => 'admin/settings/mollom',
  83. 'title' => t('Mollom'),
  84. 'description' => t('Mollom is a web service that helps you manage your community.'),
  85. 'callback' => 'drupal_get_form',
  86. 'callback arguments' => array('mollom_admin_settings'),
  87. 'access' => user_access('administer mollom'),
  88. 'type' => MENU_NORMAL_ITEM,
  89. );
  90. // A menu callback that is used for AJAX purposes:
  91. $items[] = array(
  92. 'path' => 'mollom/captcha',
  93. 'callback' => 'mollom_ajax_callback',
  94. 'access' => TRUE,
  95. 'type' => MENU_CALLBACK,
  96. );
  97. }
  98. return $items;
  99. }
  100. /**
  101. * Implementation of hook_perm().
  102. */
  103. function mollom_perm() {
  104. return array(
  105. 'administer mollom',
  106. 'post with no checking',
  107. );
  108. }
  109. /**
  110. * An AJAX callback to retrieve a CAPTCHA.
  111. */
  112. function mollom_ajax_callback($type, $session_id) {
  113. if ($type == 'audio') {
  114. $response = mollom('mollom.getAudioCaptcha', array('author_ip' => _mollom_ip_address(), 'session_id' => $session_id));
  115. if ($response) {
  116. $output = '<embed src="'. check_plain($response['url']) .'" autostart="true" width="120" height="30" />';
  117. $output .= ' (<a href="#" id="image-captcha">'. t('use image CAPTCHA') .'</a>)';
  118. }
  119. }
  120. if ($type == 'image') {
  121. $response = mollom('mollom.getImageCaptcha', array('author_ip' => _mollom_ip_address(), 'session_id' => $session_id));
  122. if ($response) {
  123. $output = '<a href="http://mollom.com"><img src="'. check_plain(url($response['url'])) .'" alt="Mollom CAPTCHA" /></a>';
  124. $output .= ' (<a href="#" id="audio-captcha">'. t('play audio CAPTCHA') .'</a>)';
  125. }
  126. }
  127. print $output;
  128. exit();
  129. }
  130. /**
  131. * Helper function used to load a Mollom session ID.
  132. */
  133. function mollom_get_data($did) {
  134. return db_fetch_object(db_query_range("SELECT * FROM {mollom} WHERE did = '%s'", $did, 0, 1));
  135. }
  136. /**
  137. * Helper function used to store a Mollom session ID.
  138. */
  139. function mollom_set_data($session, $quality, $did) {
  140. if (db_result(db_query_range("SELECT 1 FROM {mollom} WHERE did = '%s'", $did, 0, 1))) {
  141. db_query("UPDATE {mollom} SET session = '%s', quality = '%s' WHERE did = '%s'", $session, $quality, $did);
  142. }
  143. else {
  144. db_query("INSERT INTO {mollom} (session, quality, did) VALUES ('%s', '%s', '%s')", $session, $quality, $did);
  145. }
  146. }
  147. /**
  148. * Return a list of the possible feedback options for content.
  149. */
  150. function _mollom_feedback_options() {
  151. return array(
  152. '#type' => 'radios',
  153. '#title' => t('Optionally report this to Mollom'),
  154. '#options' => array(
  155. 'none' => t("Don't send feedback to Mollom"),
  156. 'spam' => t('Report as spam or unsolicited advertising'),
  157. 'profanity' => t('Report as obscene, violent or profane content'),
  158. 'low-quality' => t('Report as low-quality content or writing'),
  159. 'unwanted' => t('Report as unwanted, taunting or off-topic content'),
  160. ),
  161. '#default_value' => 'none',
  162. '#description' => t("Mollom is a web service that helps you moderate your site's content: see <a href=\"http://mollom.com\">http://mollom.com</a> for more information. By sending feedback to Mollom, you teach Mollom what content you like and what content you dislike. Like that, Mollom can do a better job helping you to moderate your site's content. If you want to report multiple posts at once, you can use Mollom's bulk operations on the content and comment administration pages."),
  163. );
  164. }
  165. /**
  166. * This function is used to report a comment as feedback and to delete it.
  167. */
  168. function mollom_report_comment($cid) {
  169. if ($comment = _comment_load($cid)) {
  170. $form['cid'] = array('#type' => 'value', '#value' => $cid);
  171. $form['feedback'] = _mollom_feedback_options();
  172. return confirm_form($form,
  173. t('Are you sure you want to delete the comment and report it?'),
  174. $_GET['destination'] ? $_GET['destination'] : 'node/'. $comment->nid,
  175. t('This action cannot be undone.'),
  176. t('Delete'), t('Cancel'));
  177. }
  178. }
  179. /**
  180. * This function is used to delete a comment and to optionally send feedback to Mollom.
  181. */
  182. function mollom_report_comment_submit($form_id, $form_values) {
  183. if ($form_values['confirm']) {
  184. if ($comment = _comment_load($form_values['cid'])) {
  185. // Load the Mollom session data:
  186. $data = mollom_get_data('comment-'. $comment->cid);
  187. // Provide feedback to Mollom if available:
  188. if ($data->session && $form_values['feedback']) {
  189. mollom('mollom.sendFeedback', array('session_id' => $data->session, 'feedback' => $form_values['feedback']));
  190. }
  191. // Delete comment and its replies.
  192. _comment_delete_thread($comment);
  193. _comment_update_node_statistics($comment->nid);
  194. cache_clear_all();
  195. drupal_set_message(t('The comment has been deleted.'));
  196. }
  197. }
  198. return "node/$comment->nid";
  199. }
  200. /**
  201. * This function is used to delete a node and to optionally send feedback to Mollom.
  202. */
  203. function mollom_report_node($nid) {
  204. if ($node = node_load($nid)) {
  205. $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
  206. $form['feedback'] = _mollom_feedback_options();
  207. return confirm_form($form,
  208. t('Are you sure you want to delete %title and report it?', array('%title' => $node->title)),
  209. $_GET['destination'] ? $_GET['destination'] : 'node/'. $node->nid,
  210. t('This action cannot be undone.'),
  211. t('Delete'), t('Cancel'));
  212. }
  213. }
  214. /**
  215. * This function is used to delete a node and to optionally send feedback to Mollom.
  216. */
  217. function mollom_report_node_submit($form_id, $form_values) {
  218. if ($form_values['confirm']) {
  219. if ($node = node_load($form_values['nid'])) {
  220. // Load the Mollom session data:
  221. $data = mollom_get_data('node-'. $node->nid);
  222. // Provide feedback to Mollom if available:
  223. if ($data->session && $form_values['feedback']) {
  224. mollom('mollom.sendFeedback', array('session_id' => $data->session, 'feedback' => $form_values['feedback']));
  225. }
  226. // Delete the node. Calling this function will delete any comments,
  227. // clear the cache and print a status message.
  228. node_delete($node->nid);
  229. }
  230. }
  231. return '<front>';
  232. }
  233. /**
  234. * Implementation of hook_mail_alter().
  235. *
  236. * This function adds a 'report as inappropriate' link to the e-mails that are sent
  237. * by the contact module.
  238. */
  239. function mollom_mail_alter($mailkey, $to, $subject, &$body, $from, $headers) {
  240. // Only attach the Mollom report link to mails sent by actual users and not
  241. // any mails sent by Drupal since they should never be reported as spam.
  242. $valid_keys = array('contact-page-mail', 'contact-page-copy', 'contact-user-mail', 'contact-user-copy');
  243. $response = $GLOBALS['mollom_response'];
  244. if (isset($response['session_id']) && in_array($mailkey, $valid_keys)) {
  245. $body .= "\n\n". t('Report as inappropriate: @link', array('@link' => url('mollom/contact/'. $response['session_id'], NULL, NULL, TRUE)));
  246. }
  247. }
  248. /**
  249. * This function is used to report a contact form message as inappropriate.
  250. */
  251. function mollom_report_contact($session) {
  252. $form['session'] = array('#type' => 'value', '#value' => $session);
  253. $form['feedback'] = _mollom_feedback_options();
  254. return confirm_form($form,
  255. t('Are you sure you want to report the e-mail message as inappropriate?'),
  256. $_GET['destination'] ? $_GET['destination'] : '',
  257. t('This action cannot be undone.'),
  258. t('Report as inappropriate'), t('Cancel'));
  259. }
  260. /**
  261. * This function is used to report a contact form message as inappropriate.
  262. */
  263. function mollom_report_contact_submit($form_id, $form_values) {
  264. if ($form_values['feedback']) {
  265. mollom('mollom.sendFeedback', array('session_id' => $form_values['session'], 'feedback' => $form_values['feedback']));
  266. drupal_set_message('The e-mail has been reported as inappropriate.');
  267. }
  268. drupal_goto();
  269. }
  270. /**
  271. * This function is called when a node is inserted.
  272. */
  273. function mollom_nodeapi($node, $op) {
  274. if ($op == 'insert' && isset($GLOBALS['mollom_response']) && isset($GLOBALS['mollom_response']['session_id'])) {
  275. mollom_set_data($GLOBALS['mollom_response']['session_id'], $GLOBALS['mollom_response']['quality'], 'node-'. $node->nid);
  276. }
  277. }
  278. /**
  279. * This function is called when a comment is inserted.
  280. */
  281. function mollom_comment($comment, $op) {
  282. if ($op == 'insert' && isset($GLOBALS['mollom_response']) && isset($GLOBALS['mollom_response']['session_id'])) {
  283. mollom_set_data($GLOBALS['mollom_response']['session_id'], $GLOBALS['mollom_response']['quality'], 'comment-'. $comment['cid']);
  284. }
  285. }
  286. /**
  287. * This function intercepts all forms in Drupal and Mollom enables them if
  288. * necessary.
  289. */
  290. function mollom_form_alter($form_id, &$form) {
  291. // Catch all handlers -- this makes it easy to protect all forms
  292. // with Mollom. Site administrators don't have their content
  293. // checked with Mollom.
  294. if (!user_access('post with no checking')) {
  295. $form['#pre_render'][] = 'mollom_captcha_prerender';
  296. $form['#validate']['mollom_validate'] = array();
  297. }
  298. // Hook into the mass comment administration page and add some
  299. // operations to communicate ham/spam to the XML-RPC server.
  300. if ($form_id == 'comment_admin_overview') {
  301. $form['options']['operation']['#options']['mollom-unpublish'] = t('Report to Mollom as spam and unpublish');
  302. $form['options']['operation']['#options']['mollom-delete'] = t('Report to Mollom as spam and delete');
  303. $form['#validate']['mollom_comment_admin_overview_submit'] = array();
  304. }
  305. // Hook into the mass comment administration page and add some
  306. // operations to communicate ham/spam to the XML-RPC server.
  307. if ($form_id == 'node_admin_nodes') {
  308. $form['options']['operation']['#options']['mollom-unpublish'] = t('Report to Mollom as spam and unpublish');
  309. $form['options']['operation']['#options']['mollom-delete'] = t('Report to Mollom as spam and delete');
  310. $form['#validate']['mollom_node_admin_overview_submit'] = array();
  311. }
  312. }
  313. function mollom_comment_admin_overview_submit($form_id, $form_values) {
  314. // The operation has the following format: mollom-<operation>,
  315. // where '<operation>' can be 'unpublish' or 'delete'.
  316. list($id, $operation) = explode('-', $form_values['operation']);
  317. if ($id == 'mollom') {
  318. foreach ($form_values['comments'] as $cid => $value) {
  319. if ($value) {
  320. // First, send the proper information to the XML-RPC server:
  321. if ($data = mollom_get_data('comment-'. $cid)) {
  322. mollom('mollom.sendFeedback', array('session_id' => $data->session, 'feedback' => 'spam'));
  323. }
  324. // Second, perform the proper operation on the comments:
  325. if ($comment = _comment_load($cid)) {
  326. if ($operation == 'unpublish') {
  327. db_query("UPDATE {comments} SET status = %d WHERE cid = %d", COMMENT_NOT_PUBLISHED, $cid);
  328. _comment_update_node_statistics($comment->nid);
  329. }
  330. elseif ($operation == 'delete') {
  331. _comment_delete_thread($comment);
  332. _comment_update_node_statistics($comment->nid);
  333. }
  334. }
  335. }
  336. }
  337. // Clear the cache:
  338. cache_clear_all();
  339. if ($operation == 'delete') {
  340. drupal_set_message(t('The selected comments have been reported as spam and are deleted.'));
  341. }
  342. else {
  343. drupal_set_message(t('The selected comments have been reported as spam and are unpublished.'));
  344. }
  345. }
  346. }
  347. function mollom_node_admin_overview_submit($form_id, $form_values) {
  348. // The operation has the following format: mollom-<operation>,
  349. // where '<operation>' can be 'unpublish' or 'delete'.
  350. list($id, $operation) = explode('-', $form_values['operation']);
  351. if ($id == 'mollom') {
  352. foreach ($form_values['nodes'] as $nid => $value) {
  353. if ($value) {
  354. if ($data = mollom_get_data('node-'. $nid)) {
  355. mollom('mollom.sendFeedback', array('session_id' => $data->session, 'feedback' => 'spam'));
  356. }
  357. if ($node = node_load($nid)) {
  358. if ($operation == 'unpublish') {
  359. db_query("UPDATE {node} SET status = 0 WHERE nid = %d", $nid);
  360. }
  361. elseif ($operation == 'delete') {
  362. node_delete($nid);
  363. }
  364. }
  365. }
  366. }
  367. // Clear the cache:
  368. cache_clear_all();
  369. if ($operation == 'delete') {
  370. drupal_set_message(t('The selected posts have been reported as spam and are deleted.'));
  371. }
  372. else {
  373. drupal_set_message(t('The selected posts have been reported as spam and are unpublished.'));
  374. }
  375. }
  376. }
  377. /**
  378. * This function will be called by mollom_validate to prepare the
  379. * XML-RPC data from the comment submission form's $form_values ...
  380. */
  381. function mollom_data_contact_mail_page($form_values) {
  382. global $user;
  383. $data = array(
  384. 'post_title' => $form_values['subject'],
  385. 'post_body' => $form_values['message'],
  386. 'author_name' => $form_values['name'] ? $form_values['name'] : $user->name,
  387. 'author_mail' => $form_values['mail'] ? $form_values['mail'] : $user->mail,
  388. 'author_id' => $user->uid > 0 ? $user->uid : NULL,
  389. 'author_ip' => _mollom_ip_address(),
  390. );
  391. return $data;
  392. }
  393. /**
  394. * This function will be called by mollom_validate to prepare the
  395. * XML-RPC data from the comment submission form's $form_values ...
  396. */
  397. function mollom_data_contact_mail_user($form_values) {
  398. global $user;
  399. $data = array(
  400. 'post_title' => $form_values['subject'],
  401. 'post_body' => $form_values['message'],
  402. 'author_name' => $form_values['name'] ? $form_values['name'] : $user->name,
  403. 'author_mail' => $form_values['mail'] ? $form_values['mail'] : $user->mail,
  404. 'author_id' => $user->uid > 0 ? $user->uid : NULL,
  405. 'author_ip' => _mollom_ip_address(),
  406. );
  407. return $data;
  408. }
  409. /**
  410. * This function will be called by mollom_validate to prepare the
  411. * XML-RPC data from the comment submission form's $form_values ...
  412. */
  413. function mollom_data_comment_form($form_values) {
  414. global $user;
  415. $data = array(
  416. 'post_title' => $form_values['subject'],
  417. 'post_body' => $form_values['comment'],
  418. 'author_name' => $form_values['name'] ? $form_values['name'] : $user->name,
  419. 'author_mail' => $form_values['mail'] ? $form_values['mail'] : $user->mail,
  420. 'author_url' => $form_values['homepage'],
  421. 'author_id' => $user->uid > 0 ? $user->uid : NULL,
  422. 'author_ip' => $form_values['cid'] ? '' : _mollom_ip_address(),
  423. );
  424. return $data;
  425. }
  426. /**
  427. * This function will be called by mollom_validate to prepare the
  428. * XML-RPC data from the comment submission form's $form_values ...
  429. */
  430. function mollom_data_node_form($form_values) {
  431. global $user;
  432. // Render the node so that all visible fields are prepared and
  433. // concatenated:
  434. $node = (object) $form_values;
  435. // Link module (and potentially other CCK fields) need to prepare submitted
  436. // form values prior to storage and rendering. This is a stop-gap fix for
  437. // http://drupal.org/node/271339, as this entire node validation approach
  438. // can be considered bogus, but this module won't be rewritten for Drupal 5.
  439. if (function_exists('_content_widget_invoke')) {
  440. _content_widget_invoke('prepare form values', $node);
  441. }
  442. $data = node_build_content($node, FALSE, FALSE);
  443. $content = drupal_render($data->content);
  444. $data = array(
  445. 'post_title' => $form_values['title'],
  446. 'post_body' => $content,
  447. 'author_name' => $form_values['name'] ? $form_values['name'] : $user->name,
  448. 'author_mail' => $form_values['mail'] ? $form_values['mail'] : $user->mail,
  449. 'author_url' => $form_values['homepage'],
  450. 'author_id' => $user->uid > 0 ? $user->uid : NULL,
  451. 'author_ip' => $form_values['nid'] ? '' : _mollom_ip_address(),
  452. );
  453. return $data;
  454. }
  455. /**
  456. * This function is a generic validate function that will protect any given
  457. * form as long there is a variable for it (i.e. as long it is configured to
  458. * be protected through Mollom). To protect a form in Drupal with Mollom,
  459. * just variable_set() the "mollom_$form_id" variable.
  460. */
  461. function mollom_validate($form_id, $form_values) {
  462. $data = array();
  463. // Retrieve the mode of protection that is required for this form:
  464. $mode = _mollom_get_mode($form_id);
  465. // Don't process the form at all if we don't need to.
  466. if ($mode == MOLLOM_MODE_DISABLED) {
  467. return;
  468. }
  469. $pos = strpos($form_id, '_node_form');
  470. if ($pos !== FALSE) {
  471. // The node forms use dynamic form IDs so we had to create a special
  472. // case for these.
  473. $data = mollom_data_node_form($form_values);
  474. }
  475. else {
  476. $function = 'mollom_data_'. $form_id;
  477. if (function_exists($function)) {
  478. $data = $function($form_values);
  479. }
  480. }
  481. // Protect the form according to the mode.
  482. if ($mode == MOLLOM_MODE_ANALYSIS) {
  483. mollom_protect_form_analysis($form_values, $data);
  484. }
  485. else if ($mode == MOLLOM_MODE_CAPTCHA) {
  486. mollom_protect_form_captcha($form_values, $data);
  487. }
  488. }
  489. /**
  490. * Given a form ID, this function will return the strategy that is used
  491. * to protect this form. Could be MOLLOM_MODE_DISABLED (none),
  492. * MOLLOM_MODE_CAPTCHA (CAPTCHAs only) or MOLLOM_MODE_ANALYSIS (text
  493. * analysis with smart CAPTCHA support).
  494. */
  495. function _mollom_get_mode($form_id) {
  496. if (variable_get('mollom_'. $form_id, MOLLOM_MODE_DISABLED)) {
  497. $forms = mollom_protectable_forms();
  498. return $forms[$form_id]['mode'];
  499. }
  500. return MOLLOM_MODE_DISABLED;
  501. }
  502. /**
  503. * This function lists all the forms that you can protect with Mollom.
  504. * If you want to protect additional forms with Mollom add the form ID
  505. * to this list.
  506. */
  507. function mollom_protectable_forms() {
  508. static $forms = NULL;
  509. if (!$forms) {
  510. if (module_exists('comment')) {
  511. $forms['comment_form'] = array(
  512. 'name' => 'comment form',
  513. 'mode' => MOLLOM_MODE_ANALYSIS,
  514. );
  515. }
  516. if (module_exists('contact')) {
  517. $forms['contact_mail_page'] = array(
  518. 'name' => 'site-wide contact form',
  519. 'mode' => MOLLOM_MODE_ANALYSIS,
  520. );
  521. $forms['contact_mail_user'] = array(
  522. 'name' => 'per-user contact forms',
  523. 'mode' => MOLLOM_MODE_ANALYSIS,
  524. );
  525. }
  526. $forms['user_register'] = array(
  527. 'name' => 'user registration form',
  528. 'mode' => MOLLOM_MODE_CAPTCHA,
  529. );
  530. $forms['user_pass'] = array(
  531. 'name' => 'user password request form',
  532. 'mode' => MOLLOM_MODE_CAPTCHA,
  533. );
  534. // Add all the node types:
  535. $types = node_get_types('names');
  536. foreach ($types as $type => $name) {
  537. $forms[$type .'_node_form'] = array(
  538. 'name' => strtolower($name) ." form",
  539. 'mode' => MOLLOM_MODE_ANALYSIS,
  540. );
  541. }
  542. }
  543. return $forms;
  544. }
  545. function mollom_admin_settings() {
  546. $keys = variable_get('mollom_public_key', '') && variable_get('mollom_private_key', '');
  547. if ($keys) {
  548. // Print a status message about the key:
  549. if (!$_POST) {
  550. // When a user visits the Mollom administration page, we automatically
  551. // clear the server list. This will cause the client to fetch a fresh
  552. // server list from the server.
  553. variable_del('mollom_servers');
  554. // Verify the key:
  555. _mollom_verify_key();
  556. }
  557. $form['statistics'] = array(
  558. '#type' => 'fieldset',
  559. '#title' => t('Site usage statistics'),
  560. '#collapsible' => TRUE,
  561. );
  562. $form['statistics']['message'] = array(
  563. '#value' => '<div><embed src="http://mollom.com/statistics.swf?key='. check_plain(variable_get('mollom_public_key', '')) .'" quality="high" width="100%" height="430" name="Mollom" align="middle" play="true" loop="false" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer"></embed></div>',
  564. );
  565. $form['spam'] = array(
  566. '#type' => 'fieldset',
  567. '#title' => t('Spam protection settings'),
  568. '#description' =>
  569. '<p>'. t("Mollom can be used to block all sorts of spam received by your website. Your Drupal site will send data you want checked for spam to the Mollom servers, which will reply with either 'spam' or 'ham' (not spam). If Mollom is not fully confident in its decision, it will ask the user to fill out a CAPTCHA. On the rare occasion that Mollom asks the poster to fill out a CAPTCHA, Mollom assumes that all legitimate posters will take the extra time to fill out this CAPTCHA. Using the CAPTCHA, Mollom avoids legitimate messages being incorrectly classified as spam and it eliminates the need to moderate messages that Mollom decided to block. Administrators can still inspect the <a href=\"@logs\">logs</a> to see what Mollom has blocked.", array('@logs' => url('admin/logs/watchdog'))) .'</p>'.
  570. '<p>'. t("To perform its service, Mollom processes, stores and compares the data submitted by your site's visitors as explained in our <a href=\"http://mollom.com/service-agreement-free-subscriptions\">Web Service Privacy Policy</a>. As the controller of the data being processed, it is your responsibility to inform your website's visitors, and to obtain appropriate consent from them to allow Mollom to process their data.") .'</p>'.
  571. '<p>'. t("More information about how Mollom works, is available on the <a href=\"@mollom-workings\">\"How Mollom works\" page</a> and the <a href=\"@mollom-faq\">Mollom FAQ</a>.", array('@mollom-workings' => 'http://mollom.com/how-mollom-works', '@mollom-faq' => 'http://mollom.com/faq')) .'</p>',
  572. '#collapsible' => TRUE,
  573. );
  574. $forms = mollom_protectable_forms();
  575. foreach ($forms as $formid => $details) {
  576. $name = 'mollom_'. $formid;
  577. $form['spam'][$name] = array(
  578. '#type' => 'checkbox',
  579. '#title' => t('Protect @name', array('@name' => $details['name'])),
  580. '#default_value' => variable_get($name, MOLLOM_MODE_DISABLED),
  581. );
  582. }
  583. $form['server'] = array(
  584. '#type' => 'fieldset',
  585. '#title' => t('Server settings'),
  586. '#collapsible' => TRUE,
  587. );
  588. $form['server']['mollom_fallback'] = array(
  589. '#type' => 'radios',
  590. '#title' => t('Fallback strategy'),
  591. '#default_value' => variable_get('mollom_fallback', MOLLOM_FALLBACK_BLOCK ), // we default to treating everything as inappropriate
  592. '#options' => array(
  593. MOLLOM_FALLBACK_BLOCK => t('Block all submissions on the protected forms until the server problems are resolved'),
  594. MOLLOM_FALLBACK_ACCEPT => t('Leave all forms unprotected and accept all submissions')
  595. ),
  596. '#description' => t('In the event that Mollom stopped servicing your requests, i.e. because a certain usage quota was reached, your Drupal site will use the configured fallback strategy. You can choose to blindly accept all submissions without spam checking, or you can choose to block all submissions until this problem is resolved. You can also <a href="@pricing">upgrade to Mollom Plus</a>: paying customers automatically get access to <a href="@sla">Mollom\'s high-availability backend infrastructure</a> not available to free users.', array('@pricing' => 'http://mollom.com/pricing', '@sla' => 'http://mollom.com/standard-service-level-agreement')),
  597. );
  598. }
  599. $form['access-keys'] = array(
  600. '#type' => 'fieldset',
  601. '#title' => t('Mollom access keys'),
  602. '#description' => t('In order to use Mollom, you need a public and a private key. Visit <a href="http://mollom.com/user">http://mollom.com/user</a> and create a user account to obtain a private and a public access key.'),
  603. '#collapsible' => TRUE,
  604. '#collapsed' => $keys,
  605. );
  606. $form['access-keys']['mollom_public_key'] = array(
  607. '#type' => 'textfield',
  608. '#title' => t('Public key'),
  609. '#default_value' => variable_get('mollom_public_key', ''),
  610. '#description' => t('The public key is used to uniquely identify you.'),
  611. '#required' => TRUE,
  612. );
  613. $form['access-keys']['mollom_private_key'] = array(
  614. '#type' => 'textfield',
  615. '#title' => t('Private key'),
  616. '#default_value' => variable_get('mollom_private_key', ''),
  617. '#description' => t('The private key is used to prevent someone from hijacking your requests. It is like a password and should never be shared with anyone.'),
  618. '#required' => TRUE,
  619. );
  620. return system_settings_form($form);
  621. }
  622. function _mollom_fallback() {
  623. $fallback = variable_get("mollom_fallback", MOLLOM_FALLBACK_BLOCK);
  624. if ($fallback == MOLLOM_FALLBACK_BLOCK) {
  625. form_set_error('mollom', t("The spam filter that is installed on this site is currently not available. Per the site's policy, we are unable to accept new submissions until that problem is resolved. Please try resubmitting the form in a couple minutes."));
  626. }
  627. watchdog('mollom', t('Mollom was unavailable (error: @errno - %error_msg)', array('@errno' => xmlrpc_errno(), '%error_msg' => xmlrpc_error_msg())), WATCHDOG_ERROR);
  628. }
  629. function mollom_protect_form_analysis(&$form, $data) {
  630. $mollom = $_POST['session-id'] ? array('session_id' => $_POST['session-id']) : array();
  631. $result = mollom('mollom.checkContent', $data + $mollom);
  632. if (isset($result['spam'])) {
  633. // We make the response available to other form API handlers:
  634. $GLOBALS['mollom_response'] = $result;
  635. if ($result['spam'] == MOLLOM_ANALYSIS_HAM) {
  636. watchdog('mollom', t('Ham: %message<br />Data: <pre>@data</pre>Result: <pre>@result</pre>', array('%message' => $data['post_body'], '@data' => print_r($data, TRUE), '@result' => print_r($result, TRUE))));
  637. }
  638. else if ($result['spam'] == MOLLOM_ANALYSIS_SPAM) {
  639. form_set_error('analysis', t('Your submission has triggered the installed spam filter and will not be accepted.'));
  640. watchdog('mollom', t('Spam: %message<br />Data: <pre>@data</pre>Result: <pre>@result</pre>', array('%message' => $data['post_body'], '@data' => print_r($data, TRUE), '@result' => print_r($result, TRUE))));
  641. }
  642. else {
  643. if ($_POST['captcha-solution']) {
  644. // Check the CAPTCHA result:
  645. $result = mollom('mollom.checkCaptcha', $data + array('session_id' => $_POST['session-id'], 'solution' => $_POST['captcha-solution'], 'author_ip' => _mollom_ip_address()));
  646. if (is_bool($result)) {
  647. if ($result) {
  648. $GLOBALS['mollom_response']['spam'] = MOLLOM_ANALYSIS_HAM;
  649. $GLOBALS['mollom_response']['session_id'] = $_POST['session-id'];
  650. watchdog('mollom', t('Correct CAPTCHA: %message<br />Data: <pre>@data</pre>Result: <pre>@result</pre>', array('%message' => $data['post_body'], '@data' => print_r($data, TRUE), '@result' => print_r($result, TRUE))));
  651. }
  652. else {
  653. form_set_error('captcha', t('The entered CAPTCHA solution is not correct. We generated a new CAPTCHA so please try again.'));
  654. watchdog('mollom', t('Incorrect CAPTCHA: %message<br />Data: <pre>@data</pre>Result: <pre>@result</pre>', array('%message' => $data['post_body'], '@data' => print_r($data, TRUE), '@result' => print_r($result, TRUE))));
  655. }
  656. }
  657. else {
  658. _mollom_fallback();
  659. }
  660. }
  661. else {
  662. form_set_error('analysis', t('We are sorry, but the spam filter on this site decided that your submission could be spam. Please fill in the CAPTCHA below to get your submission accepted.'));
  663. watchdog('mollom', t('Unsure: %message<br />Data: <pre>@data</pre>Result: <pre>@result</pre>', array('%message' => $data['post_body'], '@data' => print_r($data, TRUE), '@result' => print_r($result, TRUE))));
  664. }
  665. }
  666. }
  667. else {
  668. _mollom_fallback();
  669. }
  670. }
  671. function mollom_protect_form_captcha(&$form, $data) {
  672. if ($_POST['session-id']) {
  673. if ($_POST['captcha-solution']) {
  674. // Check the CAPTCHA result:
  675. $data += array(
  676. 'session_id' => $_POST['session-id'],
  677. 'captcha_result' => $_POST['captcha-solution'],
  678. 'author_ip' => _mollom_ip_address(),
  679. );
  680. $result = mollom('mollom.checkCaptcha', $data);
  681. if (is_bool($result)) {
  682. // We make the response available to other form API handlers:
  683. $GLOBALS['mollom_response']['session_id'] = $_POST['session-id'];
  684. if (!$result) {
  685. form_set_error('captcha', t('The entered CAPTCHA solution is not correct. We generated a new CAPTCHA so please try again.'));
  686. watchdog('mollom', t('Incorrect CAPTCHA<br />Data: <pre>@data</pre>', array('@data' => print_r($data, TRUE))));
  687. // Generate a new CAPTCHA:
  688. $GLOBALS['mollom_response'] = mollom('mollom.getImageCaptcha', array('author_ip' => _mollom_ip_address(), 'session_id' => $_POST['session-id']));
  689. }
  690. else {
  691. watchdog('mollom', t('Correct CAPTCHA<br />Data: <pre>@data</pre>', array('@data' => print_r($data, TRUE))));
  692. }
  693. }
  694. else {
  695. _mollom_fallback();
  696. }
  697. }
  698. else {
  699. form_set_error('captcha', t('The CAPTCHA field is required.'));
  700. // Generate a new CAPTCHA:
  701. $GLOBALS['mollom_response'] = mollom('mollom.getImageCaptcha', array('author_ip' => _mollom_ip_address()));
  702. }
  703. }
  704. else {
  705. form_set_error('captcha', t('The form data has been altered, the session-id field is required.'));
  706. // Generate a new CAPTCHA:
  707. $GLOBALS['mollom_response'] = mollom('mollom.getImageCaptcha', array('author_ip' => _mollom_ip_address()));
  708. }
  709. }
  710. function mollom_captcha_prerender($form_id, &$form) {
  711. $response = $GLOBALS['mollom_response'];
  712. $mode = _mollom_get_mode($form_id);
  713. if ($mode == MOLLOM_MODE_ANALYSIS && $response['spam'] == MOLLOM_ANALYSIS_UNSURE) {
  714. $response = mollom('mollom.getImageCaptcha', array('author_ip' => _mollom_ip_address(), 'session_id' => $response['session_id']));
  715. $captcha = TRUE;
  716. }
  717. else if ($mode == MOLLOM_MODE_CAPTCHA) {
  718. $response = mollom('mollom.getImageCaptcha', array('author_ip' => _mollom_ip_address(), 'session_id' => $response['session_id']));
  719. $captcha = TRUE;
  720. }
  721. if (isset($response['session_id'])) {
  722. $form['session-id'] = array(
  723. '#type' => 'hidden',
  724. '#name' => 'session-id',
  725. '#id' => 'edit-session-id',
  726. '#value' => $response['session_id'],
  727. );
  728. if ($captcha) {
  729. // Include the Javascript that allows the user to switch to an
  730. // AUDIO captcha instead:
  731. $url = base_path() . (variable_get('clean_url', 0) ? '' : 'index.php?q=');
  732. drupal_add_js(array('mollom_base_url' => $url), 'setting');
  733. drupal_add_js(drupal_get_path('module', 'mollom') .'/mollom.js');
  734. // We add an image captcha to the form. We can't do this from the
  735. // validate hook, hence the need for the #pre_render trick. In
  736. // Drupal 6, we can simply use $form_state['rebuild'] = TRUE in
  737. // the validate-hook, and rebuild the form from there but in
  738. // Drupal 5, we have to use this prerender-hook hack.
  739. // Because we're already past the prepare state of the form API,
  740. // we have a little bit more work to do ...:
  741. $form['captcha-solution'] = array(
  742. '#type' => 'textfield',
  743. '#name' => 'captcha-solution',
  744. '#id' => 'edit-captcha-solution',
  745. '#parents' => array(),
  746. '#title' => t('Word verification'),
  747. '#field_prefix' => '<div id="captcha"><a href="http://mollom.com"><img src="'. url($response['url']) .'" alt="Mollom CAPTCHA" /></a> (<a href="#" id="audio-captcha">'. t('play audio CAPTCHA') .'</a>)</div>',
  748. '#required' => TRUE,
  749. '#size' => 10,
  750. '#value' => $form['#post']['captcha'],
  751. '#description' => t("Type the characters shown in the picture above; if you can't read them, submit the form and a new image will be generated."),
  752. '#weight' => min($form['submit']['#weight'], $form['preview']['#weight']) + 100,
  753. );
  754. // Move the preview and/or submit button below the captcha:
  755. $form['preview']['#weight'] += 101;
  756. $form['submit']['#weight'] += 101;
  757. // The weights changed so we re-sort the array:
  758. uasort($form, "_element_sort");
  759. }
  760. }
  761. }
  762. function _mollom_verify_key() {
  763. $status = mollom('mollom.verifyKey');
  764. $message = t('We contacted the Mollom servers to verify your keys');
  765. if (xmlrpc_errno()) {
  766. drupal_set_message(t('@message: %error (ERROR)', array('@message' => $message, '%error' => xmlrpc_error_msg())), 'error');
  767. }
  768. else {
  769. if ($status) {
  770. drupal_set_message(t('@message: the Mollom services are operating correctly. We are now blocking spam.', array('@message' => $message)));
  771. }
  772. else {
  773. drupal_set_message(t('@message: your keys do not exist or are no longer valid. Please visit the user settings page on the Mollom website again: <a href="@mollom-user">@mollom-user</a>.', array('@message' => $message, '@mollom-user' => 'http://mollom.com/user')), 'error');
  774. }
  775. }
  776. }
  777. function _mollom_ip_address() {
  778. static $ip_address = NULL;
  779. if (!isset($ip_address)) {
  780. $ip_address = $_SERVER['REMOTE_ADDR'];
  781. if (variable_get('reverse_proxy', 0) && array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
  782. // If an array of known reverse proxy IPs is provided, then trust
  783. // the XFF header if request really comes from one of them.
  784. $reverse_proxy_addresses = variable_get('reverse_proxy_addresses', array());
  785. if (!empty($reverse_proxy_addresses) && in_array($ip_address, $reverse_proxy_addresses, TRUE)) {
  786. // If there are several arguments, we need to check the most
  787. // recently added one, i.e. the last one.
  788. $ip_address = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
  789. }
  790. }
  791. }
  792. return $ip_address;
  793. }
  794. /**
  795. * This function is used to refresh the list of servers that can be used to contact Mollom.
  796. */
  797. function _mollom_retrieve_server_list() {
  798. // Start from a hard coded list of servers:
  799. $servers = array('http://xmlrpc1.mollom.com', 'http://xmlrpc2.mollom.com', 'http://xmlrpc3.mollom.com');
  800. // Use the list of servers to retrieve a list of servers from mollom.com:
  801. foreach ($servers as $server) {
  802. $result = xmlrpc($server .'/'. MOLLOM_API_VERSION, 'mollom.getServerList', _mollom_authentication());
  803. if (!xmlrpc_errno()) {
  804. return $result;
  805. }
  806. else {
  807. watchdog('mollom', t('Error @errno: %server - %message - mollom.getServerList', array('@errno' => xmlrpc_errno(), '%server' => $server, '%message' => xmlrpc_error_msg()), WATCHDOG_ERROR));
  808. // Reset the XMLRPC error:
  809. xmlrpc_error(0); // FIXME: this is crazy.
  810. }
  811. }
  812. return array();
  813. }
  814. /**
  815. * Call a remote procedure at the Mollom server. This function will
  816. * automatically add the information required to authenticate against
  817. * Mollom.
  818. */
  819. function mollom($method, $data = array()) {
  820. // Initialize refresh variable:
  821. $refresh = FALSE;
  822. // Retrieve the list of Mollom servers from the database:
  823. $servers = variable_get('mollom_servers', NULL);
  824. if ($servers == NULL) {
  825. // Retrieve a list of servers:
  826. $servers = _mollom_retrieve_server_list();
  827. // Store the list of servers in the database:
  828. variable_set('mollom_servers', $servers);
  829. }
  830. if (is_array($servers)) {
  831. reset($servers);
  832. while ($server = current($servers)) {
  833. $result = xmlrpc($server .'/'. MOLLOM_API_VERSION, $method, $data + _mollom_authentication());
  834. if ($errno = xmlrpc_errno()) {
  835. if ($errno == MOLLOM_REFRESH) {
  836. if (!$refresh) { // Safety pal to avoid endless loops
  837. // Retrieve a list of valid Mollom servers from mollom.com:
  838. $servers = _mollom_retrieve_server_list();
  839. // Reset the list of servers so we start from the first server in the list:
  840. reset($servers);
  841. // Store the updated list of servers in the database:
  842. variable_set('mollom_servers', $servers);
  843. // Log this for debuging purposes:
  844. watchdog('mollom', t('The list of available Mollom servers was refreshed: @servers.', array('@servers' => print_r($servers, TRUE))));
  845. // Mark that we have refreshed the list:
  846. $refresh = TRUE;
  847. }
  848. }
  849. elseif ($errno == MOLLOM_REDIRECT) {
  850. // If this is a network error, we go to the next server in the list.
  851. $next = next($servers);
  852. // Do nothing, we automatically select the next server.
  853. watchdog('mollom', t('The Mollom server %server asked to use the next Mollom server in the list: %next.', array('%server' => $server, '%next' => $next)));
  854. }
  855. else {
  856. watchdog('mollom', t('Error @errno from %server: %message - %method - <pre>@data</pre>', array('@errno' => xmlrpc_errno(), '%server' => $server, '%message' => xmlrpc_error_msg(), '%method' => $method, '@data' => print_r($data, TRUE)), WATCHDOG_ERROR));
  857. // If it is a 'clean' Mollom error we return instantly.
  858. if ($errno == MOLLOM_ERROR) {
  859. return $result;
  860. }
  861. // If this is a network error, we go to the next server in the list.
  862. next($servers);
  863. }
  864. // Reset the XMLRPC error:
  865. xmlrpc_error(0); // FIXME: this is crazy.
  866. }
  867. else {
  868. return $result;
  869. }
  870. }
  871. }
  872. // If none of the servers worked, activate the fallback mechanism:
  873. _mollom_fallback();
  874. // If everything failed, we reset the server list to force Mollom to request a new list:
  875. variable_del('mollom_servers');
  876. // Report this error:
  877. watchdog('mollom', t('No Mollom servers could be reached or all servers returned an error -- the server list was emptied.'), WATCHDOG_ERROR);
  878. }
  879. /**
  880. * This function generate an array with all the information required to
  881. * authenticate against Mollom. To prevent that requests are forged and
  882. * that you are impersonated, each request is signed with a hash computed
  883. * based on a private key and a timestamp.
  884. *
  885. * Both the client and the server share the secret key that is used to
  886. * create the authentication hash based on a timestamp. They both hash
  887. * the timestamp with the secret key, and if the hashes match, the
  888. * authenticity of the message has been validated.
  889. *
  890. * To avoid that someone can intercept a (hash, timestamp)-pair and
  891. * use that to impersonate a client, Mollom will reject the request
  892. * when the timestamp is more than 15 minutes off.
  893. *
  894. * Make sure your server's time is synchronized with the world clocks,
  895. * and that you don't share your private key with anyone else.
  896. */
  897. function _mollom_authentication() {
  898. $public_key = variable_get('mollom_public_key', '');
  899. $private_key = variable_get('mollom_private_key', '');
  900. // Generate a timestamp according to the dateTime format (http://www.w3.org/TR/xmlschema-2/#dateTime):
  901. $time = gmdate("Y-m-d\TH:i:s.\\0\\0\\0O", time());
  902. // Generate a random number:
  903. $nonce = md5(mt_rand());
  904. // Calculate a HMAC-SHA1 according to RFC2104 (http://www.ietf.org/rfc/rfc2104.txt):
  905. $hash = base64_encode(
  906. pack('H*', sha1((str_pad($private_key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) .
  907. pack('H*', sha1((str_pad($private_key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) .
  908. $time .':'. $nonce .':'. $private_key))))
  909. );
  910. // Store everything in an array. Elsewhere in the code, we'll add the
  911. // actual data before we pass it onto the XML-RPC library:
  912. $data['public_key'] = $public_key;
  913. $data['time'] = $time;
  914. $data['hash'] = $hash;
  915. $data['nonce'] = $nonce;
  916. return $data;
  917. }