/** * Form validation handler to perform textual analysis on submitted form values. */ public static function validateAnalysis(&$form, FormState $form_state) { if (!static::shouldValidate($form, $form_state)) { return; } /** @var \Drupal\mollom\Entity\Form $mollom_form */ $mollom = $form_state->getValue('mollom'); if (!$mollom['require_analysis']) { return FALSE; } // Perform textual analysis. $all_data = self::extractMollomValues($form_state->cleanValues(), $mollom['enabled_fields'], $mollom['mapping']); // Cancel processing upon invalid UTF-8 data. if ($all_data === FALSE) { return FALSE; } $data = $all_data; // Remove postId property; only used by submitForm(). if (isset($data['postId'])) { unset($data['postId']); } $contentId = isset($mollom['contentId']) ? $mollom['contentId'] : NULL; if (!empty($contentId)) { $data['id'] = $contentId; } if (is_array($mollom['checks'])) { $data['checks'] = $mollom['checks']; } $data['strictness'] = $mollom['strictness']; if (isset($mollom['type'])) { $data['type'] = $mollom['type']; } if (in_array('spam', $data['checks']) && $mollom['unsure'] == 'binary') { $data['unsure'] = 0; } // Allow modules to alter data sent. \Drupal::moduleHandler()->alter('mollom_content', $data); /** @var \Drupal\mollom\API\DrupalClient $mollom */ $mollom_service = \Drupal::service('mollom.client'); $result = $mollom_service->checkContent($data); // Use all available data properties for log messages below. $data += $all_data; // Trigger global fallback behavior if there is a unexpected result. if (!is_array($result) || !isset($result['id'])) { return MollomUtilities::handleFallback(); } // Set form values accordingly. Do not overwrite the entity ID. // @todo Rename 'id' to 'entity_id'. $result['contentId'] = $result['id']; unset($result['id']); // Store the response returned by Mollom. $form_state->setValue(array('mollom', 'response', 'content'), $result); $form_state->setValue('mollom', array_merge($mollom, $result)); // Ensure the latest content ID is output as value. // form_set_value() is effectless, as this is not a element-level but a // form-level validation handler. $form['mollom']['contentId']['#value'] = $result['contentId']; // Prepare watchdog message teaser text. $teaser = '--'; if (isset($data['postTitle'])) { $teaser = Unicode::truncate(strip_tags($data['postTitle']), 40); } elseif (isset($data['postBody'])) { $teaser = Unicode::truncate(strip_tags($data['postBody']), 40); } // Handle the profanity check result. if (isset($result['profanityScore']) && $result['profanityScore'] >= 0.5) { if ($mollom['discard']) { $form_state->setError($form, t('Your submission has triggered the profanity filter and will not be accepted until the inappropriate language is removed.')); } else { $form_state->setValue(['mollom', 'require_moderation'], TRUE); } Logger::addMessage(array('message' => 'Profanity: %teaser', 'arguments' => array('%teaser' => $teaser))); } // Handle the spam check result. // The Mollom API takes over state tracking for each content ID/session. The // spamClassification will usually turn into 'ham' after solving a CAPTCHA. // It may also change to 'spam', if the user replaced the values with very // spammy content. In any case, we always do what we are told to do. $form_state->setValue(['mollom', 'require_captcha'], FALSE); $form['mollom']['captcha']['#access'] = FALSE; if (isset($result['spamClassification'])) { switch ($result['spamClassification']) { case 'ham': $message = SafeMarkup::format('Ham: %teaser', array('%teaser' => $teaser)); \Drupal::logger('mollom')->notice($message); break; case 'spam': if ($mollom['discard']) { $form_state->setError($form, t('Your submission has triggered the spam filter and will not be accepted. @fp_message', array('@fp_message' => MollomUtilities::formatFalsePositiveMessage($form_state, $data)))); } else { $form_state->setValue(array('mollom', 'require_moderation'), TRUE); } $message = SafeMarkup::format('Spam: %teaser', array('%teaser' => $teaser)); \Drupal::logger('mollom')->notice($message); break; case 'unsure': if ($mollom['unsure'] == 'moderate') { $form_state->setValue(array('mollom', 'require_moderation'), TRUE); } else { $form_state->setValue(['mollom', 'captcha_response_id'], NULL); $form['mollom']['captcha_response_id']['#value'] = NULL; $form_state->setValue(array('mollom', 'require_captcha'), TRUE); // Require a new CAPTCHA and throw an error. $had_captcha = $form_state->get('mollom_had_captcha'); $form_state->setCached(FALSE); // Set the CAPTCHA type required indicator. $form_state->setValue(array('mollom', 'captcha_required'), $mollom['captcha_type']); $form['mollom']['captcha_required']['#value'] = $mollom['captcha_type']; $form['mollom']['captcha']['#access'] = TRUE; if (!empty($had_captcha)) { $form_state->setErrorByName('mollom][captcha', t('The word verification was not completed correctly. Please complete this new word verification and try again. @fp_message', array('@fp_message' => MollomUtilities::formatFalsePositiveMessage($form_state, $data)))); } else { $form_state->setErrorByName('mollom][captcha', t('To complete this form, please complete the word verification.')); } } $message = SafeMarkup::format('Unsure: %teaser', array('%teaser' => $teaser)); \Drupal::logger('mollom')->notice($message); break; case 'unknown': default: // If we end up here, Mollom responded with a unknown spamClassification. // Normally, this should not happen, but if it does, log it. As there // could be multiple reasons for this, it is not safe to trigger the // fallback mode. $message = SafeMarkup::format('Unknown: %teaser', array('%teaser' => $teaser)); \Drupal::logger('mollom')->notice($message); break; } } }
/** * Assert any watchdog messages based on their severity. * * This function can be (repeatedly) invoked to assert new watchdog messages. * All watchdog messages with a higher severity than RfcLogLevel::NOTICE are * considered as "severe". * * @param $max_severity * (optional) A maximum watchdog severity level message constant that log * messages must have to pass the assertion. All messages with a higher * severity will fail. Defaults to RfcLogLevel::NOTICE. If a severity level * higher than RfcLogLevel::NOTICE is passed, then at least one severe message * is expected. */ protected function assertMollomWatchdogMessages($max_severity = RfcLogLevel::NOTICE) { // Ensure that all messages have been written before attempting to verify // them. Actions executed within the test class may lead to log messages, // but those get only logged when hook_exit() is triggered. // mollom.module may not be installed by a test and thus not loaded yet. //drupal_load('module', 'mollom'); Logger::writeLog(); $database = \Drupal::database(); module_load_include('inc', 'dblog', 'dblog.admin'); $this->messages = array(); $query = $database->select('watchdog', 'w')->fields('w')->orderBy('w.timestamp', 'ASC'); // The comparison logic applied in this function is a bit confusing, since // the values of watchdog severity level constants defined by RFC 3164 are // negated to their actual "severity level" meaning: // RfcLogLevel::EMERGENCY is 0, RfcLogLevel::NOTICE is 5, RfcLogLevel::DEBUG is 7. $fail_expected = $max_severity < RfcLogLevel::NOTICE; $had_severe_message = FALSE; foreach ($query->execute() as $row) { $this->messages[$row->wid] = $row; // Only messages with a maximum severity of $max_severity or less severe // messages must pass. More severe messages need to fail. See note about // severity level constant values above. $output = $this->formatMessage($row); if ($row->severity >= $max_severity) { // Visually separate debug log messages from other messages. if ($row->severity == RfcLogLevel::DEBUG) { $this->error($output, 'User notice'); } else { $this->pass(Html::escape($row->type) . ' (' . $row->severity . '): ' . $output, t('Watchdog')); } } else { $this->fail(Html::escape($row->type) . ' (' . $row->severity . '): ' . $output, t('Watchdog')); } // In case a severe message is expected, non-severe messages always pass, // since we would trigger a false positive test failure otherwise. // However, in order to actually assert the expectation, there must have // been at least one severe log message. $had_severe_message = $had_severe_message || $row->severity < RfcLogLevel::NOTICE; } // Assert that there was a severe message, in case we expected one. if ($fail_expected && !$had_severe_message) { $this->fail(t('Severe log message was found.'), t('Watchdog')); } // Delete processed watchdog messages. if (!empty($this->messages)) { $seen_ids = array_keys($this->messages); $database->delete('watchdog')->condition('wid', $seen_ids, 'IN')->execute(); } }
/** * Returns the (last known) status of the configured Mollom API keys. * * @param bool $force * (optional) Boolean whether to ignore the cached state and re-check. * Defaults to FALSE. * @param bool $update * (optional) Whether to update Mollom with locally stored configuration. * Defaults to FALSE. * * @return array * An associative array describing the current status of the module: * - isConfigured: Boolean whether Mollom API keys have been configured. * - isVerified: Boolean whether Mollom API keys have been verified. * - response: The response error code of the API verification request. * - ...: The full site resource, as returned by the Mollom API. * * @see mollom_requirements() */ public static function getAPIKeyStatus($force = FALSE, $update = FALSE) { $testing_mode = (int) \Drupal::config('mollom.settings')->get('test_mode.enabled'); /* $static_cache = &drupal_static(__FUNCTION__, array()); $status = &$static_cache[$testing_mode]; $drupal_cache = \Drupal::cache(); $cid = 'mollom_status:' . $testing_mode; $expire_valid = 86400; // once per day $expire_invalid = 3600; // once per hour // Look for cached status. if (!$force) { if (isset($status)) { return $status; } else if ($cache = $drupal_cache->get($cid)) { return $cache->data; } }*/ // Re-check configuration status. /** @var \Drupal\mollom\API\DrupalClient $mollom */ $mollom = \Drupal::service('mollom.client'); $status = array('isConfigured' => FALSE, 'isVerified' => FALSE, 'isTesting' => (bool) $testing_mode, 'response' => NULL, 'publicKey' => $mollom->loadConfiguration('publicKey'), 'privateKey' => $mollom->loadConfiguration('privateKey'), 'expectedLanguages' => $mollom->loadConfiguration('expectedLanguages')); $status['isConfigured'] = !empty($status['publicKey']) && !empty($status['privateKey']); $status['expectedLanguages'] = is_array($status['expectedLanguages']) ? array_values($status['expectedLanguages']) : []; if ($testing_mode || $status['isConfigured']) { $old_status = $status; $data = array(); if ($update) { // Ensure to use the most current API keys (might have been changed). $mollom->publicKey = $status['publicKey']; $mollom->privateKey = $status['privateKey']; $data += array('expectedLanguages' => $status['expectedLanguages']); } $data += $mollom->getClientInformation(); $response = $mollom->updateSite($data); if (is_array($response) && $mollom->lastResponseCode === TRUE) { $status = array_merge($status, $response); $status['isVerified'] = TRUE; Logger::addMessage(array('message' => 'API keys are valid.'), RfcLogLevel::INFO); // Unless we just updated, update local configuration with remote. if ($update) { if ($old_status['expectedLanguages'] != $status['expectedLanguages']) { $mollom->saveConfiguration('expectedLanguages', is_array($status['expectedLanguages']) ? $status['expectedLanguages'] : explode(',', $status['expectedLanguages'])); } } } elseif ($response === $mollom::AUTH_ERROR) { $status['response'] = $response; Logger::addMessage(array('message' => 'Invalid API keys.'), RfcLogLevel::ERROR); } elseif ($response === $mollom::REQUEST_ERROR) { $status['response'] = $response; Logger::addMessage(array('message' => 'Invalid client configuration.'), RfcLogLevel::ERROR); } else { $status['response'] = $response; // A NETWORK_ERROR and other possible responses may be caused by the // client-side environment, but also by Mollom service downtimes. Try to // recover as soon as possible. $expire_invalid = 60 * 5; Logger::addMessage(array('message' => 'API keys could not be verified.'), RfcLogLevel::ERROR); } } //$drupal_cache->set($cid, $status, $status['isVerified'] === TRUE ? $expire_valid : $expire_invalid); return $status; }
/** * Implements Mollom::request(). */ protected function request($method, $server, $path, $query = NULL, array $headers = array()) { $options = array('timeout' => $this->requestTimeout); if (isset($query)) { if ($method === 'GET') { $path .= '?' . $query; } else { $options['body'] = $query; } } $request = new Request($method, $server . '/' . $path, $headers); try { $response = $this->client->send($request, $options); } catch (\Exception $e) { //Logger::addMessage(array('message' => 'Response error: <pre>' . print_r($e, TRUE) . '</pre>')); if ($e instanceof ClientException) { $mollom_response = array('code' => $e->getCode(), 'message' => $e->getResponse()->getReasonPhrase(), 'headers' => $e->getResponse()->getHeaders(), 'body' => $e->getResponse()->getBody()); } else { Logger::addMessage(array('message' => 'failed to connect. Message !message', 'arguments' => array('!message' => $e->getMessage())), RfcLogLevel::ERROR); return (object) array('code' => '0', 'message' => $e->getMessage(), 'headers' => array(), 'body' => ''); } } if (empty($mollom_response)) { $mollom_response = array('code' => $response->getStatusCode(), 'message' => $response->getStatusCode() >= 200 && $response->getStatusCode() < 300 ? $response->getReasonPhrase() : NULL, 'headers' => $response->getHeaders(), 'body' => $response->getBody()); } // Convert headers to expected and consistent format. $headers = array(); foreach ($mollom_response['headers'] as $key => $header) { $headers[Unicode::strtolower($key)] = $header[0]; } $mollom_response['headers'] = $headers; return (object) $mollom_response; }
/** * Send feedback to Mollom. * * @param $data * A Mollom data record containing one or both of: * - contentId: The content ID to send feedback for. * - captchaId: The CAPTCHA ID to send feedback for. * @param $reason * The feedback to send, one of 'spam', 'profanity', 'quality', 'unwanted', * 'approve'. * @param $type * The type of feedback, one of 'moderate' or 'flag'. * @param $source * An optional single word string identifier for the user interface source. * This is tracked along with the feedback to provide a more complete picture * of how feedback is used and submitted on the site. */ protected static function sendFeedbackToMollom($data, $reason = 'spam', $type = 'moderate', $source = NULL) { $params = array(); $current_user = \Drupal::currentUser(); if (!empty($data->captchaId)) { $params['captchaId'] = $data->captchaId; $resource = 'CAPTCHA'; $id = $data->captchaId; } // In case we also have a contentId, also pass that, and override $resource // and $id for the log message. if (!empty($data->contentId)) { $params['contentId'] = $data->contentId; $resource = 'content'; $id = $data->contentId; } if (!isset($id)) { return FALSE; } $params += array('reason' => $reason, 'type' => $type, 'authorIp' => \Drupal::request()->getClientIp()); if (!empty($source)) { $params['source'] = $source; } if ($current_user->isAuthenticated()) { $params['authorId'] = $current_user->id(); } $result = \Drupal::service('mollom.client')->sendFeedback($params); Logger::addMessage(array('message' => 'Reported %feedback for @resource %id from %source - %type.', 'arguments' => array('%type' => $type, '%feedback' => $reason, '@resource' => $resource, '%id' => $id, '%source' => $source))); return $result; }
/** * Implements after all other processing. */ function onTerminate() { Logger::writeLog(); }