| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: mollom.module,v 1.2.2.167 2010/09/15 15:10:11 sun Exp $ 3 4 /** 5 * @file 6 * Main Mollom integration module functions. 7 */ 8 9 /** 10 * Mollom API version; used for XML-RPC communication with Mollom servers. 11 */ 12 define('MOLLOM_API_VERSION', '1.0'); 13 14 /** 15 * Text analysis result flag: No result. 16 */ 17 define('MOLLOM_ANALYSIS_UNKNOWN', 0); 18 19 /** 20 * Text analysis result flag: Content is no spam. 21 */ 22 define('MOLLOM_ANALYSIS_HAM', 1); 23 24 /** 25 * Text analysis result flag: Content is spam. 26 */ 27 define('MOLLOM_ANALYSIS_SPAM', 2); 28 29 /** 30 * Text analysis result flag: Ambiguous result. 31 */ 32 define('MOLLOM_ANALYSIS_UNSURE', 3); 33 34 /** 35 * Form protection mode: No protection. 36 */ 37 define('MOLLOM_MODE_DISABLED', 0); 38 39 /** 40 * Form protection mode: CAPTCHA-only protection. 41 */ 42 define('MOLLOM_MODE_CAPTCHA', 1); 43 44 /** 45 * Form protection mode: Text analysis with fallback to CAPTCHA. 46 */ 47 define('MOLLOM_MODE_ANALYSIS', 2); 48 49 /** 50 * XML-RPC communication failure fallback mode: Block all submissions of protected forms. 51 */ 52 define('MOLLOM_FALLBACK_BLOCK', 0); 53 54 /** 55 * XML-RPC communication failure fallback mode: Accept all submissions of protected forms. 56 */ 57 define('MOLLOM_FALLBACK_ACCEPT', 1); 58 59 /** 60 * XML-RPC communication failure: No servers could be reached. 61 * 62 * @todo Prefix with MOLLOM_. 63 */ 64 define('NETWORK_ERROR', 900); 65 66 /** 67 * XML-RPC communication failure: Error on Mollom server. 68 * 69 * @todo Prefix with SERVER_. 70 */ 71 define('MOLLOM_ERROR', 1000); 72 73 /** 74 * XML-RPC communication failure: Mollom server requests client to update its server list. 75 * 76 * @todo Prefix with SERVER_. 77 */ 78 define('MOLLOM_REFRESH', 1100); 79 80 /** 81 * XML-RPC communication failure: Mollom server defers communication to next server in server list. 82 * 83 * @todo Prefix with SERVER_. 84 */ 85 define('MOLLOM_REDIRECT', 1200); 86 87 /** 88 * Implements hook_help(). 89 */ 90 function mollom_help($path, $arg) { 91 $output = ''; 92 93 if ($path == 'admin/settings/mollom') { 94 $output .= '<p>'; 95 $output .= t('All listed forms below are protected by Mollom, unless users are able to <a href="@permissions-url">bypass Mollom\'s protection</a>.', array( 96 '@permissions-url' => url('admin/user/permissions', array('fragment' => 'module-mollom')), 97 )); 98 $output .= '</p>'; 99 $output .= '<p>'; 100 $output .= t('You can <a href="@add-form-url">add a form</a> to protect, configure already protected forms, or remove the protection.', array( 101 '@add-form-url' => url('admin/settings/mollom/add'), 102 )); 103 $output .= '</p>'; 104 return $output; 105 } 106 107 if ($path == 'admin/settings/mollom/blacklist') { 108 return t('Mollom automatically blocks unwanted content and learns from all participating sites to improve its filters. On top of automatic filtering, you can define a custom blacklist.'); 109 } 110 111 if ($path == 'admin/help#mollom') { 112 $output = '<p>'; 113 $output .= 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>.", 114 array( 115 '@mollom-website' => 'http://mollom.com', 116 '@mollom-faq' => 'http://mollom.com/faq', 117 ) 118 ); 119 $output .= '</p><p>'; 120 $output .= t("Mollom can be used to block all types of spam received on your website's protected forms. Each form can be set to one of the following options:"); 121 $output .= '</p><ul><li>'; 122 $output .= t("<strong>Text analysis and CAPTCHA backup</strong>: Mollom analyzes the data submitted on the form and presents a CAPTCHA challenge if necessary. This option is strongly recommended, as it takes full advantage of the Mollom anti-spam service to categorize your posts into ham (not spam) and spam."); 123 $output .= '</li><li>'; 124 $output .= t("<strong>CAPTCHA only</strong>: the form's data is not sent to Mollom for analysis, and a remotely-hosted CAPTCHA challenge is always presented. This option is useful when you wish to always display a CAPTCHA or want to send less data to the Mollom network. Note, however, that forms displayed with a CAPTCHA are never cached, so always displaying a CAPTCHA challenge may reduce performance."); 125 $output .= '</li></ul><p>'; 126 $output .= t("Data is processed and stored as explained in our <a href=\"@mollom-privacy\">Web Service Privacy Policy</a>. It is your responsibility to provide any necessary notices and obtain the appropriate consent regarding Mollom's use of your data. For more information, see <a href=\"@mollom-works\">How Mollom Works</a> and the <a href=\"@mollom-faq\">Mollom FAQ</a>.", array( 127 '@mollom-privacy' => 'http://mollom.com/service-agreement-free-subscriptions', 128 '@mollom-works' => 'http://mollom.com/how-mollom-works', 129 '@mollom-faq' => 'http://mollom.com/faq') 130 ); 131 $output .= '</p>'; 132 $output .= '<h3>' . t('Mollom blacklist') . '</h3>'; 133 $output .= '<p>'; 134 $output .= t("Mollom's filters are shared and trained globally over all participating sites. Due to this, unwanted content might still be accepted on your site, even after sending feedback to Mollom. By using the site-specific blacklist, the filters can be customized to your specific needs. Each entry specifies a reason for why it has been blacklisted, which further helps in improving Mollom's automated filtering."); 135 $output .= '</p>'; 136 $output .= '<p>'; 137 $output .= t("All blacklist entries are applied to a context: the entire submitted post, or only links in the post. When limiting the context to links, both the link URL and the link text is taken into account."); 138 $output .= '</p>'; 139 $output .= '<p>'; 140 $output .= t("If a blacklist entry contains multiple words, various combinations will be matched. For example, when adding \"<code>replica watches</code>\" limited to links, the following links will be blocked:"); 141 $output .= '</p>'; 142 $output .= '<ul> 143 <li><code>http://replica-watches.com</code></li> 144 <li><code>http://replica-watches.com/some/path</code></li> 145 <li><code>http://replicawatches.net</code></li> 146 <li><code>http://example.com/replica/watches</code></li> 147 <li><code><a href="http://example.com">replica watches</a></code></li> 148 </ul>'; 149 $output .= '<p>'; 150 $output .= t("The blacklist is optional. There is no whitelist, i.e., if a blacklist entry is matched in a post, it overrides any other filter result and the post will not be accepted. Blacklisting potentially ambiguous words should be avoided."); 151 $output .= '</p>'; 152 return $output; 153 } 154 } 155 156 /** 157 * Implements hook_init(). 158 */ 159 function mollom_init() { 160 // On all Mollom administration pages, check the module configuration and 161 // display the corresponding requirements error, if invalid. 162 if (empty($_POST) && strpos($_GET['q'], 'admin/settings/mollom') === 0 && user_access('administer mollom')) { 163 // Re-check the status on the settings form only. 164 $status = _mollom_status($_GET['q'] == 'admin/settings/mollom/settings'); 165 if ($status !== TRUE) { 166 // Fetch and display requirements error message, without re-checking. 167 module_load_install('mollom'); 168 $requirements = mollom_requirements('runtime', FALSE); 169 drupal_set_message($requirements['mollom']['description'], 'error'); 170 } 171 } 172 $path = drupal_get_path('module', 'mollom'); 173 drupal_add_js($path . '/mollom.js'); 174 drupal_add_css($path . '/mollom.css'); 175 } 176 177 /** 178 * Implements hook_link(). 179 */ 180 function mollom_link($type, $object, $teaser = FALSE) { 181 $links = array(); 182 183 // Only show the links if the module is configured. 184 if (_mollom_status() === TRUE) { 185 if ($type == 'comment' && user_access('administer comments') && mollom_get_mode('comment_form')) { 186 $links['mollom_report'] = array( 187 'title' => t('report to Mollom'), 188 'href' => 'mollom/report/comment/' . $object->cid, 189 'query' => drupal_get_destination(), 190 ); 191 } 192 elseif ($type == 'node' && user_access('administer nodes') && mollom_get_mode($object->type . '_node_form')) { 193 $links['mollom_report'] = array( 194 'title' => t('report to Mollom'), 195 'href' => 'mollom/report/node/' . $object->nid, 196 'query' => drupal_get_destination(), 197 ); 198 } 199 } 200 201 return $links; 202 } 203 204 /** 205 * Implements hook_menu(). 206 */ 207 function mollom_menu() { 208 $items['mollom/report/%/%'] = array( 209 'title' => 'Report to Mollom', 210 'page callback' => 'drupal_get_form', 211 'page arguments' => array('mollom_report_form', 2, 3), 212 'access callback' => 'mollom_report_access', 213 'access arguments' => array(2, 3), 214 'file' => 'mollom.pages.inc', 215 'type' => MENU_CALLBACK, 216 ); 217 218 $items['admin/settings/mollom'] = array( 219 'title' => 'Mollom', 220 'description' => 'Mollom is a web service that helps you manage your community.', 221 'page callback' => 'mollom_admin_form_list', 222 'access arguments' => array('administer mollom'), 223 'file' => 'mollom.admin.inc', 224 ); 225 $items['admin/settings/mollom/forms'] = array( 226 'title' => 'Forms', 227 'type' => MENU_DEFAULT_LOCAL_TASK, 228 'weight' => -10, 229 ); 230 $items['admin/settings/mollom/add'] = array( 231 'title' => 'Add form', 232 'page callback' => 'drupal_get_form', 233 'page arguments' => array('mollom_admin_configure_form'), 234 'access arguments' => array('administer mollom'), 235 'type' => MENU_LOCAL_TASK, 236 'file' => 'mollom.admin.inc', 237 ); 238 $items['admin/settings/mollom/manage/%mollom_form'] = array( 239 'title' => 'Configure', 240 'page callback' => 'drupal_get_form', 241 'page arguments' => array('mollom_admin_configure_form', 4), 242 'access arguments' => array('administer mollom'), 243 'file' => 'mollom.admin.inc', 244 ); 245 $items['admin/settings/mollom/unprotect/%mollom_form'] = array( 246 'title' => 'Unprotect form', 247 'page callback' => 'drupal_get_form', 248 'page arguments' => array('mollom_admin_unprotect_form', 4), 249 'access arguments' => array('administer mollom'), 250 'type' => MENU_CALLBACK, 251 'file' => 'mollom.admin.inc', 252 ); 253 $items['admin/settings/mollom/blacklist'] = array( 254 'title' => 'Blacklist', 255 'description' => 'Configure blacklists.', 256 'page callback' => 'drupal_get_form', 257 'page arguments' => array('mollom_admin_blacklist_form'), 258 'access arguments' => array('administer mollom'), 259 'type' => MENU_LOCAL_TASK, 260 'file' => 'mollom.admin.inc', 261 ); 262 $items['admin/settings/mollom/blacklist/spam'] = array( 263 'title' => 'Spam', 264 'description' => 'Configure spam blacklist entries.', 265 'type' => MENU_DEFAULT_LOCAL_TASK, 266 'weight' => -10, 267 ); 268 $items['admin/settings/mollom/blacklist/profanity'] = array( 269 'title' => 'Profanity', 270 'description' => 'Configure profanity blacklist entries.', 271 'page callback' => 'drupal_get_form', 272 'page arguments' => array('mollom_admin_blacklist_form', 4), 273 'access arguments' => array('administer mollom'), 274 'type' => MENU_LOCAL_TASK, 275 'file' => 'mollom.admin.inc', 276 ); 277 $items['admin/settings/mollom/blacklist/unwanted'] = array( 278 'title' => 'Unwanted', 279 'description' => 'Configure unwanted blacklist entries.', 280 'page callback' => 'drupal_get_form', 281 'page arguments' => array('mollom_admin_blacklist_form', 4), 282 'access arguments' => array('administer mollom'), 283 'type' => MENU_LOCAL_TASK, 284 'file' => 'mollom.admin.inc', 285 ); 286 $items['admin/settings/mollom/blacklist/delete'] = array( 287 'title' => 'Delete', 288 'page callback' => 'drupal_get_form', 289 'page arguments' => array('mollom_admin_blacklist_delete'), 290 'access arguments' => array('administer mollom'), 291 'type' => MENU_CALLBACK, 292 'file' => 'mollom.admin.inc', 293 ); 294 $items['admin/settings/mollom/settings'] = array( 295 'title' => 'Settings', 296 'description' => 'Configure Mollom keys and global settings.', 297 'page callback' => 'drupal_get_form', 298 'page arguments' => array('mollom_admin_settings'), 299 'access arguments' => array('administer mollom'), 300 'type' => MENU_LOCAL_TASK, 301 'file' => 'mollom.admin.inc', 302 ); 303 304 $items['admin/reports/mollom'] = array( 305 'title' => 'Mollom statistics', 306 'description' => 'Reports and usage statistics for the Mollom module.', 307 'page callback' => 'drupal_get_form', 308 'page arguments' => array('mollom_reports_page'), 309 'access callback' => '_mollom_access', 310 'access arguments' => array('administer mollom'), 311 'file' => 'mollom.admin.inc', 312 ); 313 314 // AJAX callback to request new CAPTCHA. 315 $items['mollom/captcha/%/%'] = array( 316 'page callback' => 'mollom_captcha_js', 317 'page arguments' => array(2, 3), 318 'access callback' => '_mollom_access', 319 'file' => 'mollom.pages.inc', 320 'type' => MENU_CALLBACK, 321 ); 322 323 return $items; 324 } 325 326 /** 327 * Access callback; check if the module is configured. 328 * 329 * This function does not actually check whether Mollom keys are valid for the 330 * site, but just if the keys have been entered. 331 * 332 * @param $permission 333 * An optional permission string to check with user_access(). 334 * 335 * @return 336 * TRUE if the module has been configured and user_access() has been checked, 337 * FALSE otherwise. 338 */ 339 function _mollom_access($permission = FALSE) { 340 return variable_get('mollom_public_key', '') && variable_get('mollom_private_key', '') && (!$permission || user_access($permission)); 341 } 342 343 /** 344 * Menu access callback; Determine access to report to Mollom. 345 * 346 * The special $entity type "session" may be used for mails and messages, which 347 * originate from form submissions protected by Mollom, and can be reported by 348 * anyone; $id is expected to be a Mollom session id instead of an entity id 349 * then. 350 * 351 * @param $entity 352 * The entity type of the data to report. 353 * @param $id 354 * The entity id of the data to report. 355 */ 356 function mollom_report_access($entity, $id) { 357 // The special entity 'session' means that $id is a Mollom session_id, which 358 // can always be reported by everyone. 359 if ($entity == 'session') { 360 return !empty($id) ? TRUE : FALSE; 361 } 362 // Retrieve information about all protectable forms. We use the first valid 363 // definition, because we assume that multiple form definitions just denote 364 // variations of the same entity (e.g. node content types). 365 foreach (mollom_form_list() as $form_id => $info) { 366 if (!isset($info['entity']) || $info['entity'] != $entity) { 367 continue; 368 } 369 // If there is a 'report access callback', invoke it. 370 if (isset($info['report access callback']) && function_exists($info['report access callback'])) { 371 $function = $info['report access callback']; 372 return $function($entity, $id); 373 } 374 // Otherwise, if there is a 'report access' list of permissions, iterate 375 // over them. 376 if (isset($info['report access'])) { 377 foreach ($info['report access'] as $permission) { 378 if (user_access($permission)) { 379 return TRUE; 380 } 381 } 382 } 383 } 384 // If we end up here, then the current user is not permitted to report this 385 // content. 386 return FALSE; 387 } 388 389 /** 390 * Implements hook_perm(). 391 */ 392 function mollom_perm() { 393 return array( 394 'administer mollom', 395 'bypass mollom protection', 396 ); 397 } 398 399 /** 400 * Implements hook_flush_caches(). 401 */ 402 function mollom_flush_caches() { 403 return array('cache_mollom'); 404 } 405 406 /** 407 * Implements hook_cron(). 408 */ 409 function mollom_cron() { 410 // Mollom session data auto-expires after 6 months. 411 $expired = time() - 86400 * 30 * 6; 412 db_query("DELETE FROM {mollom} WHERE changed < %d", array($expired)); 413 } 414 415 /** 416 * Load a Mollom data record from the database. 417 * 418 * @param $entity 419 * The entity type to retrieve data for. 420 * @param $id 421 * The entity id to retrieve data for. 422 */ 423 function mollom_data_load($entity, $id) { 424 return db_fetch_object(db_query_range("SELECT * FROM {mollom} WHERE entity = '%s' AND did = '%s'", array($entity, $id), 0, 1)); 425 } 426 427 /** 428 * Save Mollom validation data to the database. 429 * 430 * Based on the specified entity type and id, this function stores the 431 * validation results returned by Mollom in the database. The stored data 432 * is an associative array containing Mollom session information for the posted 433 * content: 434 * - session: The session ID returned by the Mollom server. 435 * - quality: A quality rating assigned to the content to tell whether or not 436 * it's spam. 437 * - languages: An array containing language codes the content might be 438 * written in. 439 * 440 * The special $entity type "session" may be used for mails and messages, which 441 * originate from form submissions protected by Mollom, and can be reported by 442 * anyone; $id is expected to be a Mollom session id instead of an entity id 443 * then. 444 * 445 * @param $entity 446 * The entity type of the data to save. 447 * @param $id 448 * The entity id the data belongs to. 449 * 450 * @todo Remove usage of global $mollom variable. 451 */ 452 function mollom_data_save($entity, $id) { 453 // Nothing to do, if we do not have a valid Mollom response. 454 if (empty($GLOBALS['mollom']['response']['session_id'])) { 455 return FALSE; 456 } 457 $data = $GLOBALS['mollom']['response']; 458 $data['session'] = $data['session_id']; 459 $data['entity'] = $entity; 460 $data['did'] = $id; 461 $data['changed'] = time(); 462 463 // Convert languages into a string. 464 if (!empty($data['languages'])) { 465 $data['languages'] = implode(' ', $data['languages']); 466 } 467 // Merge in default values that may not exist in the response. 468 $data += array( 469 'languages' => '', 470 'quality' => '', 471 'reputation' => '', 472 ); 473 474 $update = db_result(db_query_range("SELECT 'did' FROM {mollom} WHERE entity = '%s' AND did = '%s'", $entity, $id, 0, 1)); 475 drupal_write_record('mollom', $data, $update ? $update : array()); 476 return $data; 477 } 478 479 /** 480 * Deletes a Mollom session data record from the database. 481 * 482 * @param $entity 483 * The entity type to delete data for. 484 * @param $id 485 * The entity id to delete data for. 486 */ 487 function mollom_data_delete($entity, $id) { 488 return mollom_data_delete_multiple($entity, array($id)); 489 } 490 491 /** 492 * Deletes multiple Mollom session data records from the database. 493 * 494 * @param $entity 495 * The entity type to delete data for. 496 * @param $ids 497 * An array of entity ids to delete data for. 498 */ 499 function mollom_data_delete_multiple($entity, $ids) { 500 $placeholders = db_placeholders($ids, 'varchar'); 501 return db_query("DELETE FROM {mollom} WHERE entity = '%s' AND did IN ($placeholders)", array_merge(array($entity), $ids)); 502 } 503 504 /** 505 * Helper function to add Mollom feedback options to confirmation forms. 506 */ 507 function mollom_data_delete_form_alter(&$form, &$form_state) { 508 if (!isset($form['actions']['#weight'])) { 509 $form['actions']['#weight'] = 100; 510 } 511 if (!isset($form['description']['#weight'])) { 512 $form['description']['#weight'] = 90; 513 } 514 $form['mollom'] = array( 515 '#tree' => TRUE, 516 '#weight' => 80, 517 ); 518 $form['mollom']['feedback'] = array( 519 '#type' => 'radios', 520 '#title' => t('Report as inappropriate'), 521 '#options' => array( 522 0 => t('Do no report'), 523 'spam' => t('Spam, unsolicited advertising'), 524 'profanity' => t('Obscene, violent, profane'), 525 'low-quality' => t('Low-quality'), 526 'unwanted' => t('Unwanted, taunting, off-topic'), 527 ), 528 '#default_value' => 0, 529 '#description' => t('Sending feedback to <a href="@mollom-url">Mollom</a> improves the automated moderation of new submissions.', array('@mollom-url' => 'http://mollom.com')), 530 ); 531 } 532 533 /** 534 * Send feedback to Mollom and delete Mollom data. 535 * 536 * @see mollom_form_alter() 537 */ 538 function mollom_data_delete_form_submit($form, &$form_state) { 539 $forms = mollom_form_cache(); 540 $mollom_form = mollom_form_load($forms['delete'][$form_state['values']['form_id']]); 541 $data = mollom_form_get_values($form_state['values'], $mollom_form['enabled_fields'], $mollom_form['mapping']); 542 543 $entity = $mollom_form['entity']; 544 $id = $data['post_id']; 545 546 if (!empty($form_state['values']['mollom']['feedback'])) { 547 if (mollom_data_report($entity, $id, $form_state['values']['mollom']['feedback']) === TRUE) { 548 drupal_set_message(t('The content was successfully reported as inappropriate.')); 549 } 550 } 551 552 // Remove Mollom session data. 553 mollom_data_delete($entity, $id); 554 } 555 556 /** 557 * Sends feedback for a Mollom session data record. 558 * 559 * @param $entity 560 * The entity type to send feedback for. 561 * @param $id 562 * The entity id to send feedback for. 563 */ 564 function mollom_data_report($entity, $id, $feedback) { 565 return mollom_data_report_multiple($entity, array($id), $feedback); 566 } 567 568 /** 569 * Sends feedback for multiple Mollom session data records. 570 * 571 * @param $entity 572 * The entity type to send feedback for. 573 * @param $ids 574 * An array of entity ids to send feedback for. 575 */ 576 function mollom_data_report_multiple($entity, $ids, $feedback) { 577 $return = TRUE; 578 foreach ($ids as $id) { 579 // Load the Mollom session data. 580 $data = mollom_data_load($entity, $id); 581 // Send feedback, if we have session data. 582 if (isset($data->session)) { 583 $result = _mollom_send_feedback($data->session, $feedback); 584 $return = $return && $result; 585 } 586 } 587 return $return; 588 } 589 590 /** 591 * Implements hook_form_alter(). 592 * 593 * This function intercepts all forms in Drupal and Mollom-enables them if 594 * necessary. 595 */ 596 function mollom_form_alter(&$form, &$form_state, $form_id) { 597 static $forms; 598 599 // Skip installation and update forms. 600 if (defined('MAINTENANCE_MODE')) { 601 return; 602 } 603 // Verify global Mollom configuration status. 604 $status = _mollom_status(); 605 if ($status !== TRUE) { 606 return; 607 } 608 609 // Retrieve a list of all protected forms once. 610 if (!isset($forms)) { 611 $forms = mollom_form_cache(); 612 } 613 614 // Remind of enabled testing mode on all protected forms. 615 if (isset($forms['protected'][$form_id]) || strpos($_GET['q'], 'admin/settings/mollom') === 0) { 616 _mollom_testing_mode_warning(); 617 } 618 619 // Site administrators don't have their content checked with Mollom. 620 if (!user_access('bypass mollom protection')) { 621 // Retrieve configuration for this form. 622 if (isset($forms['protected'][$form_id]) && ($mollom_form = mollom_form_load($form_id))) { 623 // Determine whether to bypass validation for the current user. 624 foreach ($mollom_form['bypass access'] as $permission) { 625 if (user_access($permission)) { 626 return; 627 } 628 } 629 // Compute the weight of the CAPTCHA so we can position it in the form. 630 // #type 'actions' is new in D7, but contributed modules use the concept 631 // in D6 already. We therefore expect the key without #type. 632 if (isset($form['actions']) && !isset($form['actions']['#type'])) { 633 // D6 code should set a #weight. If none is set, we ensure a default of 634 // 100, like #type 'actions' in D7. 635 if (!isset($form['actions']['#weight'])) { 636 $form['actions']['#weight'] = 100; 637 } 638 $weight = $form['actions']['#weight'] - 1; 639 } 640 else { 641 $weight = 99999; 642 foreach (element_children($form) as $key) { 643 // Scan the top-level form elements for buttons. 644 if (isset($form[$key]['#type']) && in_array($form[$key]['#type'], array('submit', 'button', 'image_button'))) { 645 // For each button, slightly increase the weight to allocate room for 646 // the CAPTCHA. 647 if (isset($form[$key]['#weight'])) { 648 $form[$key]['#weight'] += 0.0002; 649 } 650 else { 651 $form[$key]['#weight'] = 1.0002; 652 } 653 // We want to position the CAPTCHA just before the first button, so 654 // we make the CAPTCHA's weight slightly lighter than the lightest 655 // button's weight. 656 $weight = min($weight, $form[$key]['#weight'] - 0.0001); 657 } 658 } 659 } 660 // Add Mollom form widget. 661 $form['mollom'] = array( 662 '#type' => 'mollom', 663 '#mollom_form' => $mollom_form, 664 '#weight' => $weight, 665 '#tree' => TRUE, 666 ); 667 // Add Mollom form validation handlers. 668 $form['#validate'][] = 'mollom_validate_analysis'; 669 $form['#validate'][] = 'mollom_validate_captcha'; 670 671 // Add a submit handler to remove form state storage. 672 $form['#submit'][] = 'mollom_form_submit'; 673 674 // Add link to privacy policy on forms protected via textual analysis, 675 // if enabled. 676 if ($mollom_form['mode'] == MOLLOM_MODE_ANALYSIS && variable_get('mollom_privacy_link', 1)) { 677 $form['mollom']['privacy'] = array( 678 '#prefix' => '<div class="description mollom-privacy">', 679 '#suffix' => '</div>', 680 '#value' => t('By submitting this form, you accept the <a href="@privacy-policy-url">Mollom privacy policy</a>.', array( 681 '@privacy-policy-url' => 'http://mollom.com/web-service-privacy-policy', 682 )), 683 '#weight' => 10, 684 ); 685 } 686 } 687 } 688 // Integrate with delete confirmation forms to send feedback to Mollom. 689 if (isset($forms['delete'][$form_id])) { 690 mollom_data_delete_form_alter($form, $form_state); 691 // Report before deleting. This needs to be handled here, since 692 // mollom_data_delete_form_alter() is re-used for mass-operation forms. 693 array_unshift($form['#submit'], 'mollom_data_delete_form_submit'); 694 } 695 } 696 697 /** 698 * Returns a cached mapping of protected and delete confirmation form ids. 699 * 700 * @param $reset 701 * (optional) Boolean whether to reset the static cache, flush the database 702 * cache, and return nothing (TRUE). Defaults to FALSE. 703 * 704 * @return 705 * An associative array containing: 706 * - protected: An associative array whose keys are protected form IDs and 707 * whose values are the corresponding module names the form belongs to. 708 * - delete: An associative array whose keys are 'delete form' ids and whose 709 * values are protected form ids; e.g. 710 * @code 711 * array( 712 * 'node_delete_confirm' => 'article_node_form', 713 * ) 714 * @endcode 715 * A single delete confirmation form id can map to multiple registered 716 * $form_ids, but only the first is taken into account. As in above example, 717 * we assume that all 'TYPE_node_form' definitions belong to the same entity 718 * and therefore have an identical 'post_id' mapping. 719 */ 720 function mollom_form_cache($reset = FALSE) { 721 if ($reset) { 722 // This catches both 'mollom_form_cache' as well as mollom_form_load()'s 723 // 'mollom:form:*' entries. 724 cache_clear_all('mollom', 'cache', TRUE); 725 return; 726 } 727 728 if ($cache = cache_get('mollom_form_cache')) { 729 return $cache->data; 730 } 731 732 $result = db_query("SELECT form_id, module FROM {mollom_form}"); 733 $forms['protected'] = array(); 734 while ($row = db_fetch_array($result)) { 735 $forms['protected'][$row['form_id']] = $row['module']; 736 } 737 738 // Build a list of delete confirmation forms of entities integrating with 739 // Mollom, so we are able to alter the delete confirmation form to display 740 // our feedback options. 741 $forms['delete'] = array(); 742 foreach (mollom_form_list() as $form_id => $info) { 743 if (!isset($info['delete form']) || !isset($info['entity'])) { 744 continue; 745 } 746 // We expect that the same delete confirmation form uses the same form 747 // element mapping, so multiple 'delete form' definitions are only processed 748 // once. Additionally, we only care for protected forms. 749 if (!isset($forms['delete'][$info['delete form']]) && isset($forms['protected'][$form_id])) { 750 // A delete confirmation form integration requires a 'post_id' mapping. 751 $form_info = mollom_form_info($form_id, $info['module']); 752 if (isset($form_info['mapping']['post_id'])) { 753 $forms['delete'][$info['delete form']] = $form_id; 754 } 755 } 756 } 757 cache_set('mollom_form_cache', $forms); 758 759 return $forms; 760 } 761 762 /** 763 * Return the protection mode for a given form id. 764 * 765 * @return 766 * The protection mode for the given form id, one of: 767 * - MOLLOM_MODE_DISABLED: None. 768 * - MOLLOM_MODE_CAPTCHA: CAPTCHA only. 769 * - MOLLOM_MODE_ANALYSIS: Text analysis with CAPTCHA fallback. 770 */ 771 function mollom_get_mode($form_id) { 772 static $modes; 773 774 if (!isset($modes[$form_id])) { 775 $mollom_form = mollom_form_load($form_id); 776 $modes[$form_id] = isset($mollom_form['mode']) ? $mollom_form['mode'] : MOLLOM_MODE_DISABLED; 777 } 778 779 return $modes[$form_id]; 780 } 781 782 /** 783 * Returns a list of protectable forms registered via hook_mollom_form_info(). 784 */ 785 function mollom_form_list() { 786 $form_list = array(); 787 foreach (module_implements('mollom_form_list') as $module) { 788 $function = $module . '_mollom_form_list'; 789 $module_forms = $function(); 790 foreach ($module_forms as $form_id => $info) { 791 $form_list[$form_id] = $info; 792 $form_list[$form_id] += array( 793 'form_id' => $form_id, 794 'module' => $module, 795 ); 796 } 797 } 798 return $form_list; 799 } 800 801 /** 802 * Returns information about a form registered via hook_mollom_form_info(). 803 * 804 * @param $form_id 805 * The form id to return information for. 806 * @param $module 807 * The module name $form_id belongs to. 808 */ 809 function mollom_form_info($form_id, $module) { 810 $form_info = module_invoke($module, 'mollom_form_info', $form_id); 811 if (empty($form_info)) { 812 $form_info = array(); 813 } 814 815 // Ensure default properties. 816 $form_info += array( 817 'form_id' => $form_id, 818 'title' => $form_id, 819 'module' => $module, 820 'entity' => NULL, 821 'mode' => NULL, 822 'bypass access' => array(), 823 'elements' => array(), 824 'mapping' => array(), 825 'mail ids' => array(), 826 ); 827 828 // Allow modules to alter the default form information. 829 drupal_alter('mollom_form_info', $form_info, $form_id); 830 831 return $form_info; 832 } 833 834 /** 835 * Creates a bare Mollom form configuration. 836 * 837 * @param $form_id 838 * (optional) The form id to create the Mollom form configuration for. 839 */ 840 function mollom_form_new($form_id = NULL) { 841 $mollom_form = array(); 842 if (isset($form_id)) { 843 $form_list = mollom_form_list(); 844 if (isset($form_list[$form_id])) { 845 $mollom_form += $form_list[$form_id]; 846 } 847 $mollom_form += mollom_form_info($form_id, $form_list[$form_id]['module']); 848 } 849 // Ensure default properties. 850 $mollom_form += array( 851 'form_id' => $form_id, 852 'title' => $form_id, 853 'mode' => NULL, 854 'checks' => array(), 855 'elements' => array(), 856 'enabled_fields' => array(), 857 ); 858 // Enable all fields for textual analysis by default. 859 if (!empty($mollom_form['elements'])) { 860 $mollom_form['checks'] = array('spam'); 861 $mollom_form['enabled_fields'] = array_keys($mollom_form['elements']); 862 } 863 864 return $mollom_form; 865 } 866 867 /** 868 * Menu argument loader; Loads Mollom configuration and form information for a given form id. 869 */ 870 function mollom_form_load($form_id) { 871 $cid = 'mollom:form:' . $form_id; 872 if ($cache = cache_get($cid)) { 873 return $cache->data; 874 } 875 else { 876 $mollom_form = db_fetch_array(db_query("SELECT * FROM {mollom_form} WHERE form_id = '%s'", $form_id)); 877 if ($mollom_form) { 878 $mollom_form['checks'] = unserialize($mollom_form['checks']); 879 $mollom_form['enabled_fields'] = unserialize($mollom_form['enabled_fields']); 880 881 // Attach form registry information. 882 $form_list = mollom_form_list(); 883 if (isset($form_list[$form_id])) { 884 $mollom_form += $form_list[$form_id]; 885 } 886 $mollom_form += mollom_form_info($form_id, $mollom_form['module']); 887 888 cache_set($cid, $mollom_form); 889 } 890 } 891 return $mollom_form; 892 } 893 894 /** 895 * Saves a Mollom form configuration. 896 */ 897 function mollom_form_save(&$mollom_form) { 898 $exists = db_result(db_query_range("SELECT 1 FROM {mollom_form} WHERE form_id = '%s'", $mollom_form['form_id'], 0, 1)); 899 $status = drupal_write_record('mollom_form', $mollom_form, ($exists ? 'form_id' : array())); 900 901 // Allow modules to react on saved form configurations. 902 if ($status === SAVED_NEW) { 903 module_invoke_all('mollom_form_insert', $mollom_form); 904 } 905 else { 906 module_invoke_all('mollom_form_update', $mollom_form); 907 } 908 909 // Flush cached Mollom forms and the Mollom form mapping cache. 910 mollom_form_cache(TRUE); 911 912 return $status; 913 } 914 915 /** 916 * Given an array of values and an array of fields, extract data for use. 917 * 918 * This function generates the data to send for validation to Mollom by walking 919 * through the submitted form values and 920 * - copying element values as specified via 'mapping' in hook_mollom_form_info() 921 * into the dedicated data properties 922 * - collecting and concatenating all fields that have been selected for textual 923 * analysis into the 'post_body' property 924 * 925 * The processing accounts for the following possibilities: 926 * - A field was selected for textual analysis, but there is no submitted form 927 * value. The value should have been appended to the 'post_body' property, but 928 * will be skipped. 929 * - A field is contained in the 'mapping' and there is a submitted form value. 930 * The value will not be appended to the 'post_body', but instead be assigned 931 * to the specified data property. 932 * - All fields specified in 'mapping', for which there is a submitted value, 933 * but which were NOT selected for textual analysis, are assigned to the 934 * specified data property. This is usually the case for form elements that 935 * hold system user information. 936 * 937 * @param $values 938 * An array containing submitted form values, usually $form_state['values']. 939 * @param $fields 940 * A list of strings representing form elements to extract. Nested fields are 941 * in the form of 'parent][child'. 942 * @param $mapping 943 * An associative array of form elements to map to Mollom's dedicated data 944 * properties. See hook_mollom_form_info() for details. 945 * 946 * @see hook_mollom_form_info() 947 */ 948 function mollom_form_get_values($form_values, $fields, $mapping) { 949 global $user; 950 951 // All elements specified in $mapping must be excluded from $fields, as they 952 // are used for dedicated $data properties instead. To reduce the parsing code 953 // size, we are turning a given $mapping of f.e. 954 // array('post_title' => 'title_form_element') 955 // into 956 // array('title_form_element' => 'post_title') 957 // and we reset $mapping afterwards. 958 // When iterating over the $fields, this allows us to quickly test whether the 959 // current field should be excluded, and if it should, we directly get the 960 // mapped property name to rebuild $mapping with the field values. 961 $exclude_fields = array(); 962 if (!empty($mapping)) { 963 $exclude_fields = array_flip($mapping); 964 } 965 $mapping = array(); 966 967 // Process all fields that have been selected for text analysis. 968 $post_body = array(); 969 foreach ($fields as $field) { 970 // Nested elements use a key of 'parent][child', so we need to recurse. 971 $parents = explode('][', $field); 972 $value = $form_values; 973 foreach ($parents as $key) { 974 $value = isset($value[$key]) ? $value[$key] : NULL; 975 } 976 // If this field was contained in $mapping and should be excluded, add it to 977 // $mapping with the actual form element value, and continue to the next 978 // field. Also unset this field from $exclude_fields, so we can process the 979 // remaining mappings below. 980 if (isset($exclude_fields[$field]) && drupal_validate_utf8($value)) { 981 $mapping[$exclude_fields[$field]] = $value; 982 unset($exclude_fields[$field]); 983 continue; 984 } 985 // Only add form element values that are not empty. 986 if (isset($value)) { 987 if (is_string($value) && drupal_validate_utf8($value) && drupal_strlen($value)) { 988 $post_body[$field] = $value; 989 } 990 // Recurse into nested values (e.g. multiple value fields). 991 elseif (!empty($value)) { 992 // Ensure we have a flat array to implode(); form values of 993 // field_attach_form() use several subkeys. 994 _mollom_flatten_form_values($value); 995 if (($value = implode("\n", $value)) && drupal_validate_utf8($value)) { 996 $post_body[$field] = $value; 997 } 998 } 999 } 1000 } 1001 $post_body = implode("\n", $post_body); 1002 1003 // Try to assign any further form values by processing the remaining mappings, 1004 // which have been turned into $exclude_fields above. All fields that were 1005 // already used for 'post_body' no longer exist in $exclude_fields. 1006 foreach ($exclude_fields as $field => $property) { 1007 // Nested elements use a key of 'parent][child', so we need to recurse. 1008 $parents = explode('][', $field); 1009 $value = $form_values; 1010 foreach ($parents as $key) { 1011 $value = isset($value[$key]) ? $value[$key] : NULL; 1012 } 1013 if (isset($value) && drupal_validate_utf8($value)) { 1014 $mapping[$property] = $value; 1015 } 1016 } 1017 1018 // Mollom's XML-RPC methods only accept data properties that are defined. We 1019 // also do not want to send more than we have to, so we need to build an 1020 // exact data structure. 1021 $data = array(); 1022 // Post id; not sent to Mollom. 1023 // @see mollom_form_submit() 1024 if (!empty($mapping['post_id'])) { 1025 $data['post_id'] = $mapping['post_id']; 1026 } 1027 // Post title. 1028 if (!empty($mapping['post_title'])) { 1029 $data['post_title'] = $mapping['post_title']; 1030 } 1031 // Post body. 1032 if (!empty($post_body)) { 1033 $data['post_body'] = $post_body; 1034 } 1035 1036 // User name. 1037 if (!empty($mapping['author_name'])) { 1038 $data['author_name'] = $mapping['author_name']; 1039 // Try to inherit user from author name. 1040 $account = user_load(array('name' => $data['author_name'])); 1041 } 1042 elseif (!empty($user->name)) { 1043 $data['author_name'] = $user->name; 1044 } 1045 1046 // User e-mail. 1047 if (!empty($mapping['author_mail'])) { 1048 $data['author_mail'] = $mapping['author_mail']; 1049 } 1050 elseif (!empty($data['author_name'])) { 1051 if (!empty($account->mail)) { 1052 $data['author_mail'] = $account->mail; 1053 } 1054 } 1055 elseif (!empty($user->mail)) { 1056 $data['author_mail'] = $user->mail; 1057 } 1058 1059 // User homepage. 1060 if (!empty($mapping['author_url'])) { 1061 $data['author_url'] = $mapping['author_url']; 1062 } 1063 1064 // User ID. 1065 if (!empty($mapping['author_id'])) { 1066 $data['author_id'] = $mapping['author_id']; 1067 } 1068 elseif (!empty($data['author_name'])) { 1069 if (!empty($account->uid)) { 1070 $data['author_id'] = $account->uid; 1071 } 1072 } 1073 elseif (!empty($user->uid)) { 1074 $data['author_id'] = $user->uid; 1075 } 1076 1077 // User OpenID. 1078 if (!empty($mapping['author_openid'])) { 1079 $data['author_openid'] = $mapping['author_openid']; 1080 } 1081 elseif (!empty($data['author_id'])) { 1082 if (!empty($account->uid) && ($openid = _mollom_get_openid($account))) { 1083 $data['author_openid'] = $openid; 1084 } 1085 } 1086 elseif (!empty($user->uid) && ($openid = _mollom_get_openid($user))) { 1087 $data['author_openid'] = $openid; 1088 } 1089 1090 // User IP. 1091 $data['author_ip'] = ip_address(); 1092 1093 return $data; 1094 } 1095 1096 /** 1097 * Recursive helper function to flatten nested form values. 1098 * 1099 * Takes a potentially nested array and moves all nested keys to the top-level. 1100 */ 1101 function _mollom_flatten_form_values(&$values) { 1102 foreach ($values as $key => $value) { 1103 if (is_array($value)) { 1104 $values += _mollom_flatten_form_values($value); 1105 unset($values[$key]); 1106 } 1107 } 1108 return $values; 1109 } 1110 1111 /** 1112 * Helper function to return OpenID identifiers associated with a given user account. 1113 */ 1114 function _mollom_get_openid($account) { 1115 if (isset($account->uid)) { 1116 $result = db_query("SELECT * FROM {authmap} WHERE module = 'openid' AND uid = %d", $account->uid); 1117 1118 $ids = array(); 1119 while ($identity = db_fetch_object($result)) { 1120 $ids[] = $identity->authname; 1121 } 1122 1123 if (!empty($ids)) { 1124 return implode($ids, ' '); 1125 } 1126 } 1127 } 1128 1129 /** 1130 * Returns the (last known) status of the configured Mollom API keys. 1131 * 1132 * @param $reset 1133 * (optional) Boolean whether to reset the stored state and re-check. 1134 * Defaults to FALSE. 1135 * 1136 * @return 1137 * TRUE if the module is considered operable, or an associative array 1138 * describing the current status of the module: 1139 * - keys: Boolean whether Mollom API keys have been configured. 1140 * - keys valid: TRUE if Mollom API keys are valid, or the error code as 1141 * returned by Mollom servers. 1142 * - servers: Boolean whether there is a non-empty list of Mollom servers. 1143 * 1144 * @see mollom_init() 1145 * @see mollom_admin_settings() 1146 * @see mollom_requirements() 1147 */ 1148 function _mollom_status($reset = FALSE) { 1149 // Load stored status. 1150 $status = variable_get('mollom_status', array( 1151 'keys' => FALSE, 1152 'keys valid' => FALSE, 1153 )); 1154 1155 // Both API keys are required. 1156 $public_key = variable_get('mollom_public_key', ''); 1157 $private_key = variable_get('mollom_private_key', ''); 1158 $status['keys'] = (!empty($public_key) && !empty($private_key)); 1159 1160 // If we have keys and are asked to reset, check whether keys are valid. 1161 if ($status['keys'] && $reset) { 1162 $status['keys valid'] = mollom('mollom.verifyKey', _mollom_get_version()); 1163 } 1164 1165 // In case of an error, indicate whether we have a non-empty server list. 1166 if ($status['keys valid'] !== TRUE) { 1167 $servers = variable_get('mollom_servers', array()); 1168 $status['servers'] = !empty($servers); 1169 } 1170 1171 // Update stored status upon reset. 1172 if ($reset) { 1173 variable_set('mollom_status', $status); 1174 } 1175 1176 return ($status['keys valid'] === TRUE ? TRUE : $status); 1177 } 1178 1179 /** 1180 * Outputs a warning message about enabled testing mode (once). 1181 */ 1182 function _mollom_testing_mode_warning() { 1183 static $warned; 1184 if (isset($warned)) { 1185 return; 1186 } 1187 $warned = TRUE; 1188 1189 if (variable_get('mollom_testing_mode', 0) && empty($_POST)) { 1190 $admin_message = ''; 1191 if (user_access('administer mollom') && $_GET['q'] != 'admin/settings/mollom/settings') { 1192 $admin_message = t('Visit the <a href="@settings-url">Mollom settings page</a> to disable it.', array( 1193 '@settings-url' => url('admin/settings/mollom/settings'), 1194 )); 1195 } 1196 $message = t('Mollom testing mode is still enabled. !admin-message', array( 1197 '!admin-message' => $admin_message, 1198 )); 1199 drupal_set_message($message, 'warning'); 1200 } 1201 } 1202 1203 /** 1204 * Helper function to log and optionally output an error message when Mollom servers are unavailable. 1205 */ 1206 function _mollom_fallback() { 1207 $fallback = variable_get('mollom_fallback', MOLLOM_FALLBACK_BLOCK); 1208 // @todo Prevents mollom_admin_settings() from implementing a proper form 1209 // validation. Add !empty($_POST) to this condition + manually invoke from 1210 // mollom_process_form() on GET requests? Or don't call it from mollom()? 1211 // Anything, but just don't mix FAPI logic into XML-RPC logic. 1212 if ($fallback == MOLLOM_FALLBACK_BLOCK) { 1213 form_set_error('mollom', t("The spam filter installed on this site is currently unavailable. Per site policy, we are unable to accept new submissions until that problem is resolved. Please try resubmitting the form in a couple of minutes.")); 1214 } 1215 1216 $servers = variable_get('mollom_servers', array()); 1217 _mollom_watchdog(array( 1218 'All servers unavailable: %servers' => array('%servers' => $servers ? implode(', ', $servers) : '--'), 1219 'Last error: @code %message' => array('@code' => xmlrpc_errno(), '%message' => xmlrpc_error_msg()), 1220 ), WATCHDOG_ERROR); 1221 } 1222 1223 /** 1224 * @defgroup mollom_form_api Mollom Form API workarounds 1225 * @{ 1226 * Various helper functions to work around bugs in Form API. 1227 * 1228 * Normally, Mollom's integration with Form API would be quite simple: 1229 * - If a form is protected by Mollom, we setup initial information 1230 * about the session and the form in $form_state['storage'], bound to the 1231 * 'form_build_id'. 1232 * - We enable form caching via $form_state['cache'], so our information in the 1233 * form storage is cached. Form API then automatically ensures a proper 1234 * 'form_build_id' for every form and every user. 1235 * - We mainly work in and after form validation. Textual analysis validates all 1236 * values in the form as a form validation handler. If this validation fails, 1237 * we alter the form (during validation) to add a CAPTCHA. If the CAPTCHA 1238 * response is invalid, we still alter the form during validation to display a 1239 * new CAPTCHA, but without the previously entered value. 1240 * - In short, roughly: 1241 * - Form construction: Nothing. 1242 * - Form processing: Nothing. 1243 * - Form validation: Perform validation and alterations based on validation. 1244 * 1245 * This, however, is not possible due to various bugs in Drupal core. 1246 * - Form caching cannot be enabled for certain forms, because they contain 1247 * processing and validation logic. 1248 * http://drupal.org/node/644222 1249 * - $form_state['storage'] is not updated after form processing and validation. 1250 * http://drupal.org/node/644150 1251 * - Form validation handlers cannot alter the form structure. 1252 * http://drupal.org/node/642702 1253 * 1254 * Hence, something that could be done in one simple function becomes quite a 1255 * nightmare: 1256 * - We need our own {cache_mollom} table as replacement for native form 1257 * caching, as well as our own logic to validate a submitted 'session_id' 1258 * ('form_build_id') against forms and users. 1259 * - We need to perform form alterations during form rendering, where 1260 * $form_state is no longer available. To make this possible, we leverage the 1261 * fact that an element property that is a reference to a key in $form_state 1262 * (which in itself is passed by reference) persists on to the rendering 1263 * layer. The essential part is: 1264 * @code 1265 * $element['#mollom'] = &$form_state['mollom']; 1266 * @endcode 1267 * - Since we cannot alter elements in the form structure during form 1268 * validation, this reference already needs to be set up during form 1269 * processing (in a #process callback), while everything else lives in form 1270 * validation handlers (unless it needs to add or alter the form structure). 1271 * 1272 * @see mollom_form_alter() 1273 */ 1274 1275 /** 1276 * Implements hook_elements(). 1277 */ 1278 function mollom_elements() { 1279 return array( 1280 'mollom' => array( 1281 '#input' => TRUE, 1282 '#process' => array( 1283 // Try to fetch a Mollom session from cache during form processing/validation. 1284 'mollom_process_mollom_session_id', 1285 // Setup a new Mollom session. 1286 'mollom_process_mollom', 1287 ), 1288 '#pre_render' => array('mollom_pre_render_mollom'), 1289 ), 1290 ); 1291 } 1292 1293 /** 1294 * Implements hook_theme(). 1295 */ 1296 function mollom_theme() { 1297 return array( 1298 'mollom' => array( 1299 'arguments' => array('element' => NULL), 1300 ), 1301 'mollom_admin_blacklist_form' => array( 1302 'arguments' => array('form' => NULL), 1303 'file' => 'mollom.admin.inc', 1304 ), 1305 ); 1306 } 1307 1308 /** 1309 * Format the Mollom form element. 1310 * 1311 * This works like #type 'markup' and is only required, because D6 only supports 1312 * #process callbacks on elements with #input = TRUE. 1313 * 1314 * @see form_builder() 1315 * @see _form_builder_handle_input_element() 1316 */ 1317 function theme_mollom($element) { 1318 return isset($element['#children']) ? $element['#children'] : ''; 1319 } 1320 1321 /** 1322 * Form element #process callback for the 'mollom' element. 1323 * 1324 * The 'mollom' form element is stateful. The Mollom session ID that is exchanged 1325 * between Drupal, the Mollom back-end, and the user allows us to keep track of 1326 * the form validation state. 1327 * 1328 * The session ID is valid for a given $form_id only. We expire it as soon as 1329 * the form is submitted, to avoid it being replayed. 1330 */ 1331 function mollom_process_mollom($element, $input, &$form_state, $complete_form) { 1332 // Setup initial Mollom session and form information. 1333 if (empty($form_state['mollom'])) { 1334 $form_state['mollom'] = array( 1335 'require_analysis' => $element['#mollom_form']['mode'] == MOLLOM_MODE_ANALYSIS, 1336 'require_captcha' => $element['#mollom_form']['mode'] == MOLLOM_MODE_CAPTCHA, 1337 'passed_captcha' => FALSE, 1338 'response' => array( 1339 'session_id' => '', 1340 ), 1341 ); 1342 } 1343 $form_state['mollom'] += $element['#mollom_form']; 1344 1345 // Add the Mollom session element. 1346 $element['session_id'] = array( 1347 '#type' => 'hidden', 1348 '#default_value' => isset($form_state['mollom']['response']['session_id']) ? $form_state['mollom']['response']['session_id'] : '', 1349 '#attributes' => array('class' => 'mollom-session-id'), 1350 ); 1351 1352 // Add the CAPTCHA element. 1353 $element['captcha'] = array( 1354 '#type' => 'textfield', 1355 '#title' => t('Word verification'), 1356 '#size' => 10, 1357 '#default_value' => '', 1358 '#description' => t("Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive."), 1359 ); 1360 1361 // Make Mollom form and session information available to #pre_render callback. 1362 // This must be assigned by reference. It is the essential "communication 1363 // layer" between form API and the rendering system. Any modifications to 1364 // $form_state['mollom'] will be carried over to the element for rendering. 1365 $element['#mollom'] = &$form_state['mollom']; 1366 1367 // Make Mollom form and session information available to entirely different 1368 // functions. 1369 $GLOBALS['mollom'] = &$form_state['mollom']; 1370 1371 return $element; 1372 } 1373 1374 /** 1375 * Form element #process callback for Mollom's form storage handling. 1376 * 1377 * Albeit this *should* be an #element_validate handler that is only executed 1378 * during form validation, we must use a #process callback, because 1379 * mollom_process_mollom() needs to copy over $form_state['mollom'] into 1380 * $element['#mollom'], and as of now, Form API does not allow form validation 1381 * handlers to alter any elements in the form structure by reference. 1382 * @see http://drupal.org/node/642702 1383 */ 1384 function mollom_process_mollom_session_id($element, $input, &$form_state) { 1385 // The current state can come either from the $form_state, if the form 1386 // was just rebuilt in the same request, or from data posted by the user. In 1387 // the latter case the state is fetched from the temporary data store. It is 1388 // verified that the session was created for the current form and that it has 1389 // not expired or already been used. 1390 if (empty($form_state['mollom']) && !empty($input['session_id'])) { 1391 @list($timestamp, $mollom_session_id) = explode('-', $input['session_id'], 2); 1392 1393 if (empty($mollom_session_id)) { 1394 watchdog('mollom', 'Bogus session id %session.', array('%session' => $form_state['input']['mollom']['session_id']), WATCHDOG_WARNING); 1395 } 1396 elseif (!$cache = cache_get($mollom_session_id, 'cache_mollom')) { 1397 if (time() - $timestamp > 30 * 60) { 1398 watchdog('mollom', 'Expired session id %session.', array('%session' => $mollom_session_id)); 1399 } 1400 else { 1401 watchdog('mollom', 'Unknown session id %session. This is not a bug in Mollom. If this happens too often, check your site for attacks.', array('%session' => $mollom_session_id), WATCHDOG_WARNING); 1402 } 1403 } 1404 elseif ($cache->data['form_id'] !== $form_state['values']['form_id']) { 1405 watchdog('mollom', 'Invalid form id %form_id for session id %session (generated for %form_id_session). This is not a bug in Mollom. If this happens too often, check your site for attacks.', array('%session' => $mollom_session_id, '%form_id_session' => $cache->data['form_id'], '%form_id' => $form_state['values']['form_id']), WATCHDOG_WARNING); 1406 } 1407 else { 1408 $form_state['mollom'] = $cache->data; 1409 } 1410 } 1411 return $element; 1412 } 1413 1414 /** 1415 * Form validation handler to perform textual analysis of submitted form values. 1416 * 1417 * Validation needs to re-run in case of a form validation error (elsewhere in 1418 * the form). In case Mollom's textual analysis returns no definite result, we 1419 * must trigger a CAPTCHA, but text analysis is always performed, even if the 1420 * CAPTCHA was solved correctly. 1421 */ 1422 function mollom_validate_analysis(&$form, &$form_state) { 1423 // Text analysis may only ever be skipped, if we do not require it in the 1424 // first place. With regard to that, $form_state['mollom']['require_analysis'] 1425 // is only set once during initialization of $form_state['mollom'] in 1426 // mollom_process_form() and must not be updated elsewhere. 1427 if (!$form_state['mollom']['require_analysis']) { 1428 return; 1429 } 1430 1431 // Perform textual analysis. 1432 $all_data = mollom_form_get_values($form_state['values'], $form_state['mollom']['enabled_fields'], $form_state['mollom']['mapping']); 1433 $data = $all_data; 1434 // Remove 'post_id' property; only used by mollom_form_submit(). 1435 if (isset($data['post_id'])) { 1436 unset($data['post_id']); 1437 } 1438 $data['session_id'] = $form_state['mollom']['response']['session_id']; 1439 $data['checks'] = implode(',', $form_state['mollom']['checks']); 1440 $result = mollom('mollom.checkContent', $data); 1441 // Use all available data properties for log messages below. 1442 $data += $all_data; 1443 1444 // Trigger global fallback behavior if there is no result. 1445 if (!isset($result['session_id'])) { 1446 return _mollom_fallback(); 1447 } 1448 1449 // If a new session ID was generated by Mollom, flush the old session from the 1450 // database cache. 1451 if (!empty($form_state['mollom']['response']['session_id']) && $result['session_id'] != $form_state['mollom']['response']['session_id']) { 1452 cache_clear_all($form_state['mollom']['response']['session_id'], 'cache_mollom'); 1453 } 1454 // Store the response returned by Mollom. 1455 $form_state['mollom']['response'] = $result; 1456 1457 // Handle the spam check result. 1458 // The Mollom backend is remembering results of previous mollom.checkContent 1459 // invocations for a single user/post session. When content is re-checked 1460 // during form validation, the result may change according to the values that 1461 // have been submitted (which e.g. can change during previews). Only in case 1462 // the spam check led to a MOLLOM_ANALYSIS_UNSURE result, and the user solved 1463 // the CAPTCHA correctly, subsequent spam check results will likely be 1464 // MOLLOM_ANALYSIS_HAM (though not guaranteed). 1465 $teaser = truncate_utf8(strip_tags(isset($data['post_title']) ? $data['post_title'] : isset($data['post_body']) ? $data['post_body'] : '--'), 40); 1466 if (isset($result['spam'])) { 1467 switch ($result['spam']) { 1468 case MOLLOM_ANALYSIS_HAM: 1469 $form_state['mollom']['require_captcha'] = FALSE; 1470 _mollom_watchdog(array( 1471 'Ham: %teaser' => array('%teaser' => $teaser), 1472 'Data:<pre>@data</pre>' => array('@data' => $data), 1473 'Result:<pre>@result</pre>' => array('@result' => $result), 1474 ), WATCHDOG_INFO); 1475 break; 1476 1477 case MOLLOM_ANALYSIS_SPAM: 1478 $form_state['mollom']['require_captcha'] = FALSE; 1479 form_set_error('mollom', t('Your submission has triggered the spam filter and will not be accepted.')); 1480 _mollom_watchdog(array( 1481 'Spam: %teaser' => array('%teaser' => $teaser), 1482 'Data:<pre>@data</pre>' => array('@data' => $data), 1483 'Result:<pre>@result</pre>' => array('@result' => $result), 1484 )); 1485 break; 1486 1487 case MOLLOM_ANALYSIS_UNSURE: 1488 _mollom_watchdog(array( 1489 'Unsure: %teaser' => array('%teaser' => $teaser), 1490 'Data:<pre>@data</pre>' => array('@data' => $data), 1491 'Result:<pre>@result</pre>' => array('@result' => $result), 1492 ), WATCHDOG_INFO); 1493 1494 // Only throw a validation error and retrieve a CAPTCHA, if we check 1495 // this post for the first time. Otherwise, mollom_validate_captcha() 1496 // issued the CAPTCHA and needs to validate it prior to throwing any 1497 // errors. 1498 if (!$form_state['mollom']['require_captcha']) { 1499 $form_state['mollom']['require_captcha'] = TRUE; 1500 form_set_error('mollom][captcha', t('To complete this form, please complete the word verification below.')); 1501 } 1502 break; 1503 1504 case MOLLOM_ANALYSIS_UNKNOWN: 1505 default: 1506 // If we end up here, something went totally wrong. 1507 _mollom_fallback(); 1508 break; 1509 } 1510 } 1511 } 1512 1513 /** 1514 * Form validation handler for Mollom's CAPTCHA form element. 1515 * 1516 * Validates whether a CAPTCHA was solved correctly. A form may contain a 1517 * CAPTCHA, if it was configured to be protected by a CAPTCHA only, or when the 1518 * text analysis result is "unsure". 1519 */ 1520 function mollom_validate_captcha(&$form, &$form_state) { 1521 // CAPTCHA validation may only be skipped, if we do not require it in the 1522 // first place, or if the user already solved a CAPTCHA correctly. We need to 1523 // validate, if $form_state['mollom']['require_captcha'] is TRUE, which is 1524 // either set during initialization of $form_state['mollom'] in 1525 // mollom_process_form(), or after performing text analysis. The second 1526 // return condition, $form_state['mollom']['passed_captcha'], may only ever be 1527 // set by this validation handler and must not be changed elsewhere. 1528 if (!$form_state['mollom']['require_captcha'] || $form_state['mollom']['passed_captcha']) { 1529 return; 1530 } 1531 1532 // Nothing to validate if there is no value. 1533 // @todo The field is #required, so Form API should already handle this. Add a 1534 // test to be sure and remove this code. 1535 if (empty($form_state['values']['mollom']['captcha'])) { 1536 form_set_error('mollom][captcha', t('The word verification field is required.')); 1537 return; 1538 } 1539 1540 // Check the CAPTCHA result. 1541 // Next to the Mollom session id and captcha result, the Mollom back-end also 1542 // takes into account the author's IP and local user id (if registered). Any 1543 // other values are ignored. 1544 $all_data = mollom_form_get_values($form_state['values'], $form_state['mollom']['enabled_fields'], $form_state['mollom']['mapping']); 1545 $data = array( 1546 'session_id' => $form_state['mollom']['response']['session_id'], 1547 'captcha_result' => $form_state['values']['mollom']['captcha'], 1548 'author_ip' => $all_data['author_ip'], 1549 'author_id' => isset($all_data['author_id']) ? $all_data['author_id'] : NULL, 1550 ); 1551 $result = mollom('mollom.checkCaptcha', $data); 1552 // Use all available data properties for log messages below. 1553 $data += $all_data; 1554 1555 // Invoke fallback behavior upon a server error; communication errors are 1556 // handled by mollom() already. A server error may happen in case of an 1557 // expired or invalid session_id. 1558 if ($result === MOLLOM_ERROR) { 1559 return _mollom_fallback(); 1560 } 1561 1562 // Store the response for #submit handlers. 1563 $form_state['mollom']['response']['captcha'] = $result; 1564 1565 if ($result === TRUE) { 1566 $form_state['mollom']['passed_captcha'] = TRUE; 1567 1568 _mollom_watchdog(array( 1569 'Correct CAPTCHA' => array(), 1570 'Data:<pre>@data</pre>' => array('@data' => $data), 1571 'Result:<pre>@result</pre>' => array('@result' => $result), 1572 ), WATCHDOG_INFO); 1573 } 1574 else { 1575 form_set_error('mollom][captcha', t('The word verification was not completed correctly. Please complete this new word verification and try again.')); 1576 _mollom_watchdog(array( 1577 'Incorrect CAPTCHA' => array(), 1578 'Data:<pre>@data</pre>' => array('@data' => $data), 1579 'Result:<pre>@result</pre>' => array('@result' => $result), 1580 ), WATCHDOG_INFO); 1581 } 1582 } 1583 1584 /** 1585 * Form element #pre_render callback for CAPTCHA element. 1586 * 1587 * Conditionally alters the #type of the CAPTCHA form element into a 'hidden' 1588 * element if the response was correct. If it was not, then we empty the value 1589 * of the textfield to allow the user to re-enter a new one. 1590 * 1591 * This #pre_render trick is required, because form API validation does not 1592 * allow form validation handlers to alter the actual form structure. Both the 1593 * form constructor function and the #process callback for the 'mollom' element 1594 * are therefore executed too early (before form validation), so the CAPTCHA 1595 * element still contains not yet validated (default) values. 1596 * We also cannot invoke a form validation handler during form construction or 1597 * processing, because mollom_form_get_values() would be invoked too early 1598 * and therefore $form_state['values'] would not contain any additions from 1599 * form validation functions like mollom_comment_form_validate(). 1600 * @see http://drupal.org/node/642702 1601 */ 1602 function mollom_pre_render_mollom($element) { 1603 $form_state['mollom'] = &$element['#mollom']; 1604 // Request and inject a CAPTCHA when required; but also in case validation 1605 // through textual analysis failed. 1606 if ($form_state['mollom']['require_captcha'] && !$form_state['mollom']['passed_captcha']) { 1607 $element['captcha']['#required'] = TRUE; 1608 // Empty the CAPTCHA field value, since the user has to re-enter a new one. 1609 $element['captcha']['#value'] = ''; 1610 1611 // Prevent the page cache from storing a form containing a CAPTCHA element. 1612 $GLOBALS['conf']['cache'] = CACHE_DISABLED; 1613 1614 $data = array(); 1615 if (!empty($form_state['mollom']['response']['session_id'])) { 1616 $data['session_id'] = $form_state['mollom']['response']['session_id']; 1617 } 1618 $captcha = mollom_get_captcha('image', $data); 1619 1620 // If we get a response, add the image CAPTCHA to the form element. 1621 if (isset($captcha['response']['session_id']) && !empty($captcha['markup'])) { 1622 $element['captcha']['#field_prefix'] = $captcha['markup']; 1623 1624 // If a new session ID was generated by Mollom, flush the old session from 1625 // the database cache. 1626 if (!empty($form_state['mollom']['response']['session_id']) && $captcha['response']['session_id'] != $form_state['mollom']['response']['session_id']) { 1627 cache_clear_all($form_state['mollom']['response']['session_id'], 'cache_mollom'); 1628 } 1629 // Assign the session ID returned by Mollom. 1630 $form_state['mollom']['response']['session_id'] = $captcha['response']['session_id']; 1631 } 1632 // Otherwise, we have a communication or configuration error. 1633 // @todo Short-cut form processing entirely in this case; see also 1634 // mollom_validate_captcha(). 1635 else { 1636 $form_state['mollom']['require_analysis'] = FALSE; 1637 $form_state['mollom']['require_captcha'] = FALSE; 1638 return array(); 1639 } 1640 } 1641 // If no CAPTCHA is required or the response was correct, hide the CAPTCHA. 1642 elseif (!$form_state['mollom']['require_captcha'] || $form_state['mollom']['passed_captcha']) { 1643 $element['captcha']['#access'] = FALSE; 1644 } 1645 1646 // If we received a Mollom session id via textual analysis or a CAPTCHA 1647 // request, inject it to the form. 1648 $timestamp = time(); 1649 if (!empty($form_state['mollom']['response']['session_id'])) { 1650 $element['session_id']['#value'] = $timestamp . '-' . $form_state['mollom']['response']['session_id']; 1651 cache_set($form_state['mollom']['response']['session_id'], $form_state['mollom'], 'cache_mollom', $timestamp + 21600); 1652 } 1653 1654 return $element; 1655 } 1656 1657 /** 1658 * Form submit handler to flush Mollom session and form information from cache. 1659 */ 1660 function mollom_form_submit($form, &$form_state) { 1661 // Some modules are implementing multi-step forms without separate form 1662 // submit handlers. In case we reach here and the form will be rebuilt, we 1663 // need to defer our submit handling until final submission. 1664 if (!empty($form_state['rebuild'])) { 1665 return; 1666 } 1667 // If an 'entity' and a 'post_id' mapping was provided via 1668 // hook_mollom_form_info(), try to automatically store Mollom session data. 1669 if (!empty($form_state['mollom']['entity']) && isset($form_state['mollom']['mapping']['post_id'])) { 1670 // For new entities, the entity's form submit handler will have added the 1671 // new entity id value into $form_state['values'], so we need to rebuild the 1672 // data mapping. We do not care for the actual fields, only for the value of 1673 // the mapped post_id. 1674 $data = mollom_form_get_values($form_state['values'], array(), $form_state['mollom']['mapping']); 1675 // We only consider non-empty and non-zero values as valid entity ids. 1676 if (!empty($data['post_id'])) { 1677 mollom_data_save($form_state['mollom']['entity'], $data['post_id']); 1678 } 1679 } 1680 // Flush Mollom session information from database cache. 1681 if (!empty($form_state['mollom']['response']['session_id'])) { 1682 cache_clear_all($form_state['mollom']['response']['session_id'], 'cache_mollom'); 1683 } 1684 // Remove Mollom session information from form state to account for unforeseen 1685 // new builds of the form. 1686 unset($form_state['mollom']); 1687 } 1688 1689 /** 1690 * @} End of "defgroup mollom_form_api". 1691 */ 1692 1693 /** 1694 * Call a remote procedure at the Mollom server. 1695 * 1696 * This function automatically adds the information required to authenticate 1697 * against Mollom. 1698 * 1699 * @todo Currently, this function's return value mixes actual values and 1700 * error values. We should rewrite the error handling so that calling 1701 * functions can properly handle error situations. 1702 */ 1703 function mollom($method, $data = array()) { 1704 module_load_include('inc', 'mollom'); 1705 $messages = array(); 1706 1707 // Initialize refresh variable. 1708 $refresh = FALSE; 1709 1710 // Enable testing mode. 1711 if (variable_get('mollom_testing_mode', 0)) { 1712 $data['testing'] = TRUE; 1713 } 1714 1715 // Retrieve the list of Mollom servers from the database. 1716 $servers = variable_get('mollom_servers', array()); 1717 1718 if (empty($servers)) { 1719 // Retrieve a new list of servers. 1720 $servers = _mollom_retrieve_server_list(); 1721 // If API keys are invalid, a XML-RPC error code is returned. 1722 if (!is_array($servers)) { 1723 return $servers; 1724 } 1725 1726 $messages[] = array( 1727 'Refreshed servers: %servers' => array('%servers' => implode(', ', $servers)), 1728 ); 1729 1730 // Store the list of servers in the database. 1731 variable_set('mollom_servers', $servers); 1732 } 1733 1734 if (is_array($servers)) { 1735 // Send the request to the first server; if that fails, try the other 1736 // servers in the list. 1737 reset($servers); 1738 while ($server = current($servers)) { 1739 $result = xmlrpc($server . '/' . MOLLOM_API_VERSION, $method, $data + _mollom_authentication()); 1740 1741 if ($result === FALSE && ($error = xmlrpc_error())) { 1742 if ($error->code === MOLLOM_REFRESH) { 1743 // Avoid endless loops. 1744 if (!$refresh) { 1745 $refresh = TRUE; 1746 1747 // Retrieve a new list of valid Mollom servers. 1748 $servers = _mollom_retrieve_server_list(); 1749 // If API keys are invalid, the XML-RPC error code is returned. 1750 // To reach this, we must have had a server list (and therefore 1751 // valid keys) before, so we do not immediately return (like above), 1752 // but instead trigger the fallback mode. 1753 if (!is_array($servers)) { 1754 break; 1755 } 1756 1757 // Reset the list of servers to restart from the first server. 1758 reset($servers); 1759 1760 // Update the server list. 1761 variable_set('mollom_servers', $servers); 1762 1763 $messages[] = array( 1764 'Refreshed servers: %servers' => array('%servers' => implode(', ', $servers)), 1765 ); 1766 } 1767 } 1768 elseif ($error->code === MOLLOM_REDIRECT) { 1769 // Try the next server in the list. 1770 $next = next($servers); 1771 1772 $messages[] = array( 1773 'Server %server redirected to: %next.' => array('%server' => $server, '%next' => $next), 1774 ); 1775 } 1776 else { 1777 $messages[] = array( 1778 'Error @errno from %server for %method: %message' => array( 1779 '@errno' => $error->code, 1780 '%server' => $server, 1781 '%method' => $method, 1782 '%message' => $error->message, 1783 ), 1784 'Data:<pre>@data</pre>' => array('@data' => $data), 1785 ); 1786 1787 // Instantly return upon a 'real' error. 1788 if ($error->code === MOLLOM_ERROR) { 1789 _mollom_watchdog_multiple($messages, WATCHDOG_ERROR); 1790 return MOLLOM_ERROR; 1791 } 1792 // Otherwise, try the next server. 1793 next($servers); 1794 } 1795 } 1796 else { 1797 _mollom_watchdog_multiple($messages, WATCHDOG_DEBUG); 1798 return $result; 1799 } 1800 } 1801 } 1802 1803 // If none of the servers worked, activate the fallback mechanism. 1804 // @todo mollom() can be invoked outside of form processing. _mollom_fallback() 1805 // unconditionally invokes form_set_error(), which always displays the 1806 // fallback error message. Ideally, we would pass a $verbose argument to 1807 // _mollom_fallback(), but for that, we'd have to know here already. 1808 // Consequently, mollom() would need that $verbose argument. In the end, we 1809 // likely want to either embed the fallback handling into form processing, 1810 // or introduce a new helper function that is invoked instead of mollom() 1811 // during form processing. 1812 if ($method != 'mollom.verifyKey') { 1813 _mollom_fallback(); 1814 } 1815 1816 // If everything failed, we reset the server list to force Mollom to request 1817 // a new list. 1818 variable_del('mollom_servers'); 1819 1820 // Report this error. 1821 $messages[] = array( 1822 'All servers unreachable or returning errors. The server list was emptied.' => array(), 1823 ); 1824 _mollom_watchdog_multiple($messages, WATCHDOG_ERROR); 1825 1826 return NETWORK_ERROR; 1827 } 1828 1829 /** 1830 * Log a Mollom system message. 1831 * 1832 * @param $parts 1833 * A list of message parts. Each item is an associative array whose keys are 1834 * log message strings and whose corresponding values are t()-style 1835 * replacement token arguments. At least one part is required. 1836 * @param $severity 1837 * The severity of the message, as per RFC 3164. Possible values are 1838 * WATCHDOG_ERROR, WATCHDOG_WARNING, etc. 1839 * 1840 * @see watchdog() 1841 */ 1842 function _mollom_watchdog(array $parts, $severity = WATCHDOG_NOTICE) { 1843 // First message part is required. 1844 $message = key($parts); 1845 $arguments = $parts[$message]; 1846 unset($parts[$message]); 1847 1848 // Hide further message details in the log overview table, if any. 1849 // @see theme_dblog_message() 1850 if ($parts) { 1851 $message = str_pad($message, 56, ' ', STR_PAD_RIGHT); 1852 $message .= '<br />'; 1853 } 1854 1855 // Each item in $parts is a part of the log message. 1856 foreach ($parts as $string => $string_arguments) { 1857 $message .= $string . "\n"; 1858 $arguments += $string_arguments; 1859 } 1860 1861 // Prettify replacement token values, if possible. 1862 foreach ($arguments as $token => $array) { 1863 $flat_value = FALSE; 1864 if (is_array($array)) { 1865 $flat_value = ''; 1866 foreach ($array as $key => $value) { 1867 if (is_array($value)) { 1868 $flat_value = FALSE; 1869 break; 1870 } 1871 $value = var_export($value, TRUE); 1872 // Indent the new value, so there is a visual separation from the last. 1873 $flat_value .= " {$key} = {$value}\n"; 1874 } 1875 } 1876 // Only convert one-dimensional arrays, or we would lose debugging data. 1877 if ($flat_value !== FALSE) { 1878 $arguments[$token] = $flat_value; 1879 } 1880 else { 1881 $arguments[$token] = var_export($array, TRUE); 1882 } 1883 } 1884 1885 watchdog('mollom', $message, $arguments, $severity); 1886 } 1887 1888 /** 1889 * Helper function for mollom() to invoke watchdog() with cumulative messages. 1890 * 1891 * We do not want false errors to clutter the log, for example, when the server 1892 * list failed, but we were able to retrieve new servers. We therefore collect 1893 * all messages and invoke this function in mollom() right before returning any 1894 * XML-RPC response with the entire stack of collected messages. 1895 * This is also required for tests to pass. 1896 */ 1897 function _mollom_watchdog_multiple($messages, $severity) { 1898 foreach ($messages as $message) { 1899 _mollom_watchdog($message, $severity); 1900 } 1901 } 1902 1903 /** 1904 * Returns version information to send with mollom.verifyKey. 1905 * 1906 * Retrieves platform and module version information for mollom.verifyKey, which 1907 * is normally invoked on Mollom's administration pages only. 1908 * 1909 * This information is solely used to speed up support requests and technical 1910 * inquiries. The data may also be aggregated to help the Mollom staff to make 1911 * decisions on new features or the necessity of back-porting improved 1912 * functionality to older versions. 1913 * 1914 * @return 1915 * An array containing: 1916 * - platform_name: The name of the Drupal distribution; i.e., "Drupal". 1917 * - platform_version: The version of Drupal; e.g., "7.0". 1918 * - client_name: The module name; i.e., "Mollom". 1919 * - client_version: The version of the module; e.g., "7.x-1.0". 1920 * 1921 * @see _mollom_status() 1922 */ 1923 function _mollom_get_version() { 1924 if ($cache = cache_get('mollom_version')) { 1925 return $cache->data; 1926 } 1927 1928 // Retrieve Drupal distribution and installation profile information. 1929 include_once './includes/install.inc'; 1930 // Prior to D7, there is no distribution name recorded anywhere, so we have to 1931 // use "Drupal". 1932 $profile_info['distribution_name'] = 'Drupal'; 1933 $profile_info['version'] = VERSION; 1934 1935 // Retrieve Mollom module information. 1936 $mollom_info = db_result(db_query("SELECT info FROM {system} WHERE type = 'module' AND name = 'mollom'")); 1937 $mollom_info = unserialize($mollom_info); 1938 if (empty($mollom_info['version'])) { 1939 // Manually build a module version string for repository checkouts. 1940 $mollom_info['version'] = DRUPAL_CORE_COMPATIBILITY . '-1.x-dev'; 1941 } 1942 1943 $data = array( 1944 'platform_name' => $profile_info['distribution_name'], 1945 'platform_version' => $profile_info['version'], 1946 'client_name' => $mollom_info['name'], 1947 'client_version' => $mollom_info['version'], 1948 ); 1949 cache_set('mollom_version', $data); 1950 1951 return $data; 1952 } 1953 1954 /** 1955 * Send feedback to Mollom. 1956 */ 1957 function _mollom_send_feedback($session_id, $feedback = 'spam') { 1958 $result = mollom('mollom.sendFeedback', array( 1959 'session_id' => $session_id, 1960 'feedback' => $feedback, 1961 )); 1962 _mollom_watchdog(array( 1963 'Reported %feedback for session id %session.' => array('%session' => $session_id, '%feedback' => $feedback), 1964 )); 1965 return $result; 1966 } 1967 1968 /** 1969 * Fetch the site's Mollom statistics from the API. 1970 * 1971 * @param $refresh 1972 * A boolean if TRUE, will force the statistics to be re-fetched and stored 1973 * in the cache. 1974 * 1975 * @return 1976 * An array of statistics. 1977 */ 1978 function mollom_get_statistics($refresh = FALSE) { 1979 $statistics = FALSE; 1980 $cache = cache_get('mollom:statistics'); 1981 1982 // Only fetch if $refresh is TRUE, the cache is empty, or the cache is expired. 1983 if ($refresh || !$cache || time() >= $cache->expire) { 1984 if (_mollom_status() === TRUE) { 1985 $statistics = drupal_map_assoc(array( 1986 'total_days', 1987 'total_accepted', 1988 'total_rejected', 1989 'yesterday_accepted', 1990 'yesterday_rejected', 1991 'today_accepted', 1992 'today_rejected', 1993 )); 1994 1995 foreach ($statistics as $statistic) { 1996 $result = mollom('mollom.getStatistics', array('type' => $statistic)); 1997 if ($result === NETWORK_ERROR || $result === MOLLOM_ERROR) { 1998 // If there was an error, stop fetching statistics and store FALSE 1999 // in the cache. This will help prevent from making unnecessary 2000 // requests to Mollom if the service is down or the server cannot 2001 // connect to the Mollom service. 2002 $statistics = FALSE; 2003 break; 2004 } 2005 else { 2006 $statistics[$statistic] = $result; 2007 } 2008 } 2009 } 2010 2011 // Cache the statistics and set them to expire in one hour. 2012 cache_set('mollom:statistics', $statistics, 'cache', time() + 3600); 2013 } 2014 else { 2015 $statistics = $cache->data; 2016 } 2017 2018 return $statistics; 2019 } 2020 2021 /** 2022 * Implements hook_content_extra_fields(). 2023 * 2024 * Allow users of CCK to re-order the CAPTCHA field on node forms through the 2025 * CCK UI. 2026 */ 2027 function mollom_content_extra_fields($type_name) { 2028 if ($mollom_form = mollom_form_load($type_name . '_node_form')) { 2029 $extras['mollom'] = array( 2030 'label' => t('Mollom'), 2031 'description' => t('Mollom CAPTCHA or privacy policy link'), 2032 'weight' => 99, 2033 ); 2034 return $extras; 2035 } 2036 } 2037 2038 /** 2039 * Get the HTML markup for a Mollom CAPTCHA. 2040 * 2041 * @param $type 2042 * The CAPTCHA type to retrieve, e.g. 'image' or 'audio'. 2043 * @param $data 2044 * An optional array of parameters to send to Mollom when requesting the 2045 * CAPTCHA. 2046 * 2047 * @return 2048 * An array with the following key/value pairs: 2049 * - 'data': An array of parameters sent to Mollom when requesting the 2050 * CAPTCHA. 2051 * - 'response': An array with the response from Mollom. 2052 * - 'markup': The markup of the CAPTCHA HTML. 2053 */ 2054 function mollom_get_captcha($type, array $data = array()) { 2055 $data += array( 2056 'author_ip' => ip_address(), 2057 'ssl' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on', 2058 ); 2059 2060 // @todo Convert these to actual theme functions? 2061 $output = ''; 2062 switch ($type) { 2063 case 'audio': 2064 $response = mollom('mollom.getAudioCaptcha', $data); 2065 if ($response) { 2066 $source = url(base_path() . drupal_get_path('module', 'mollom') . '/mollom-captcha-player.swf', array( 2067 'query' => array('url' => $response['url']), 2068 'external' => TRUE, 2069 )); 2070 $output = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="110" height="50">'; 2071 $output .= '<param name="allowFullScreen" value="false" />'; 2072 $output .= '<param name="movie" value="' . $source . '" />'; 2073 $output .= '<param name="loop" value="false" />'; 2074 $output .= '<param name="menu" value="false" />'; 2075 $output .= '<param name="quality" value="high" />'; 2076 $output .= '<param name="wmode" value="transparent" />'; 2077 $output .= '<param name="bgcolor" value="#ffffff" />'; 2078 $output .= '<embed src="' . $source . '" loop="false" menu="false" quality="high" wmode="transparent" bgcolor="#ffffff" width="110" height="50" align="baseline" allowScriptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer_de" />'; 2079 $output .= '</object>'; 2080 2081 $output = '<span class="mollom-captcha-content mollom-audio-captcha">' . $output . '</span>'; 2082 $output .= ' (<a href="#" class="mollom-switch-captcha mollom-image-captcha">' . t('verify using image') . '</a>)'; 2083 } 2084 break; 2085 2086 case 'image': 2087 $response = mollom('mollom.getImageCaptcha', $data); 2088 if ($response) { 2089 $captcha = theme('image', $response['url'], t('Type the characters you see in this picture.'), '', NULL, FALSE); 2090 $output = '<span class="mollom-captcha-content mollom-image-captcha">' . $captcha . '</span>'; 2091 $output .= ' (<a href="#" class="mollom-switch-captcha mollom-audio-captcha">' . t('verify using audio') . '</a>)'; 2092 } 2093 break; 2094 } 2095 2096 return array( 2097 'data' => $data, 2098 'response' => $response, 2099 'markup' => $output, 2100 ); 2101 } 2102 2103 /** 2104 * Implements hook_mail_alter(). 2105 * 2106 * Adds a "report as inappropriate" link to e-mails sent after Mollom-protected 2107 * form submissions. 2108 */ 2109 function mollom_mail_alter(&$message) { 2110 // Attached the Mollom report link to any mails with IDs specified from the 2111 // submitted form's hook_mollom_form_info(). This should ensure that the 2112 // report link is added to mails sent by actual users and not any mails sent 2113 // by Drupal since they should never be reported as spam. 2114 if (!empty($GLOBALS['mollom']['mail ids']) && in_array($message['id'], $GLOBALS['mollom']['mail ids'])) { 2115 mollom_mail_add_report_link($message); 2116 } 2117 } 2118 2119 /** 2120 * Add the 'Report as innapropriate' link to an e-mail message. 2121 */ 2122 function mollom_mail_add_report_link(&$message) { 2123 if (!empty($GLOBALS['mollom']['response']['session_id'])) { 2124 mollom_data_save('session', $GLOBALS['mollom']['response']['session_id']); 2125 $report_link = t('Report as inappropriate: @link', array( 2126 '@link' => url('mollom/report/session/' . $GLOBALS['mollom']['response']['session_id'], array('absolute' => TRUE)), 2127 )); 2128 // Until D7, hook_mail_alter() accepts both arrays and strings. 2129 if (is_array($message['body'])) { 2130 $message['body'][] = $report_link; 2131 } 2132 else { 2133 $message['body'] .= "\n\n" . $report_link; 2134 } 2135 } 2136 } 2137 2138 /** 2139 * @name mollom_node Node module integration for Mollom. 2140 * @{ 2141 */ 2142 2143 /** 2144 * Implements hook_mollom_form_list(). 2145 */ 2146 function node_mollom_form_list() { 2147 $forms = array(); 2148 foreach (node_get_types('types') as $type) { 2149 $form_id = $type->type . '_node_form'; 2150 $forms[$form_id] = array( 2151 'title' => t('@name form', array('@name' => $type->name)), 2152 'entity' => 'node', 2153 'report access callback' => 'node_mollom_report_access', 2154 'report delete callback' => 'node_mollom_report_delete', 2155 ); 2156 } 2157 return $forms; 2158 } 2159 2160 /** 2161 * Implements hook_mollom_form_info(). 2162 */ 2163 function node_mollom_form_info($form_id) { 2164 // Retrieve internal type from $form_id. 2165 $nodetype = drupal_substr($form_id, 0, -10); 2166 2167 $type = node_get_types('type', $nodetype); 2168 $form_info = array( 2169 // @todo This is incompatible with node access. 2170 'bypass access' => array('administer nodes', 'edit any ' . $type->type . ' content'), 2171 'bundle' => $type->type, 2172 'elements' => array(), 2173 'mapping' => array( 2174 'post_id' => 'nid', 2175 'author_name' => 'name', 2176 ), 2177 ); 2178 // @see node_content_form() 2179 if ($type->has_title) { 2180 $form_info['elements']['title'] = check_plain($type->title_label); 2181 $form_info['mapping']['post_title'] = 'title'; 2182 } 2183 if ($type->has_body) { 2184 $form_info['elements']['body'] = check_plain($type->body_label); 2185 } 2186 2187 // Add text fields. 2188 if (module_exists('content')) { 2189 $content_info = content_types($type->type); 2190 foreach ($content_info['fields'] as $field_name => $field) { 2191 if ($field['type'] == 'text') { 2192 $form_info['elements'][$field_name] = check_plain(t($field['widget']['label'])); 2193 } 2194 } 2195 } 2196 2197 return $form_info; 2198 } 2199 2200 /** 2201 * Implements hook_nodeapi(). 2202 */ 2203 function mollom_nodeapi($node, $op) { 2204 if ($op == 'insert') { 2205 mollom_data_save('node', $node->nid); 2206 } 2207 elseif ($op == 'delete') { 2208 mollom_data_delete('node', $node->nid); 2209 } 2210 } 2211 2212 /** 2213 * Implements hook_form_FORMID_alter(). 2214 * 2215 * Hook into the mass comment administration page and add some operations to 2216 * communicate ham/spam to the XML-RPC server. 2217 * 2218 * @see mollom_node_admin_overview_submit() 2219 */ 2220 function mollom_form_node_admin_content_alter(&$form, $form_state) { 2221 module_load_include('inc', 'mollom', 'mollom.admin'); 2222 $form['admin']['options']['operation']['#options']['mollom-unpublish'] = t('Report to Mollom and unpublish'); 2223 $form['admin']['options']['operation']['#options']['mollom-delete'] = t('Report to Mollom and delete'); 2224 $form['#validate'][] = 'mollom_node_admin_overview_submit'; 2225 } 2226 2227 /** 2228 * Mollom report access callback; Determine access to report and delete a node. 2229 */ 2230 function node_mollom_report_access($entity, $id) { 2231 $node = node_load($id); 2232 return $node && node_access('delete', $node); 2233 } 2234 2235 /** 2236 * Mollom report delete callback; Deletes a node. 2237 */ 2238 function node_mollom_report_delete($entity, $id) { 2239 node_delete($id); 2240 } 2241 2242 /** 2243 * @} End of "name mollom_node". 2244 */ 2245 2246 /** 2247 * @name mollom_comment Comment module integration for Mollom. 2248 * @{ 2249 */ 2250 2251 /** 2252 * Implements hook_mollom_form_list(). 2253 */ 2254 function comment_mollom_form_list() { 2255 $forms['comment_form'] = array( 2256 'title' => t('Comment form'), 2257 'entity' => 'comment', 2258 'report access' => array('administer comments'), 2259 'report delete callback' => 'comment_mollom_report_delete', 2260 ); 2261 return $forms; 2262 } 2263 2264 /** 2265 * Implements hook_mollom_form_info(). 2266 */ 2267 function comment_mollom_form_info($form_id) { 2268 $form_info = array( 2269 'mode' => MOLLOM_MODE_ANALYSIS, 2270 'bypass access' => array('administer comments'), 2271 'elements' => array( 2272 'subject' => t('Subject'), 2273 'comment' => t('Comment'), 2274 ), 2275 'mapping' => array( 2276 'post_id' => 'cid', 2277 'post_title' => 'subject', 2278 // In D6, comment_form() dynamically uses different form elements for 2279 // anonymous users, authenticated users, and comment administrators. 2280 'author_name' => 'name', 2281 'author_mail' => 'mail', 2282 'author_url' => 'homepage', 2283 ), 2284 ); 2285 2286 return $form_info; 2287 } 2288 2289 /** 2290 * Implements hook_form_FORMID_alter(). 2291 * 2292 * When a registered user posts a comment or when a comment administrator edits 2293 * an existing comment, comment_form() does not define 'name' and 'mail' form 2294 * elements, so our form element mapping will fail. 2295 * 2296 * @see comment_mollom_form_info() 2297 * @see mollom_comment_form_validate() 2298 * 2299 * @todo Remove in D7. 2300 */ 2301 function mollom_form_comment_form_alter(&$form, &$form_state) { 2302 if (isset($form['author']) || isset($form['admin']['author'])) { 2303 $form['#validate'][] = 'mollom_comment_form_validate'; 2304 } 2305 } 2306 2307 /** 2308 * Form validation handler for comment_form(). 2309 * 2310 * @todo Remove in D7. 2311 */ 2312 function mollom_comment_form_validate($form, &$form_state) { 2313 // If there were no validation errors, prepare submitted form values for 2314 // validation via Mollom. 2315 if (!form_get_errors()) { 2316 // Author is a registered user or comment is edited by administrator. 2317 if (isset($form_state['values']['author'])) { 2318 // Populate 'name' with value of 'author'. 2319 if (!isset($form_state['values']['name'])) { 2320 form_set_value(array('#parents' => array('name')), $form_state['values']['author'], $form_state); 2321 } 2322 // Populate 'mail' based on corresponding user account. 2323 if (!isset($form_state['values']['mail'])) { 2324 // This should already be validated by comment_validate(), but we still 2325 // double-check that we have a valid account before trying to access it. 2326 $account = user_load(array('name' => $form_state['values']['author'])); 2327 if ($account) { 2328 form_set_value(array('#parents' => array('mail')), $account->mail, $form_state); 2329 } 2330 } 2331 } 2332 } 2333 } 2334 2335 /** 2336 * Implements hook_comment(). 2337 */ 2338 function mollom_comment($comment, $op) { 2339 if ($op == 'insert') { 2340 mollom_data_save('comment', $comment['cid']); 2341 } 2342 elseif ($op == 'delete') { 2343 mollom_data_delete('comment', $comment->cid); 2344 } 2345 } 2346 2347 /** 2348 * Implements hook_form_FORMID_alter(). 2349 * 2350 * Hook into the mass comment administration page and add some operations to 2351 * communicate ham/spam to the XML-RPC server. 2352 * 2353 * @see mollom_comment_admin_overview_submit() 2354 */ 2355 function mollom_form_comment_admin_overview_alter(&$form, $form_state) { 2356 module_load_include('inc', 'mollom', 'mollom.admin'); 2357 $form['options']['operation']['#options']['mollom-unpublish'] = t('Report to Mollom and unpublish'); 2358 $form['options']['operation']['#options']['mollom-delete'] = t('Report to Mollom and delete'); 2359 $form['#submit'][] = 'mollom_comment_admin_overview_submit'; 2360 } 2361 2362 /** 2363 * Mollom report delete callback; Deletes a comment and its replies. 2364 * 2365 * @see comment_confirm_delete_submit() 2366 */ 2367 function comment_mollom_report_delete($entity, $id) { 2368 module_load_include('inc', 'comment', 'comment.admin'); 2369 $comment = _comment_load($id); 2370 // Delete the comment and its replies. 2371 _comment_delete_thread($comment); 2372 _comment_update_node_statistics($comment->nid); 2373 // Clear the cache so an anonymous user sees that his comment was deleted. 2374 cache_clear_all(); 2375 2376 drupal_set_message(t('The comment has been deleted.')); 2377 } 2378 2379 /** 2380 * @} End of "name mollom_comment". 2381 */ 2382 2383 /** 2384 * @name mollom_user User module integration for Mollom. 2385 * @{ 2386 */ 2387 2388 /** 2389 * Implements hook_mollom_form_list(). 2390 */ 2391 function user_mollom_form_list() { 2392 $forms['user_register'] = array( 2393 'title' => t('User registration form'), 2394 'entity' => 'user', 2395 ); 2396 $forms['user_pass'] = array( 2397 'title' => t('User password request form'), 2398 'entity' => 'user', 2399 ); 2400 return $forms; 2401 } 2402 2403 /** 2404 * Implements hook_mollom_form_info(). 2405 */ 2406 function user_mollom_form_info($form_id) { 2407 switch ($form_id) { 2408 case 'user_register': 2409 $form_info = array( 2410 'mode' => MOLLOM_MODE_CAPTCHA, 2411 'bypass access' => array('administer users'), 2412 'mapping' => array( 2413 'post_id' => 'uid', 2414 'author_name' => 'name', 2415 'author_mail' => 'mail', 2416 ), 2417 ); 2418 return $form_info; 2419 2420 case 'user_pass': 2421 $form_info = array( 2422 'mode' => MOLLOM_MODE_CAPTCHA, 2423 'bypass access' => array('administer users'), 2424 'mapping' => array( 2425 'post_id' => 'uid', 2426 'author_name' => 'name', 2427 // The 'name' form element accepts either a username or mail address. 2428 'author_mail' => 'name', 2429 ), 2430 ); 2431 return $form_info; 2432 } 2433 } 2434 2435 /** 2436 * @} End of "name mollom_user". 2437 */ 2438 2439 /** 2440 * @name mollom_contact Contact module integration for Mollom. 2441 * @{ 2442 */ 2443 2444 /** 2445 * Implements hook_mollom_form_list(). 2446 */ 2447 function contact_mollom_form_list() { 2448 $forms['contact_mail_page'] = array( 2449 'title' => t('Site-wide contact form'), 2450 ); 2451 $forms['contact_mail_user'] = array( 2452 'title' => t('User contact form'), 2453 ); 2454 return $forms; 2455 } 2456 2457 /** 2458 * Implements hook_mollom_form_info(). 2459 */ 2460 function contact_mollom_form_info($form_id) { 2461 switch ($form_id) { 2462 case 'contact_mail_page': 2463 $form_info = array( 2464 'mode' => MOLLOM_MODE_ANALYSIS, 2465 'bypass access' => array('administer site-wide contact form'), 2466 'mail ids' => array('contact_page_mail'), 2467 'elements' => array( 2468 'subject' => t('Subject'), 2469 'message' => t('Message'), 2470 ), 2471 'mapping' => array( 2472 'post_title' => 'subject', 2473 'author_name' => 'name', 2474 'author_mail' => 'mail', 2475 ), 2476 ); 2477 return $form_info; 2478 2479 case 'contact_mail_user': 2480 $form_info = array( 2481 'mode' => MOLLOM_MODE_ANALYSIS, 2482 'bypass access' => array('administer users'), 2483 'mail ids' => array('contact_user_mail'), 2484 'elements' => array( 2485 'subject' => t('Subject'), 2486 'message' => t('Message'), 2487 ), 2488 'mapping' => array( 2489 'post_title' => 'subject', 2490 ), 2491 ); 2492 return $form_info; 2493 } 2494 } 2495 2496 /** 2497 * @} End of "name mollom_contact". 2498 */
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |