mollom.admin.inc 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. <?php
  2. /**
  3. * @file
  4. * Administrative page callbacks for Mollom module.
  5. */
  6. /**
  7. * Helper function to prepare a list of available languages.
  8. */
  9. function _mollom_supported_languages() {
  10. // D7-: _locale_prepare_predefined_list() removes installed languages, which
  11. // is the exact opposite of what we want.
  12. include_once DRUPAL_ROOT . '/includes/iso.inc';
  13. $predefined = _locale_get_predefined_list();
  14. $supported = array_flip(MOLLOM::$LANGUAGES_SUPPORTED);
  15. $supported = array_combine(array_keys($supported), array_keys($supported));
  16. // Define those mappings that differ between Drupal codes and Mollom codes.
  17. $mapped = array(
  18. 'nb' => 'no',
  19. 'zh-hans' => 'zh-cn',
  20. 'zh-hant' => 'zh-tw',
  21. );
  22. foreach($mapped as $drupal_key => $mollom_key) {
  23. if (isset($supported[$mollom_key])) {
  24. $supported[$drupal_key] = $mollom_key;
  25. unset($supported[$mollom_key]);
  26. }
  27. }
  28. $options = array();
  29. $languages_enabled = language_list();
  30. $installed_languages = array();
  31. // This does assume that all Mollom supported languages are in the predefined
  32. // Drupal list.
  33. foreach ($predefined as $langcode => $language) {
  34. $found = FALSE;
  35. $simplified_code = strtok($langcode, '-');
  36. if (isset($supported[$simplified_code]) && !isset($options[$simplified_code])) {
  37. $options[$supported[$simplified_code]] = t($language[0]);
  38. $found = TRUE;
  39. }
  40. else if (isset($supported[$langcode]) && !isset($options[$langcode])) {
  41. $options[$supported[$langcode]] = t($language[0]);
  42. $found = TRUE;
  43. }
  44. if ($found) {
  45. // Update the list of installed languages that are supported.
  46. if (isset($languages_enabled[$langcode])) {
  47. $installed_languages[$supported[$simplified_code]] = $simplified_code;
  48. }
  49. }
  50. }
  51. // Sort by translated option labels.
  52. asort($options);
  53. // UX: Sort installed languages first.
  54. return array_intersect_key($options, $installed_languages) + $options;
  55. }
  56. /**
  57. * Checks the configuration status on Mollom administration pages.
  58. *
  59. * On all Mollom administration pages, check the module configuration and
  60. * display the corresponding requirements error, if invalid.
  61. */
  62. function mollom_admin_site_status($force = FALSE, $update = FALSE) {
  63. $status = _mollom_status($force, $update);
  64. if (empty($_POST) && !$status['isVerified']) {
  65. // Fetch and display requirements error message, without re-checking.
  66. module_load_install('mollom');
  67. $requirements = mollom_requirements('runtime', FALSE);
  68. if (isset($requirements['mollom']['description'])) {
  69. drupal_set_message($requirements['mollom']['description'], 'error');
  70. }
  71. }
  72. return $status;
  73. }
  74. /**
  75. * Menu callback; Displays a list of forms configured for Mollom.
  76. */
  77. function mollom_admin_form_list() {
  78. mollom_admin_site_status();
  79. _mollom_testing_mode_warning();
  80. // Reset the cached list of protected forms.
  81. mollom_form_cache(TRUE);
  82. $modes = array(
  83. MOLLOM_MODE_ANALYSIS => t('Text analysis'),
  84. MOLLOM_MODE_CAPTCHA => t('CAPTCHA'),
  85. );
  86. $header = array(
  87. t('Form'),
  88. t('Protection mode'),
  89. array('data' => t('Operations'), 'colspan' => 2),
  90. );
  91. $result = db_query('SELECT form_id FROM {mollom_form}')->fetchCol();
  92. $forms = array();
  93. $module_info = system_get_info('module');
  94. foreach ($result as $form_id) {
  95. $forms[$form_id] = mollom_form_load($form_id);
  96. // system_get_info() only supports enabled modules. Default to the module's
  97. // machine name in case it is disabled.
  98. $module = $forms[$form_id]['module'];
  99. if (!isset($module_info[$module])) {
  100. $module_info[$module]['name'] = $module;
  101. }
  102. $forms[$form_id]['title'] = t('!module: !form-title', array(
  103. '!form-title' => $forms[$form_id]['title'],
  104. '!module' => t($module_info[$module]['name']),
  105. ));
  106. }
  107. // Sort forms by title (including module name prefix).
  108. uasort($forms, 'drupal_sort_title');
  109. $rows = array();
  110. foreach ($forms as $form_id => $mollom_form) {
  111. $row_attributes = array();
  112. $row = array();
  113. $row[] = $mollom_form['title'];
  114. if (isset($modes[$mollom_form['mode']])) {
  115. if ($mollom_form['mode'] == MOLLOM_MODE_ANALYSIS) {
  116. // @todo Output unsure mode in summary listing.
  117. $row[] = t('!protection-mode (@discard)', array(
  118. '!protection-mode' => $modes[$mollom_form['mode']],
  119. '@discard' => $mollom_form['discard'] ? t('discard') : t('retain'),
  120. ));
  121. }
  122. else {
  123. $row[] = $modes[$mollom_form['mode']];
  124. }
  125. }
  126. else {
  127. $row[] = t('- orphan -');
  128. }
  129. if (empty($mollom_form['orphan'])) {
  130. $row[] = array('data' => array(
  131. '#type' => 'link',
  132. '#title' => t('Configure'),
  133. '#href' => 'admin/config/content/mollom/manage/' . $form_id,
  134. ));
  135. }
  136. else {
  137. $row[] = '';
  138. $row_attributes['class'] = array('error');
  139. drupal_set_message(t("%module module's %form_id form no longer exists.", array(
  140. '%form_id' => $form_id,
  141. '%module' => isset($module_info[$mollom_form['module']]['name']) ? t($module_info[$mollom_form['module']]['name']) : $mollom_form['module'],
  142. )), 'warning');
  143. }
  144. $row[] = array('data' => array(
  145. '#type' => 'link',
  146. '#title' => t('Unprotect'),
  147. '#href' => 'admin/config/content/mollom/unprotect/' . $form_id,
  148. ));
  149. $rows[] = $row_attributes + array('data' => $row);
  150. }
  151. $build['forms'] = array(
  152. '#theme' => 'table',
  153. '#header' => $header,
  154. '#rows' => $rows,
  155. '#empty' => l(t('Add form'), 'admin/config/content/mollom/add'),
  156. );
  157. return $build;
  158. }
  159. /**
  160. * Return registered forms as an array suitable for a 'checkboxes' form element #options property.
  161. */
  162. function mollom_admin_form_options() {
  163. // Retrieve all registered forms.
  164. $form_list = mollom_form_list();
  165. // Remove already configured form ids.
  166. $result = db_query('SELECT form_id FROM {mollom_form}')->fetchCol();
  167. foreach ($result as $form_id) {
  168. unset($form_list[$form_id]);
  169. }
  170. // If all registered forms are configured already, output a message, and
  171. // redirect the user back to overview.
  172. if (empty($form_list)) {
  173. drupal_set_message(t('All available forms are protected already.'));
  174. drupal_goto('admin/config/content/mollom');
  175. }
  176. // Load module information.
  177. $module_info = system_get_info('module');
  178. // Transform form information into an associative array suitable for #options.
  179. $options = array();
  180. foreach ($form_list as $form_id => $info) {
  181. // system_get_info() only supports enabled modules. Default to the module's
  182. // machine name in case it is disabled.
  183. $module = $info['module'];
  184. if (!isset($module_info[$module])) {
  185. $module_info[$module]['name'] = $module;
  186. }
  187. $options[$form_id] = t('!module: !form-title', array(
  188. '!form-title' => $info['title'],
  189. '!module' => t($module_info[$module]['name']),
  190. ));
  191. }
  192. // Sort form options by title.
  193. asort($options);
  194. return $options;
  195. }
  196. /**
  197. * Form builder; Configure Mollom protection for a form.
  198. */
  199. function mollom_admin_configure_form($form, &$form_state, $mollom_form = NULL) {
  200. // If no $mollom_form was passed, then we are adding a new form configuration.
  201. if (!isset($mollom_form)) {
  202. if (!isset($form_state['storage']['mollom_form'])) {
  203. $form_state['storage']['step'] = 'select';
  204. }
  205. else {
  206. $form_state['storage']['step'] = 'configure';
  207. $mollom_form = $form_state['storage']['mollom_form'];
  208. }
  209. }
  210. // When adding a new form configuration, passing form_id via path argument.
  211. elseif (is_string($mollom_form)) {
  212. $mollom_form = mollom_form_new($mollom_form);
  213. $form_state['storage']['step'] = 'configure';
  214. $form_state['storage']['mollom_form'] = $mollom_form;
  215. }
  216. // Otherwise, we are editing an existing form configuration.
  217. else {
  218. $form_state['storage']['step'] = 'configure';
  219. $form_state['storage']['mollom_form'] = $mollom_form;
  220. }
  221. $form['#tree'] = TRUE;
  222. $form['actions'] = array(
  223. '#type' => 'actions',
  224. );
  225. $form['#attached'] = array(
  226. 'js' => array(
  227. drupal_get_path('module', 'mollom') . '/mollom.admin.js',
  228. ),
  229. );
  230. switch ($form_state['storage']['step']) {
  231. case 'select':
  232. $form['mollom']['form_id'] = array(
  233. '#type' => 'select',
  234. '#title' => t('Form'),
  235. '#options' => mollom_admin_form_options(),
  236. '#required' => TRUE,
  237. );
  238. $form['actions']['next'] = array(
  239. '#type' => 'submit',
  240. '#value' => t('Next'),
  241. '#submit' => array('mollom_admin_configure_form_next_submit'),
  242. );
  243. break;
  244. case 'configure':
  245. drupal_set_title(t('Configure %form-title protection', array('%form-title' => $mollom_form['title'])), PASS_THROUGH);
  246. $recommended = t('recommended');
  247. $form['mollom']['form_id'] = array(
  248. '#type' => 'value',
  249. '#value' => $mollom_form['form_id'],
  250. );
  251. $modes = array();
  252. $modes[MOLLOM_MODE_ANALYSIS] = t('!option <em>(!recommended)</em>', array(
  253. '!option' => t('Text analysis'),
  254. '!recommended' => $recommended,
  255. ));
  256. $modes[MOLLOM_MODE_CAPTCHA] = t('CAPTCHA only');
  257. $form['mollom']['mode'] = array(
  258. '#type' => 'radios',
  259. '#title' => t('Protection mode'),
  260. '#options' => $modes,
  261. '#default_value' => isset($mollom_form['mode']) ? $mollom_form['mode'] : key($modes),
  262. );
  263. $form['mollom']['mode'][MOLLOM_MODE_ANALYSIS] = array(
  264. '#description' => t('Mollom will analyze the post and will only show a CAPTCHA when it is unsure.'),
  265. );
  266. $form['mollom']['mode'][MOLLOM_MODE_CAPTCHA] = array(
  267. '#description' => t('A CAPTCHA will be shown for every post. Only choose this if there are too few text fields to analyze.'),
  268. );
  269. $form['mollom']['mode'][MOLLOM_MODE_CAPTCHA]['#description'] .= '<br />' . t('Note: Page caching is disabled on all pages containing a CAPTCHA-only protected form.');
  270. $all_permissions = array();
  271. foreach (module_implements('permission') as $module) {
  272. if ($module_permissions = module_invoke($module, 'permission')) {
  273. foreach ($module_permissions as &$info) {
  274. $info += array('module' => $module);
  275. }
  276. $all_permissions += $module_permissions;
  277. }
  278. }
  279. // Prepend Mollom's global permission to the list.
  280. array_unshift($mollom_form['bypass access'], 'bypass mollom protection');
  281. $permissions = array();
  282. foreach ($mollom_form['bypass access'] as $permission) {
  283. // @todo D7: Array keys are used as CSS class for the link list item,
  284. // but are not sanitized: http://drupal.org/node/98696
  285. $permissions[drupal_html_class($permission)] = array(
  286. 'title' => $all_permissions[$permission]['title'],
  287. 'href' => 'admin/people/permissions',
  288. 'fragment' => 'module-' . $all_permissions[$permission]['module'],
  289. 'html' => TRUE,
  290. );
  291. }
  292. $form['mollom']['mode']['#description'] = t('The protection is omitted for users having any of the permissions: !permission-list', array(
  293. '!permission-list' => theme('links', array(
  294. 'links' => $permissions,
  295. // @todo D7: Something went entirely wrong: system.menus.css makes ANY
  296. // ul.links appear as if it would have the .inline CSS class.
  297. 'attributes' => array(),
  298. )),
  299. ));
  300. // If not re-configuring an existing protection, make it the default.
  301. if (!isset($mollom_form['mode'])) {
  302. $form['mollom']['mode']['#default_value'] = MOLLOM_MODE_ANALYSIS;
  303. }
  304. // Textual analysis filters.
  305. $form['mollom']['checks'] = array(
  306. '#type' => 'checkboxes',
  307. '#title' => t('Text analysis checks'),
  308. '#options' => array(
  309. 'spam' => t('Spam'),
  310. 'profanity' => t('Profanity'),
  311. ),
  312. '#default_value' => $mollom_form['checks'],
  313. '#states' => array(
  314. 'visible' => array(
  315. ':input[name="mollom[mode]"]' => array('value' => (string) MOLLOM_MODE_ANALYSIS),
  316. ),
  317. ),
  318. );
  319. // Profanity check requires text to analyze; unlike the spam check, there
  320. // is no fallback in case there is no text.
  321. $form['mollom']['checks']['profanity']['#access'] = !empty($mollom_form['elements']);
  322. // Form elements defined by hook_mollom_form_info() use the
  323. // 'parent][child' syntax, which Form API also uses internally for
  324. // form_set_error(), and which allows us to recurse into nested fields
  325. // during processing of submitted form values. However, since we are using
  326. // those keys also as internal values to configure the fields to use for
  327. // textual analysis, we need to encode them. Otherwise, a nested field key
  328. // would result in the following checkbox attribute:
  329. // '#name' => 'mollom[enabled_fields][parent][child]'
  330. // This would lead to a form validation error, because it is a valid key.
  331. // By encoding them, we prevent this from happening:
  332. // '#name' => 'mollom[enabled_fields][parent%5D%5Bchild]'
  333. $elements = array();
  334. foreach ($mollom_form['elements'] as $key => $value) {
  335. $elements[rawurlencode($key)] = $value;
  336. }
  337. $enabled_fields = array();
  338. foreach ($mollom_form['enabled_fields'] as $value) {
  339. $enabled_fields[] = rawurlencode($value);
  340. }
  341. $form['mollom']['enabled_fields'] = array(
  342. '#type' => 'checkboxes',
  343. '#title' => t('Text fields to analyze'),
  344. '#options' => $elements,
  345. '#default_value' => $enabled_fields,
  346. '#description' => t('Only enable fields that accept text (not numbers). Omit fields that contain sensitive data (e.g., credit card numbers) or computed/auto-generated values, as well as author information fields (e.g., name, e-mail).'),
  347. '#access' => !empty($mollom_form['elements']),
  348. '#states' => array(
  349. 'visible' => array(
  350. ':input[name="mollom[mode]"]' => array('value' => (string) MOLLOM_MODE_ANALYSIS),
  351. ),
  352. ),
  353. );
  354. $form['mollom']['strictness'] = array(
  355. '#type' => 'radios',
  356. '#title' => t('Text analysis strictness'),
  357. '#options' => array(
  358. 'normal' => t('!option <em>(!recommended)</em>', array(
  359. '!option' => t('Normal'),
  360. '!recommended' => $recommended,
  361. )),
  362. 'strict' => t('Strict: Posts are more likely classified as spam'),
  363. 'relaxed' => t('Relaxed: Posts are more likely classified as ham'),
  364. ),
  365. '#default_value' => $mollom_form['strictness'],
  366. '#states' => array(
  367. 'visible' => array(
  368. ':input[name="mollom[mode]"]' => array('value' => (string) MOLLOM_MODE_ANALYSIS),
  369. ),
  370. ),
  371. );
  372. $form['mollom']['unsure'] = array(
  373. '#type' => 'radios',
  374. '#title' => t('When text analysis is unsure'),
  375. '#default_value' => $mollom_form['unsure'],
  376. '#options' => array(
  377. 'captcha' => t('!option <em>(!recommended)</em>', array(
  378. '!option' => t('Show a CAPTCHA'),
  379. '!recommended' => $recommended,
  380. )),
  381. 'moderate' => t('Retain the post for manual moderation'),
  382. 'binary' => t('Accept the post'),
  383. ),
  384. '#required' => $mollom_form['mode'] == MOLLOM_MODE_ANALYSIS,
  385. // Only possible for forms protected via text analysis.
  386. '#states' => array(
  387. 'visible' => array(
  388. ':input[name="mollom[mode]"]' => array('value' => (string) MOLLOM_MODE_ANALYSIS),
  389. ':input[name="mollom[checks][spam]"]' => array('checked' => TRUE),
  390. ),
  391. ),
  392. );
  393. // Only possible for forms supporting moderation of unpublished posts.
  394. $form['mollom']['unsure']['moderate']['#access'] = !empty($mollom_form['moderation callback']);
  395. $form['mollom']['discard'] = array(
  396. '#type' => 'radios',
  397. '#title' => t('When text analysis identifies spam'),
  398. '#default_value' => $mollom_form['discard'],
  399. '#options' => array(
  400. 1 => t('!option <em class="mollom-recommended">(!recommended)</em>', array(
  401. '!option' => t('Discard the post'),
  402. '!recommended' => $recommended,
  403. )),
  404. 0 => t('!option <em class="mollom-recommended">(!recommended)</em>', array(
  405. '!option' => t('Retain the post for manual moderation'),
  406. '!recommended' => $recommended,
  407. )),
  408. ),
  409. '#required' => $mollom_form['mode'] == MOLLOM_MODE_ANALYSIS,
  410. // Only possible for forms supporting moderation of unpublished posts.
  411. '#access' => !empty($mollom_form['moderation callback']),
  412. // Only possible for forms protected via text analysis.
  413. '#states' => array(
  414. 'visible' => array(
  415. ':input[name="mollom[mode]"]' => array('value' => (string) MOLLOM_MODE_ANALYSIS),
  416. ':input[name="mollom[checks][spam]"]' => array('checked' => TRUE),
  417. ),
  418. ),
  419. );
  420. $form['actions']['submit'] = array(
  421. '#type' => 'submit',
  422. '#value' => t('Save'),
  423. );
  424. break;
  425. }
  426. $form['actions']['cancel'] = array(
  427. '#type' => 'link',
  428. '#title' => t('Cancel'),
  429. '#href' => 'admin/config/content/mollom',
  430. );
  431. return $form;
  432. }
  433. /**
  434. * Form submit handler for 'Next' button on Mollom form configuration form.
  435. */
  436. function mollom_admin_configure_form_next_submit($form, &$form_state) {
  437. $form_id = $form_state['values']['mollom']['form_id'];
  438. $form_state['redirect'] = $_GET['q'] . '/' . $form_id;
  439. }
  440. /**
  441. * Form validation handler for mollom_admin_configure_form().
  442. */
  443. function mollom_admin_configure_form_validate(&$form, &$form_state) {
  444. // For the 'configure' step, output custom #required form element errors for
  445. // 'checks' and 'enabled_fields', as their labels do not work with the default
  446. // #required form error message.
  447. if ($form_state['storage']['step'] == 'configure') {
  448. // Make field checkboxes required, if protection mode is text analysis.
  449. // @see http://drupal.org/node/875722
  450. $required = ($form_state['values']['mollom']['mode'] == MOLLOM_MODE_ANALYSIS);
  451. $form['mollom']['checks']['#required'] = $required;
  452. $form['mollom']['discard']['#required'] = $required;
  453. if ($required && !array_filter($form_state['values']['mollom']['checks'])) {
  454. form_error($form['mollom']['checks'], t('At least one text analysis check is required.'));
  455. }
  456. }
  457. }
  458. /**
  459. * Form submit handler for mollom_admin_configure_form().
  460. */
  461. function mollom_admin_configure_form_submit($form, &$form_state) {
  462. $mollom_form = $form_state['values']['mollom'];
  463. // Merge in form information from $form_state.
  464. $mollom_form += $form_state['storage']['mollom_form'];
  465. // Only store a list of enabled textual analysis checks.
  466. $mollom_form['checks'] = array_keys(array_filter($mollom_form['checks']));
  467. // Prepare selected fields for storage.
  468. $enabled_fields = array();
  469. foreach (array_keys(array_filter($mollom_form['enabled_fields'])) as $field) {
  470. $enabled_fields[] = rawurldecode($field);
  471. }
  472. $mollom_form['enabled_fields'] = $enabled_fields;
  473. $status = mollom_form_save($mollom_form);
  474. if ($status === SAVED_NEW) {
  475. drupal_set_message(t('The form protection has been added.'));
  476. }
  477. else {
  478. drupal_set_message(t('The form protection has been updated.'));
  479. }
  480. if (!empty($mollom_form['discard']) && !empty($mollom_form['moderation'])) {
  481. drupal_set_message(t('Spam that is discarded cannot be moderated from the Mollom Content Moderation Platform.'), 'warning');
  482. }
  483. else {
  484. $form_state['redirect'] = 'admin/config/content/mollom';
  485. }
  486. }
  487. /**
  488. * Form builder; Remove Mollom protection from a form.
  489. */
  490. function mollom_admin_unprotect_form($form, &$form_state, $mollom_form) {
  491. $form['#tree'] = TRUE;
  492. $form['form'] = array(
  493. '#type' => 'item',
  494. '#title' => t('Form'),
  495. '#markup' => $mollom_form['title'],
  496. );
  497. $form['mollom']['form_id'] = array(
  498. '#type' => 'value',
  499. '#value' => $mollom_form['form_id'],
  500. );
  501. return confirm_form($form,
  502. t('Are you sure you want to unprotect this form?'),
  503. 'admin/config/content/mollom',
  504. t('Mollom will no longer protect this form from spam.')
  505. );
  506. }
  507. /**
  508. * Form submit handler for mollom_admin_unprotect_form().
  509. */
  510. function mollom_admin_unprotect_form_submit($form, &$form_state) {
  511. mollom_form_delete($form_state['values']['mollom']['form_id']);
  512. drupal_set_message(t('The form protection has been removed.'));
  513. $form_state['redirect'] = 'admin/config/content/mollom';
  514. }
  515. /**
  516. * Form constructor to configure the blacklist.
  517. *
  518. * @param $type
  519. * The type of blacklist; i.e., 'spam', 'profanity', or 'unwanted'.
  520. */
  521. function mollom_admin_blacklist_form($form, &$form_state, $type = 'spam') {
  522. $form['#tree'] = TRUE;
  523. $form['#attached']['css'][] = drupal_get_path('module', 'mollom') . '/mollom.admin.css';
  524. // Translate internal reason values for rendering and select list in form.
  525. $contexts = array(
  526. 'allFields' => t('- All fields -'),
  527. 'author' => t('- All author fields -'),
  528. 'authorName' => t('Author name'),
  529. 'authorMail' => t('Author e-mail'),
  530. 'authorIp' => t('Author IP'),
  531. 'authorId' => t('Author User ID'),
  532. 'post' => t('- All post fields -'),
  533. 'postTitle' => t('Post title'),
  534. 'links' => t('Links'),
  535. );
  536. $matches = array(
  537. 'contains' => t('contains'),
  538. 'exact' => t('exact'),
  539. );
  540. $form['blacklist'] = array();
  541. // Do not retrieve the current blacklist when submitting the form.
  542. $blacklist = (empty($form_state['input']) ? mollom()->getBlacklist() : array());
  543. if (is_array($blacklist)) {
  544. foreach ($blacklist as $entry) {
  545. if ($entry['reason'] != $type) {
  546. continue;
  547. }
  548. $row = array();
  549. // #class property is internally used by
  550. // theme_mollom_admin_blacklist_form().
  551. $row['context'] = array(
  552. '#markup' => check_plain(isset($contexts[$entry['context']]) ? $contexts[$entry['context']] : $entry['context']),
  553. '#class' => 'mollom-blacklist-context value-' . check_plain($entry['context']),
  554. );
  555. $row['match'] = array(
  556. '#markup' => check_plain($matches[$entry['match']]),
  557. '#class' => 'mollom-blacklist-match value-' . check_plain($entry['match']),
  558. );
  559. $row['value'] = array(
  560. '#markup' => check_plain($entry['value']),
  561. '#class' => 'mollom-blacklist-value',
  562. );
  563. $row['actions']['delete'] = array(
  564. '#type' => 'link',
  565. '#title' => t('delete'),
  566. '#href' => 'admin/config/content/mollom/blacklist/delete',
  567. '#options' => array(
  568. 'query' => array('id' => $entry['id']) + drupal_get_destination(),
  569. ),
  570. );
  571. $form['blacklist'][$entry['id']] = $row;
  572. }
  573. }
  574. $form['entry']['context'] = array(
  575. '#type' => 'select',
  576. '#title' => t('Context'),
  577. '#title_display' => 'invisible',
  578. '#options' => $contexts,
  579. '#required' => TRUE,
  580. '#id' => 'mollom-blacklist-filter-context',
  581. );
  582. $form['entry']['match'] = array(
  583. '#type' => 'select',
  584. '#title' => t('Match'),
  585. '#title_display' => 'invisible',
  586. '#options' => $matches,
  587. '#required' => TRUE,
  588. '#id' => 'mollom-blacklist-filter-match',
  589. );
  590. $form['entry']['value'] = array(
  591. '#type' => 'textfield',
  592. '#title' => t('Value'),
  593. '#title_display' => 'invisible',
  594. '#size' => 40,
  595. '#required' => TRUE,
  596. '#maxlength' => 64,
  597. '#id' => 'mollom-blacklist-filter-value',
  598. '#attributes' => array(
  599. 'autocomplete' => 'off',
  600. ),
  601. );
  602. $form['entry']['reason'] = array(
  603. '#type' => 'value',
  604. '#value' => $type,
  605. );
  606. $form['entry']['actions'] = array(
  607. '#type' => 'actions',
  608. '#tree' => FALSE,
  609. );
  610. $form['entry']['actions']['submit'] = array(
  611. '#type' => 'submit',
  612. '#value' => t('Add'),
  613. );
  614. return $form;
  615. }
  616. /**
  617. * Form submit handler to save a text string to the Mollom blacklist.
  618. */
  619. function mollom_admin_blacklist_form_submit($form, &$form_state) {
  620. $data = array(
  621. 'value' => $form_state['values']['entry']['value'],
  622. 'context' => $form_state['values']['entry']['context'],
  623. 'match' => $form_state['values']['entry']['match'],
  624. 'reason' => $form_state['values']['entry']['reason'],
  625. );
  626. $result = mollom()->saveBlacklistEntry($data);
  627. $args = array(
  628. '@value' => $data['value'],
  629. '@context' => $data['context'],
  630. '@match' => $data['match'],
  631. '@reason' => $data['reason'],
  632. );
  633. if (!empty($result['id'])) {
  634. drupal_set_message(t('The entry was added to the blacklist.'));
  635. mollom_log(array(
  636. 'message' => 'Added @value (@context, @match) to @reason blacklist.',
  637. 'arguments' => $args,
  638. ));
  639. }
  640. else {
  641. drupal_set_message(t('An error occurred upon trying to add the value to the blacklist.'), 'error');
  642. mollom_log(array(
  643. 'message' => 'Failed to add @value (@context, @match) to @reason blacklist.',
  644. 'arguments' => $args,
  645. ), WATCHDOG_ERROR);
  646. }
  647. }
  648. /**
  649. * Formats the blacklist form as table to embed the form.
  650. */
  651. function theme_mollom_admin_blacklist_form($variables) {
  652. $form = $variables['form'];
  653. $header = array(
  654. t('Context'),
  655. t('Matches'),
  656. t('Value'),
  657. '',
  658. );
  659. $rows = array();
  660. $rows[] = array(
  661. drupal_render($form['entry']['context']),
  662. drupal_render($form['entry']['match']),
  663. drupal_render($form['entry']['value']),
  664. drupal_render($form['entry']['actions']),
  665. );
  666. foreach (element_children($form['blacklist']) as $id) {
  667. $rows[] = array(
  668. array(
  669. 'data' => drupal_render($form['blacklist'][$id]['context']),
  670. 'class' => $form['blacklist'][$id]['context']['#class'],
  671. ),
  672. array(
  673. 'data' => drupal_render($form['blacklist'][$id]['match']),
  674. 'class' => $form['blacklist'][$id]['match']['#class'],
  675. ),
  676. array(
  677. 'data' => drupal_render($form['blacklist'][$id]['value']),
  678. 'class' => $form['blacklist'][$id]['value']['#class'],
  679. ),
  680. drupal_render($form['blacklist'][$id]['actions']),
  681. );
  682. }
  683. // This table is never empty due to the form.
  684. $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'mollom-blacklist')));
  685. $output .= drupal_render_children($form);
  686. drupal_add_js(drupal_get_path('module', 'mollom') . '/mollom.admin.blacklist.js');
  687. return $output;
  688. }
  689. /**
  690. * Form builder; Builds the confirmation form for deleting a blacklist item.
  691. *
  692. * @ingroup forms
  693. * @see mollom_admin_blacklist_delete_submit()
  694. */
  695. function mollom_admin_blacklist_delete($form, &$form_state) {
  696. $id = $_GET['id'];
  697. if (empty($id) || !($entry = mollom()->getBlacklistEntry($id))) {
  698. drupal_not_found();
  699. return;
  700. }
  701. $form['entry'] = array(
  702. '#type' => 'value',
  703. '#value' => $entry,
  704. );
  705. return confirm_form(
  706. $form,
  707. t('Are you sure you want to delete %value from the blacklist?', array('%value' => $entry['value'])),
  708. 'admin/config/content/mollom/blacklist',
  709. t('This action cannot be undone.'),
  710. t('Delete'), t('Cancel')
  711. );
  712. }
  713. /**
  714. * Form submit handler to delete an entry from the blacklist.
  715. */
  716. function mollom_admin_blacklist_delete_submit($form, &$form_state) {
  717. $result = mollom()->deleteBlacklistEntry($form_state['values']['entry']['id']);
  718. $args = array(
  719. '@value' => $form_state['values']['entry']['value'],
  720. '@context' => $form_state['values']['entry']['context'],
  721. '@reason' => $form_state['values']['entry']['reason'],
  722. );
  723. if ($result === TRUE) {
  724. drupal_set_message(t('The entry was removed from the blacklist.'));
  725. mollom_log(array(
  726. 'message' => 'Removed @value (@context) from @reason blacklist.',
  727. 'arguments' => $args,
  728. ));
  729. }
  730. else {
  731. drupal_set_message(t('An error occurred upon trying to remove the item from the blacklist.'), 'error');
  732. mollom_log(array(
  733. 'message' => 'Failed to remove @value (%context) from @reason blacklist.',
  734. 'arguments' => $args,
  735. ), WATCHDOG_ERROR);
  736. }
  737. $form_state['redirect'] = 'admin/config/content/mollom/blacklist';
  738. }
  739. /**
  740. * Form builder; Global Mollom settings form.
  741. *
  742. * This form does not validate Mollom API keys, since the fallback method still
  743. * needs to be able to be reconfigured in case Mollom services are down.
  744. * mollom.verifyKey would invalidate the keys and throw an error; hence,
  745. * _mollom_fallback() would invoke form_set_error(), effectively preventing this
  746. * form from submitting.
  747. *
  748. * @todo Implement proper form validation now that mollom() no longer triggers
  749. * the fallback mode.
  750. */
  751. function mollom_admin_settings($form, &$form_state) {
  752. $mollom = mollom();
  753. $check = empty($_POST);
  754. $status = mollom_admin_site_status($check);
  755. if ($check && $status['isVerified'] && !variable_get('mollom_testing_mode', 0)) {
  756. drupal_set_message(t('Mollom servers verified your keys. The services are operating correctly.'));
  757. }
  758. $form['access-keys'] = array(
  759. '#type' => 'fieldset',
  760. '#title' => t('Mollom API keys'),
  761. '#description' => t('To obtain API keys, <a href="@signup-url">sign up</a> or log in to your <a href="@site-manager-url">Site manager</a>, register this site, and copy the keys into the fields below.', array(
  762. '@signup-url' => 'https://www.mollom.com/pricing',
  763. '@site-manager-url' => 'https://www.mollom.com/site-manager',
  764. )),
  765. '#collapsible' => TRUE,
  766. // Only show key configuration fields if they are not configured or invalid.
  767. '#collapsed' => !$status['isVerified'],
  768. );
  769. // Keys are not #required to allow to install this module and configure it
  770. // later.
  771. $form['access-keys']['mollom_public_key'] = array(
  772. '#type' => 'textfield',
  773. '#title' => t('Public key'),
  774. '#default_value' => variable_get('mollom_public_key'),
  775. '#element_validate' => array('mollom_admin_settings_validate_key'),
  776. '#description' => t('Used to uniquely identify this site.'),
  777. );
  778. $form['access-keys']['mollom_private_key'] = array(
  779. '#type' => 'textfield',
  780. '#title' => t('Private key'),
  781. '#default_value' => variable_get('mollom_private_key'),
  782. '#element_validate' => array('mollom_admin_settings_validate_key'),
  783. '#description' => t('Used for authentication. Similar to a password, the private key should not be shared with anyone.'),
  784. );
  785. $form['mollom_fallback'] = array(
  786. '#type' => 'radios',
  787. '#title' => t('When the Mollom service is unavailable'),
  788. '#default_value' => variable_get('mollom_fallback', MOLLOM_FALLBACK_ACCEPT),
  789. '#options' => array(
  790. MOLLOM_FALLBACK_ACCEPT => t('Accept all form submissions'),
  791. MOLLOM_FALLBACK_BLOCK => t('Block all form submissions'),
  792. ),
  793. '#description' => t('Mollom offers a <a href="@pricing-url">high-availability</a> infrastructure for users on paid plans to reduce potential downtime.', array(
  794. '@pricing-url' => 'https://www.mollom.com/pricing',
  795. )),
  796. );
  797. $options = _mollom_supported_languages();
  798. $default_languages = array();
  799. if (isset($status['expectedLanguages'])) {
  800. $default_languages = $status['expectedLanguages'];
  801. }
  802. else {
  803. $default_languages = $mollom->loadConfiguration('expectedLanguages');
  804. }
  805. $path = drupal_get_path('module', 'mollom');
  806. $form[$mollom->configuration_map['expectedLanguages']] = array(
  807. '#type' => 'select',
  808. '#title' => t('Expected languages'),
  809. '#options' => $options,
  810. '#multiple' => TRUE,
  811. '#size' => 6,
  812. '#default_value' => $default_languages,
  813. '#description' => t('Restricts all posts to selected languages. Used by text analysis only. Leave empty if users may post in other languages.'),
  814. // Ensure that selected languages are apparent for site administrators and
  815. // not potentially hidden in a large select widget.
  816. '#attributes' => array(
  817. // @see form_process_select()
  818. 'data-placeholder' => t('- Empty -'),
  819. 'id' => $mollom->configuration_map['expectedLanguages'],
  820. ),
  821. '#attached' => array(
  822. 'js' => array(
  823. $path . '/mollom.admin.js',
  824. ),
  825. ),
  826. );
  827. // Add the chosen library if available through the libraries module.
  828. if (module_exists('libraries')) {
  829. if ($path = libraries_get_path('chosen')) {
  830. $form[$mollom->configuration_map['expectedLanguages']]['#attached']['js'][] = $path . '/chosen.jquery.min.js';
  831. $form[$mollom->configuration_map['expectedLanguages']]['#attached']['css'][] = $path . '/chosen.min.css';
  832. }
  833. }
  834. $form['mollom_privacy_link'] = array(
  835. '#type' => 'checkbox',
  836. '#title' => t("Show a link to Mollom's privacy policy"),
  837. '#return_value' => 1,
  838. '#default_value' => variable_get('mollom_privacy_link', 1),
  839. '#description' => t('Only applies to forms protected with text analysis. When disabling this option, you should inform visitors about the privacy of their data through other means.'),
  840. );
  841. $form['mollom_testing_mode'] = array(
  842. '#type' => 'checkbox',
  843. '#title' => t('Enable testing mode'),
  844. '#default_value' => variable_get('mollom_testing_mode', 0),
  845. '#description' => t('Submitting "ham", "unsure", or "spam" triggers the corresponding behavior; image CAPTCHAs only respond to "correct" and audio CAPTCHAs only respond to "demo". Do not enable this option if this site is publicly accessible.'),
  846. );
  847. $form['mollom_advanced'] = array(
  848. '#type' => 'fieldset',
  849. '#title' => t('Advanced configuration'),
  850. '#collapsible' => TRUE,
  851. '#collapsed' => TRUE,
  852. );
  853. // Lower severity numbers indicate a high severity level.
  854. $min_severity = variable_get('mollom_log_minimum_severity', WATCHDOG_WARNING);
  855. $form['mollom_advanced']['mollom_log_minimum_severity'] = array(
  856. '#type' => 'radios',
  857. '#title' => t('Mollom logging level warning'),
  858. '#options' => array(
  859. WATCHDOG_WARNING => t('Only log warnings and errors'),
  860. WATCHDOG_DEBUG => t('Log all Mollom messages'),
  861. ),
  862. '#default_value' => $min_severity <= WATCHDOG_WARNING ? WATCHDOG_WARNING : WATCHDOG_DEBUG,
  863. );
  864. $form['mollom_advanced']['mollom_audio_captcha_enabled'] = array(
  865. '#type' => 'checkbox',
  866. '#title' => t('Enable audio CAPTCHAs.'),
  867. '#description' => t('Allows users to switch to an audio verification using the <a href="!faq-url">NATO alphabet</a>. This may not be appropriate for non-English language sites.', array(
  868. '!faq-url' => 'https://www.mollom.com/faq/mollom-audible-captcha-language',
  869. )),
  870. '#default_value' => variable_get('mollom_audio_captcha_enabled', 1),
  871. );
  872. $form['mollom_advanced']['mollom_connection_timeout'] = array(
  873. '#type' => 'textfield',
  874. '#title' => t('Time-out when attempting to contact Mollom servers.'),
  875. '#description' => t('This is the length of time that a call to Mollom will wait before timing out.'),
  876. '#default_value' => variable_get('mollom_connection_timeout', 3),
  877. '#size' => 5,
  878. '#field_suffix' => t('seconds'),
  879. '#required' => TRUE,
  880. );
  881. $form['mollom_advanced']['mollom_fba_enabled'] = array(
  882. '#type' => 'checkbox',
  883. '#title' => t('Enable form behavior analysis (beta).'),
  884. '#description' => t('This will place a small tracking image from Mollom on each form protected by textural analysis to help Mollom determine if the form is filled out by a bot. <a href="!fba-url">Learn more</a>.', array(
  885. '!fba-url' => 'https://www.mollom.com/faq/form-behavior-analysis',
  886. )),
  887. '#default_value' => variable_get('mollom_fba_enabled', 0),
  888. );
  889. // Available entity types are those entities that have a report access
  890. // callback defined. This is limited by type even though multiple forms
  891. // can be for the same entity type.
  892. $forms = mollom_form_list();
  893. $options = array();
  894. foreach($forms as $info) {
  895. if (!empty($info['entity']) && !empty($info['entity report access callback'])) {
  896. $options[] = $info['entity'];
  897. }
  898. };
  899. sort($options);
  900. $form['mollom_advanced']['mollom_fai_entity_types'] = array(
  901. '#type' => 'checkboxes',
  902. '#title' => t('Allow users to "Flag as Inappropriate" for the following:'),
  903. '#description' => t('"Flag as inappropriate" will only appear on protected forms for users who have permission to the content type and have the permission to "Report to Mollom". <a href="!fai-url">Learn more</a>.', array(
  904. '!fai-url' => 'https://www.mollom.com/faq/flag-as-inappropriate',
  905. )),
  906. '#options' => drupal_map_assoc($options),
  907. '#default_value' => variable_get('mollom_fai_entity_types', array('comment' => 'comment')),
  908. );
  909. // Only display dialog options if the user has at least one integrated
  910. // module installed and enabled.
  911. module_load_include('inc', 'mollom', 'mollom.flag');
  912. $implemented = mollom_flag_dialog_info();
  913. $available = array_intersect_key($implemented, module_list());
  914. $options = array();
  915. foreach($available as $module => $info) {
  916. $options[$module] = $info['title'];
  917. }
  918. if (count($options) > 0) {
  919. $options['mollom'] = 'Mollom custom dialog';
  920. $form['mollom_advanced']['mollom_fai_dialog'] = array(
  921. '#type' => 'radios',
  922. '#title' => t('Flag as inappropriate dialog type'),
  923. '#options' => $options,
  924. '#default_value' => variable_get('mollom_fai_dialog', 'mollom'),
  925. );
  926. }
  927. else {
  928. // Reset to use the Mollom dialog if no integrated modules are available.
  929. $form['mollom_advanced']['mollom_fai_dialog'] = array(
  930. '#type' => 'value',
  931. '#value' => 'mollom',
  932. );
  933. }
  934. $form['#submit'][] = 'mollom_admin_settings_prepare_submit';
  935. $form = system_settings_form($form);
  936. $form['#submit'][] = 'mollom_admin_settings_submit';
  937. return $form;
  938. }
  939. /**
  940. * Element validation handler for API key text fields.
  941. */
  942. function mollom_admin_settings_validate_key($element, &$form_state) {
  943. if ($element['#value'] !== '') {
  944. // Remove any leading/trailing white-space and override submitted value.
  945. $element['#value'] = trim($element['#value']);
  946. form_set_value($element, $element['#value'], $form_state);
  947. // Verify the key has a length of 32 characters.
  948. if (drupal_strlen($element['#value']) != 32) {
  949. form_error($element, t('!title must be 32 characters. Ensure you copied the key correctly.', array(
  950. '!title' => $element['#title'],
  951. )));
  952. }
  953. }
  954. }
  955. /**
  956. * Form submission handler for global settings form.
  957. */
  958. function mollom_admin_settings_prepare_submit($form, &$form_state) {
  959. // If the minimum log severity checkbox was disabled (no input), convert
  960. // 0 into WATCHDOG_DEBUG.
  961. if (!isset($form_state['input']['mollom_log_minimum_severity'])) {
  962. $form_state['values']['mollom_log_minimum_severity'] = WATCHDOG_DEBUG;
  963. }
  964. }
  965. /**
  966. * Form submission handler for global settings form.
  967. */
  968. function mollom_admin_settings_submit($form, &$form_state) {
  969. // Update Mollom site record with local configuration.
  970. _mollom_status(TRUE, TRUE);
  971. }
  972. /**
  973. * Menu callback; Displays the administrative reports page.
  974. */
  975. function mollom_reports_page($form, &$form_state) {
  976. $embed_attributes = array(
  977. 'src' => 'https://www.mollom.com/statistics.swf?key=' . check_plain(variable_get('mollom_public_key', '')),
  978. 'quality' => 'high',
  979. 'width' => '100%',
  980. 'height' => '430',
  981. 'name' => 'Mollom',
  982. 'align' => 'middle',
  983. 'play' => 'true',
  984. 'loop' => 'false',
  985. 'allowScriptAccess' => 'sameDomain',
  986. 'type' => 'application/x-shockwave-flash',
  987. 'pluginspage' => 'http://www.adobe.com/go/getflashplayer',
  988. 'wmode' => 'transparent',
  989. );
  990. $form['chart'] = array(
  991. '#type' => 'item',
  992. '#title' => t('Statistics'),
  993. '#markup' => '<embed' . drupal_attributes($embed_attributes) . '></embed>',
  994. );
  995. return $form;
  996. }