mollom_test_server.module

Tracking 7.x-2.x branch
  1. drupal
    1. 6 contributions/mollom/tests/mollom_test_server.module
    2. 7 contributions/mollom/tests/mollom_test_server.module

Testing functionality for Mollom module.

Functions & methods

NameDescription
mollom_test_server_check_captchaAPI callback for mollom.checkCaptcha to validate a CAPTCHA response.
mollom_test_server_check_contentAPI callback for mollom.checkContent to perform textual analysis.
mollom_test_server_check_content_blacklistChecks a string against blacklisted terms.
mollom_test_server_get_captchaAPI callback for mollom.getImageCaptcha to fetch a CATPCHA image.
mollom_test_server_menuImplements hook_menu().
mollom_test_server_rest_add_xml
mollom_test_server_rest_blacklistREST callback for Blacklist API.
mollom_test_server_rest_captchaREST callback to for CAPTCHAs.
mollom_test_server_rest_contentREST callback for mollom.checkContent to perform textual analysis.
mollom_test_server_rest_deliverDelivery callback for REST API endpoints.
mollom_test_server_rest_get_auth_headerReturns the parsed HTTP Authorization request header as an array.
mollom_test_server_rest_get_parametersReturns HTTP request query parameters for the current request.
mollom_test_server_rest_send_feedbackREST callback for mollom.sendFeedback to send feedback for a moderated post.
mollom_test_server_rest_siteREST callback for CRUD site operations.
mollom_test_server_rest_validate_authReturns whether the OAuth request signature is valid.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Testing functionality for Mollom module.
  5. */
  6. /**
  7. * Implements hook_menu().
  8. */
  9. function mollom_test_server_menu() {
  10. $path = 'mollom-test/rest/v1';
  11. $base_args = count(explode('/', $path)) - 1;
  12. // @todo Consider to use a generic page callback, passing arg(3), the resource
  13. // type, and optionally arg(4), the resource, as argument. This would allow
  14. // us to use PHP Exceptions to throw different status codes and errors. Make
  15. // that page callback dynamically switch the delivery callback (for JSON).
  16. $base = array(
  17. 'access callback' => TRUE,
  18. 'type' => MENU_CALLBACK,
  19. 'delivery callback' => 'mollom_test_server_rest_deliver',
  20. );
  21. $items[$path . '/site'] = $base + array(
  22. 'page callback' => 'mollom_test_server_rest_site',
  23. );
  24. $items[$path . '/content'] = $base + array(
  25. 'page callback' => 'mollom_test_server_rest_content',
  26. );
  27. $items[$path . '/captcha'] = $base + array(
  28. 'page callback' => 'mollom_test_server_rest_captcha',
  29. );
  30. $items[$path . '/feedback'] = $base + array(
  31. 'page callback' => 'mollom_test_server_rest_send_feedback',
  32. );
  33. $items[$path . '/blacklist/%'] = $base + array(
  34. 'page callback' => 'mollom_test_server_rest_blacklist',
  35. 'page arguments' => array($base_args + 2),
  36. );
  37. // @todo Whitelist endpoints.
  38. return $items;
  39. }
  40. /**
  41. * Returns HTTP request query parameters for the current request.
  42. *
  43. * @see Mollom::httpBuildQuery()
  44. * @see http://php.net/manual/en/wrappers.php.php
  45. */
  46. function mollom_test_server_rest_get_parameters() {
  47. $data = &drupal_static(__FUNCTION__);
  48. if (isset($data)) {
  49. return $data;
  50. }
  51. if ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') {
  52. $data = Mollom::httpParseQuery($_SERVER['QUERY_STRING']);
  53. // Remove $_GET['q'].
  54. unset($data['q']);
  55. }
  56. elseif ($_SERVER['REQUEST_METHOD'] == 'POST' || $_SERVER['REQUEST_METHOD'] == 'PUT') {
  57. $data = Mollom::httpParseQuery(file_get_contents('php://input'));
  58. }
  59. return $data;
  60. }
  61. /**
  62. * Returns the parsed HTTP Authorization request header as an array.
  63. */
  64. function mollom_test_server_rest_get_auth_header() {
  65. $header = &drupal_static(__FUNCTION__);
  66. if (isset($header)) {
  67. return $header;
  68. }
  69. $header = array();
  70. if (function_exists('apache_request_headers')) {
  71. $headers = apache_request_headers();
  72. if (isset($headers['Authorization'])) {
  73. $input = $headers['Authorization'];
  74. }
  75. }
  76. elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
  77. $input = $_SERVER['HTTP_AUTHORIZATION'];
  78. }
  79. if (isset($input)) {
  80. preg_match_all('@([^, =]+)="([^"]*)"@', $input, $header);
  81. $header = array_combine($header[1], $header[2]);
  82. }
  83. return $header;
  84. }
  85. /**
  86. * Delivery callback for REST API endpoints.
  87. */
  88. function mollom_test_server_rest_deliver($page_callback_result) {
  89. drupal_add_http_header('Content-Type', 'application/xml; charset=utf-8');
  90. $xml = new DOMDocument('1.0', 'utf-8');
  91. $element = $xml->createElement('response');
  92. // Append status response parameters.
  93. // @todo Add support for custom codes (redirect/refresh) + error messages.
  94. $code = 200;
  95. if (!is_array($page_callback_result) && $page_callback_result !== TRUE) {
  96. switch ($page_callback_result) {
  97. case MENU_NOT_FOUND:
  98. $code = 404;
  99. $message = 'Not found';
  100. break;
  101. case Mollom::AUTH_ERROR:
  102. $code = 401;
  103. $message = 'Unauthorized';
  104. break;
  105. default:
  106. $code = 400;
  107. $message = 'Bad request';
  108. break;
  109. }
  110. }
  111. $status = array(
  112. 'code' => $code,
  113. );
  114. if (isset($message)) {
  115. $status['message'] = $message;
  116. }
  117. mollom_test_server_rest_add_xml($xml, $element, $status);
  118. // Append other response parameters.
  119. if (is_array($page_callback_result)) {
  120. mollom_test_server_rest_add_xml($xml, $element, $page_callback_result);
  121. }
  122. $xml->appendChild($element);
  123. print $xml->saveXML();
  124. // Perform end-of-request tasks.
  125. drupal_page_footer();
  126. }
  127. function mollom_test_server_rest_add_xml(DOMDocument $doc, DOMNode $parent, $data, $key = NULL) {
  128. if (is_scalar($data)) {
  129. // Mollom REST API always uses integers instead of Booleans due to varying
  130. // implementations of JSON protocol across client platforms/frameworks.
  131. if (is_bool($data)) {
  132. $data = (int) $data;
  133. }
  134. $element = $doc->createTextNode($data);
  135. $parent->appendChild($element);
  136. }
  137. else {
  138. foreach ($data as $property => $value) {
  139. $key = (is_numeric($property) ? 'item' : $property);
  140. $element = $doc->createElement($key);
  141. $parent->appendChild($element);
  142. mollom_test_server_rest_add_xml($doc, $element, $value, $key);
  143. }
  144. }
  145. }
  146. /**
  147. * Returns whether the OAuth request signature is valid.
  148. */
  149. function mollom_test_server_rest_validate_auth() {
  150. $data = mollom_test_server_rest_get_parameters();
  151. $header = mollom_test_server_rest_get_auth_header();
  152. $sites = variable_get('mollom_test_server_site', array());
  153. $sent_signature = $header['oauth_signature'];
  154. unset($header['oauth_signature']);
  155. $base_string = implode('&', array(
  156. $_SERVER['REQUEST_METHOD'],
  157. Mollom::rawurlencode($GLOBALS['base_url'] . '/' . $_GET['q']),
  158. Mollom::rawurlencode(Mollom::httpBuildQuery($data + $header)),
  159. ));
  160. $time = $header['oauth_timestamp'];
  161. $nonce = $header['oauth_nonce'];
  162. if (!isset($sites[$header['oauth_consumer_key']]['privateKey'])) {
  163. return FALSE;
  164. }
  165. $privateKey = $sites[$header['oauth_consumer_key']]['privateKey'];
  166. $key = Mollom::rawurlencode($privateKey) . '&' . '';
  167. $signature = rawurlencode(base64_encode(hash_hmac('sha1', $base_string, $key, TRUE)));
  168. return $signature === $sent_signature;
  169. }
  170. /**
  171. * REST callback for CRUD site operations.
  172. *
  173. * @param $publicKey
  174. * (optional) The public key of a site.
  175. * @param $delete
  176. * (optional) Whether to delete the site with $publicKey.
  177. */
  178. function mollom_test_server_rest_site($publicKey = NULL, $delete = FALSE) {
  179. $data = mollom_test_server_rest_get_parameters();
  180. $bin = 'mollom_test_server_site';
  181. $sites = variable_get($bin, array());
  182. if (isset($publicKey)) {
  183. // Validate authentication.
  184. if (!mollom_test_server_rest_validate_auth()) {
  185. return Mollom::AUTH_ERROR;
  186. }
  187. // Check whether publicKey exists.
  188. if (!isset($sites[$publicKey])) {
  189. return MENU_NOT_FOUND;
  190. }
  191. }
  192. if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  193. // Return existing site.
  194. if (isset($publicKey)) {
  195. $response = $sites[$publicKey];
  196. }
  197. // Return list of existing sites.
  198. else {
  199. $response = array(
  200. 'list' => array_values($sites),
  201. 'listCount' => count($sites),
  202. 'listOffset' => 0,
  203. 'listTotal' => count($sites),
  204. );
  205. return $response;
  206. }
  207. }
  208. else {
  209. // Update site.
  210. if (isset($publicKey) && !$delete) {
  211. $sites[$publicKey] = $data + $sites[$publicKey];
  212. variable_set($bin, $sites);
  213. $response = $sites[$publicKey];
  214. }
  215. // Create new site.
  216. // Authentication is ignored in this case.
  217. elseif (!$delete) {
  218. $data['publicKey'] = $publicKey = md5(rand() . REQUEST_TIME);
  219. $data['privateKey'] = $privateKey = md5(rand() . REQUEST_TIME);
  220. // Apply default values.
  221. $data += array(
  222. 'url' => '',
  223. 'email' => '',
  224. 'languages' => array(),
  225. 'subscriptionType' => 0, // Mollom Free.
  226. // Client version info is not defined by default.
  227. );
  228. $sites[$publicKey] = $data;
  229. variable_set($bin, $sites);
  230. $response = $data;
  231. }
  232. // Delete site.
  233. else {
  234. unset($sites[$publicKey]);
  235. variable_set($bin, $sites);
  236. return TRUE;
  237. }
  238. }
  239. return array('site' => $response);
  240. }
  241. /**
  242. * REST callback for mollom.checkContent to perform textual analysis.
  243. */
  244. function mollom_test_server_rest_content($contentId = NULL) {
  245. $data = mollom_test_server_rest_get_parameters();
  246. if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  247. // @todo List/read content.
  248. if (empty($contentId)) {
  249. return FALSE;
  250. }
  251. return FALSE;
  252. }
  253. else {
  254. // Content ID in request parameters must match the one in path.
  255. if (isset($data['id']) && $data['id'] != $contentId) {
  256. return FALSE;
  257. }
  258. if (isset($contentId)) {
  259. $data['id'] = $contentId;
  260. }
  261. }
  262. // Default POST: Create or update content and check it.
  263. return array('content' => mollom_test_server_check_content($data));
  264. }
  265. /**
  266. * REST callback to for CAPTCHAs.
  267. */
  268. function mollom_test_server_rest_captcha($captchaId = NULL) {
  269. $data = mollom_test_server_rest_get_parameters();
  270. if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  271. // There is no GET /captcha[/{captchaId}].
  272. return FALSE;
  273. }
  274. else {
  275. // CAPTCHA ID in request parameters must match the one in path.
  276. if (isset($data['id']) && $data['id'] != $captchaId) {
  277. return FALSE;
  278. }
  279. // Verify CAPTCHA.
  280. if (isset($captchaId)) {
  281. $data['id'] = $captchaId;
  282. $response = mollom_test_server_check_captcha($data);
  283. if (!is_array($response)) {
  284. return $response;
  285. }
  286. return array('captcha' => $response);
  287. }
  288. }
  289. // Create a new CAPTCHA resource.
  290. return array('captcha' => mollom_test_server_get_captcha($data));
  291. }
  292. /**
  293. * REST callback for Blacklist API.
  294. *
  295. * @param $public_key
  296. * The public key of a site.
  297. *
  298. * @todo Abstract actual functionality like other REST handlers.
  299. */
  300. function mollom_test_server_rest_blacklist($public_key, $entryId = NULL, $delete = FALSE) {
  301. if (empty($public_key)) {
  302. return FALSE;
  303. }
  304. $data = mollom_test_server_rest_get_parameters();
  305. // Prepare text value.
  306. if (isset($data['value'])) {
  307. $data['value'] = drupal_strtolower(trim($data['value']));
  308. }
  309. $bin = 'mollom_test_server_blacklist_' . $public_key;
  310. $entries = variable_get($bin, array());
  311. if ($_SERVER['REQUEST_METHOD'] == 'GET') {
  312. // List blacklist entries.
  313. if (empty($entryId)) {
  314. $response = array();
  315. // Remove deleted entries (== FALSE).
  316. $entries = array_filter($entries);
  317. $response['list'] = $entries;
  318. // @todo Not required yet.
  319. $response['listCount'] = count($entries);
  320. $response['listOffset'] = 0;
  321. $response['listTotal'] = count($entries);
  322. return $response;
  323. }
  324. // Read a single entry.
  325. else {
  326. // Check whether the entry exists and was not deleted.
  327. if (!empty($entries[$entryId])) {
  328. return array('entry' => $entries[$entryId]);
  329. }
  330. else {
  331. return MENU_NOT_FOUND;
  332. }
  333. }
  334. }
  335. else {
  336. // Update an existing entry.
  337. if (isset($entryId)) {
  338. // Entry ID must match.
  339. if (isset($data['id']) && $data['id'] != $entryId) {
  340. return FALSE;
  341. }
  342. // Check that the entry was not deleted.
  343. if (empty($entries[$entryId])) {
  344. return MENU_NOT_FOUND;
  345. }
  346. // Entry ID cannot be updated.
  347. unset($data['id']);
  348. $entries[$entryId] = $data;
  349. variable_set($bin, $entries);
  350. $response = $data;
  351. $response['id'] = $entryId;
  352. return array('entry' => $response);
  353. }
  354. // Create a new entry.
  355. elseif (!$delete) {
  356. $entryId = max(array_keys($entries)) + 1;
  357. $data['id'] = $entryId;
  358. $entries[$entryId] = $data;
  359. variable_set($bin, $entries);
  360. $response = $data;
  361. return array('entry' => $response);
  362. }
  363. // Delete an existing entry.
  364. else {
  365. // Check that the entry was not deleted already.
  366. if (!empty($entries[$entryId])) {
  367. $entries[$entryId] = FALSE;
  368. variable_set($bin, $entries);
  369. return TRUE;
  370. }
  371. else {
  372. return MENU_NOT_FOUND;
  373. }
  374. }
  375. }
  376. }
  377. /**
  378. * REST callback for mollom.sendFeedback to send feedback for a moderated post.
  379. */
  380. function mollom_test_server_rest_send_feedback() {
  381. $data = mollom_test_server_rest_get_parameters();
  382. // A resource ID is required.
  383. if (empty($data['contentId']) && empty($data['captchaId'])) {
  384. return 400;
  385. }
  386. // The feedback is valid if the supplied reason is one of the supported
  387. // strings. Otherwise, it's a bad request.
  388. $storage = variable_get('mollom_test_server_feedback', array());
  389. $storage[] = $data;
  390. variable_set('mollom_test_server_feedback', $storage);
  391. $result = in_array($data['reason'], array('spam', 'profanity', 'quality', 'unwanted', 'approve', 'delete'));
  392. return $result ? TRUE : 400;
  393. }
  394. /**
  395. * API callback for mollom.checkContent to perform textual analysis.
  396. *
  397. * @todo Add support for 'redirect' and 'refresh' values.
  398. */
  399. function mollom_test_server_check_content($data) {
  400. $response = array();
  401. // If only a single value for checks is passed, it is a string.
  402. if (isset($data['checks']) && is_string($data['checks'])) {
  403. $data['checks'] = array($data['checks']);
  404. }
  405. $header = mollom_test_server_rest_get_auth_header();
  406. $publicKey = $header['oauth_consumer_key'];
  407. // Fetch blacklist.
  408. $blacklist = variable_get('mollom_test_server_blacklist_' . $publicKey, array());
  409. $post = implode('\n', array_intersect_key($data, array('postTitle' => 1, 'postBody' => 1)));
  410. $update = isset($data['stored']);
  411. // Spam filter: Check post_title and post_body for ham, spam, or unsure.
  412. if (!$update && (!isset($data['checks']) || in_array('spam', $data['checks']))) {
  413. $spam = FALSE;
  414. $ham = FALSE;
  415. // 'spam' always has precedence.
  416. if (strpos($post, 'spam') !== FALSE) {
  417. $spam = TRUE;
  418. }
  419. // Otherwise, check for 'ham'.
  420. elseif (strpos($post, 'ham') !== FALSE) {
  421. $ham = TRUE;
  422. }
  423. // Lastly, take a forced 'unsure' into account.
  424. elseif (strpos($post, 'unsure') !== FALSE) {
  425. // Differentiate between binary unsure mode.
  426. if (!isset($data['unsure']) || $data['unsure']) {
  427. $spam = TRUE;
  428. $ham = TRUE;
  429. }
  430. else {
  431. $spam = TRUE;
  432. $ham = FALSE;
  433. }
  434. }
  435. // Check blacklist.
  436. if ($matches = mollom_test_server_check_content_blacklist($post, $blacklist, 'spam')) {
  437. $spam = TRUE;
  438. $ham = FALSE;
  439. $response['reason'] = 'blacklist';
  440. $response['blacklistSpam'] = $matches;
  441. }
  442. if ($spam && $ham) {
  443. $response['spamScore'] = 0.5;
  444. $response['spamClassification'] = 'unsure';
  445. $qualityScore = 0.5;
  446. }
  447. elseif ($spam) {
  448. $response['spamScore'] = 1.0;
  449. $response['spamClassification'] = 'spam';
  450. $qualityScore = 0.0;
  451. }
  452. elseif ($ham) {
  453. $response['spamScore'] = 0.0;
  454. $response['spamClassification'] = 'ham';
  455. $qualityScore = 1.0;
  456. }
  457. else {
  458. $response['spamScore'] = 0.5;
  459. $response['spamClassification'] = 'unsure';
  460. $qualityScore = NULL;
  461. }
  462. // In case a previous spam check was unsure and a CAPTCHA was solved, the
  463. // result is supposed to be ham.
  464. $captcha_sessions = variable_get('mollom_test_server_check_captcha_sessions', array());
  465. if (!empty($data['captchaId']) && !empty($captcha_sessions[$data['captchaId']])) {
  466. $response['spamScore'] = 0.0;
  467. $response['spamClassification'] = 'ham';
  468. }
  469. }
  470. // Quality filter.
  471. if (isset($data['checks']) && in_array('quality', $data['checks'])) {
  472. if (isset($qualityScore)) {
  473. $response['qualityScore'] = $qualityScore;
  474. }
  475. else {
  476. $response['qualityScore'] = 0;
  477. }
  478. }
  479. // Profanity filter.
  480. if (isset($data['checks']) && in_array('profanity', $data['checks'])) {
  481. $profanityScore = 0.0;
  482. if (strpos($post, 'profanity') !== FALSE) {
  483. $profanityScore = 1.0;
  484. }
  485. // Check blacklist.
  486. if ($matches = mollom_test_server_check_content_blacklist($post, $blacklist, 'profanity')) {
  487. $profanityScore = 1.0;
  488. $response['blacklistProfanity'] = $matches;
  489. }
  490. $response['profanityScore'] = $profanityScore;
  491. }
  492. // Language detection.
  493. if (isset($data['checks']) && in_array('language', $data['checks'])) {
  494. $languages = array();
  495. preg_match_all('@\blang-(..)\b@', $post, $matches);
  496. if (empty($matches[1])) {
  497. $languages[] = array(
  498. 'languageCode' => 'zxx',
  499. 'languageScore' => 1.0,
  500. );
  501. }
  502. elseif (count($matches[1]) > 3) {
  503. $languages[] = array(
  504. 'languageCode' => LANGUAGE_NONE,
  505. 'languageScore' => 1.0,
  506. );
  507. }
  508. else {
  509. $languageScore = 1 / count($matches[1]);
  510. foreach ($matches[1] as $language) {
  511. $languages[] = array(
  512. 'languageCode' => $language,
  513. 'languageScore' => $languageScore,
  514. );
  515. }
  516. }
  517. $response['languages'] = $languages;
  518. $response['langDebug'] = $matches;
  519. }
  520. $storage = variable_get('mollom_test_server_content', array());
  521. $contentId = (!empty($data['id']) ? $data['id'] : md5(mt_rand()));
  522. if (isset($storage[$contentId])) {
  523. $storage[$contentId] = array_merge($storage[$contentId], $data);
  524. }
  525. else {
  526. $storage[$contentId] = $data;
  527. }
  528. if ($update) {
  529. $response = array_merge($storage[$contentId], $response);
  530. }
  531. $response['id'] = $contentId;
  532. variable_set('mollom_test_server_content', $storage);
  533. return $response;
  534. }
  535. /**
  536. * Checks a string against blacklisted terms.
  537. */
  538. function mollom_test_server_check_content_blacklist($string, $blacklist, $reason) {
  539. $terms = array();
  540. foreach ($blacklist as $entry) {
  541. if ($entry['reason'] == $reason) {
  542. $term = preg_quote($entry['value']);
  543. if ($entry['match'] == 'exact') {
  544. $term = '\b' . $term . '\b';
  545. }
  546. $terms[] = $term;
  547. }
  548. }
  549. if (!empty($terms)) {
  550. $terms = '/(' . implode('|', $terms) . ')/';
  551. preg_match_all($terms, strtolower($string), $matches);
  552. return $matches[1];
  553. }
  554. return array();
  555. }
  556. /**
  557. * API callback for mollom.getImageCaptcha to fetch a CATPCHA image.
  558. */
  559. function mollom_test_server_get_captcha($data) {
  560. $response = array();
  561. // Return a HTTPS URL if 'ssl' parameter was passed.
  562. $base_url = $GLOBALS['base_url'];
  563. if (!empty($data['ssl'])) {
  564. $base_url = str_replace('http', 'https', $base_url);
  565. }
  566. $response['url'] = $base_url . '/' . drupal_get_path('module', 'mollom') . '/images/powered-by-mollom-2.gif';
  567. $storage = variable_get('mollom_test_server_captcha', array());
  568. $captchaId = (!empty($data['id']) ? $data['id'] : md5(mt_rand()));
  569. $storage[$captchaId] = $data;
  570. $response['id'] = $captchaId;
  571. variable_set('mollom_test_server_captcha', $storage);
  572. return $response;
  573. }
  574. /**
  575. * API callback for mollom.checkCaptcha to validate a CAPTCHA response.
  576. *
  577. * @todo Add support for 'redirect' and 'refresh' values.
  578. */
  579. function mollom_test_server_check_captcha($data) {
  580. $response = array();
  581. if (isset($data['solution']) && $data['solution'] == 'correct') {
  582. $response['solved'] = TRUE;
  583. }
  584. else {
  585. $response['solved'] = FALSE;
  586. $response['reason'] = '';
  587. }
  588. $storage = variable_get('mollom_test_server_captcha', array());
  589. $captchaId = $data['id'];
  590. if (!isset($storage[$captchaId])) {
  591. return MENU_NOT_FOUND;
  592. }
  593. $storage[$captchaId] = $data;
  594. $response['id'] = $captchaId;
  595. variable_set('mollom_test_server_captcha', $storage);
  596. $captcha_sessions = variable_get('mollom_test_server_check_captcha_sessions', array());
  597. $captcha_sessions[$captchaId] = $response['solved'];
  598. variable_set('mollom_test_server_check_captcha_sessions', $captcha_sessions);
  599. return $response;
  600. }