/[drupal]/contributions/modules/spam/spam.module
ViewVC logotype

Contents of /contributions/modules/spam/spam.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.51 - (show annotations) (download) (as text)
Sun Apr 17 20:15:18 2005 UTC (4 years, 7 months ago) by jeremy
Branch: MAIN
CVS Tags: DRUPAL-4-5--1-0, DRUPAL-4-6--1-0, HEAD
Branch point for: DRUPAL-4-6, DRUPAL-4-7
Changes since 1.50: +139 -61 lines
File MIME type: text/x-php
 - spam.module
   o updated to work with Drupal 4.6
   o fixed issues with publishing/unpublishing nodes and comments
   o added ability to mass-delete spam nodes and comments
   o issue #17031: simplified statistics page (needs lots of work)
   o issue #14382: added ability to search/modify old comments
 - spam.mysql
   o issue #18411: mark all primary keys NOT NULL
1 <?php
2 // $Id: spam.module,v 1.50 2005/03/09 13:00:19 jeremy Exp $
3
4 /*********** spam api ***********/
5
6 /**
7 * Determine whether or not provided text is spam.
8 *
9 * @param $content
10 * An object containing the content to test as spam
11 * @param $body
12 * A string that is the name of the text body within the content object
13 * @param $header
14 * A string that is the name of the text header within the content object
15 * @param $callback
16 * A function to call when we determine whether or not given content is spam
17 * @action
18 * 'insert' or 'update'
19 * @old_value
20 * Only valid on updates, whether this content was previously considered spam
21 * (TRUE or FALSE)
22 *
23 * @return
24 * TRUE (spam) or FALSE (not spam)
25 */
26 function spam_check($content, $body, $header, $callback = NULL, $action = NULL, $old_value = NULL) {
27 $weight = spam_filter_open_relay();
28 $weight += spam_custom_filter($content->$header .' '. $content->$body, $delete);
29 $weight += spam_filter_urls($content->$header .' '. $content->$body);
30 $tokens = spam_tokenize($content->$header, $header .'*');
31 $tokens = array_merge($tokens, spam_tokenize($content->$body));
32 $weight += spam_limit_urls($header, spam_count_urls());
33 // invoke _spam hook, allow external modules to perform filter actions
34 $updated = spam_invoke_spam_hook('check', $content->$header . ' '. $content->$body, $tokens, $header);
35 if ($updated['weight']) {
36 $weight += $updated['weight'];
37 }
38 $rating = _spam_rating($tokens, $weight);
39 $spam = ($rating >= variable_get('spam_threshold', 80));
40 if ($callback != NULL) {
41 if ($action == 'insert') {
42 $callback($content, $tokens, $spam, $rating, $action, $delete);
43 }
44 else if ($action == 'update') {
45 $callback($content, $tokens, $spam, $rating, $action, $delete, $old_value);
46 }
47 }
48 if ($spam)
49 return TRUE;
50 else
51 return FALSE;
52 }
53
54 /*********** spam module drupal hooks ***********/
55
56 function spam_help($section) {
57 switch ($section) {
58 case 'admin/modules#description':
59 $output = t('Bayesian filter to automatically handle spam.');
60 break;
61 case 'admin/comment/list/spam':
62 $output = t('The following comments have been marked as spam.');
63 break;
64 case 'admin/node/spam':
65 $output = t('The following posts have been marked as spam.');
66 break;
67 case 'admin/spam':
68 case 'admin/spam/statistics':
69 $output = t('<p>The spam module provides an automatic mechanism for detecting and dealing with unwanted spam. It is able to filter comments and nodes. Spam is content posted to your site that is unrelated to the current topic, usually either advertising a product or including links to an external website in the hopes of increasing that website\'s visibility in search engines.</p><p>Configure the spam module at '. l(t('administer &raquo; settings &raquo; spam'), 'admin/settings/spam') .'. View all comment spam at '. l(t('administer &raquo comments &raquo; list &raquo spam'), 'admin/comment/list/spam') .'. View all node spam at '. l(t('administer &raquo content &raquo; spam'), 'admin/node/spam') .'.</p>');
70 break;
71 case 'admin/spam/custom':
72 $output = t('<p>Define words, phrases and regular expressions to be tested against new content on your site. If your custom filter matches, you can cause this to increase or decrease the probability that the given content is spam. For example, if a comment about "viagra" is completely out of place on your site you could create a custom filter such that any comment with the word "viagra" in it would always be marked as spam, no matter what the Bayesian filter rates it.</p>');
73 break;
74 case 'admin/spam/urls':
75 $output = t('<p>They Bayesian filter automatically learns spammer domain names. Any new comment or other content containing one of the domain names listed below will be automatically marked as spam. For example, if "spam.com" is listed below, a new comment containing the text "http://spam.com/great/deals" will be marked as spam. In addition to automatically learning spammer domains, you can also manually add known spammer domains below.</p><p>It is possible to instead block spammer domains by adding an appropriate "custom filter" at %custom, however the big advantage of "URL filters" is that they are automatically learned using Bayesian logic.', array('%custom' => l(t('administer &raquo spam &raquo customer filters'), 'admin/spam/custom')));
76 break;
77 case 'admin/help#spam':
78 $output = t("
79 <p><b>Overview</b><br />
80 The spam module provides an automatic mechanism for detecting and dealing with unwanted spam. It is able to filter comments and nodes. Spam is content posted to your site that is unrelated to the current topic, usually either advertising a product or including links to an external website in the hopes of increasing that website's visibility in search engines.</p>
81 <p>Spam is detected with Bayesian logic. Essentially, all new content is broken into individual words. These words are then looked up in a database table to check if we've seen them before. If most of the words that we have seen before more often in spam content than non-spam conent, then there is a high probability that this new content is also spam. If new content has a high enough probability of being spam, the module can take automatic action, such as unpublishing the content and notifying the website administrator.</p>
82 <p>To begin, the module has no knowledge of what is spam and what is not spam. Thus, it will assume that all new content is not spam. When you get your first spam posting, you will need to click 'mark as spam' to teach the Bayesian filter that it made a mistake. The content will then be broken into individual words, and the words will be stored in the database to be used for detecting future spam. You will have to continue teaching the Bayesian filter with a couple hundred examples before it will begin to consistently detect spam automatically.</p>
83 <p><b>Configuration</b><br />
84 The spam module can be configured at ". l(t('administer &raquo; settings &raquo; spam'), 'admin/settings/spam') .". By default, only comments will be filtered. However, you are able to enable content filtering as well. Each node type supported by your website will be listed on the spam configuration page, allowing you to for example filter forum posts but not stories.</p>
85 <p>The next configuration section provides automatic actions for the module to take when it detects spam. For example, if you wish for spam to be automatically unpublished when detected, click 'Automatically unpublish spam'. If you wish to send a notification email to the site administrator, click 'Notify admin when spam detected'.</p>
86 <p>The rest of the options are related to the Bayesian logic, and are best left in their default configurations, unless you feel very confident that you know what you're doing.</p>
87 <p><b>Permissions</b><br />
88 The spam module provides to permissions that can be configured at ". l(t('administer &raquo; user &raquo; configure &raquo; permissions'), 'admin/user/configure/permission') .". The 'administer spam rating' permisison allows a user to see whether or not conent is rated as spam. The 'administer spam rating' permissions allows a user to teach the Bayesian filter, providing links that say 'mark as spam', and 'mark as not spam'.</p>
89 <p><b>Spam</b><br />
90 The spam module adds links to any site content that is being filtered. The links are only visible to users with the proper permissions (explained above). Additionally, a complete listing of all spam comments on the website can be viewed at ". l(t('administer &raquo; comments &raquo; spam'), 'admin/comment/list/spam') .". A complete listing of all spam posts can be viewed at ". l(t('administer &raquo content &raquo; spam'), 'admin/node/spam') ."</p>
91 <p><b>Background</b><br />
92 This module was inspired by my experiences with the <a href='http://spamassassin.apache.org/'>Spamassassin</a> mail filter. For actual implementation, I referred to Paul Graham's excellent papers, <a href='http://www.paulgraham.com/spam.html'>A Plan For Spam</a> and <a href='http://www.paulgraham.com/better.html'>Better Bayesian Filtering</a>. Further enhancements and ideas were also inspired by Bill Yerazunis' <a href='http://crm114.sourceforge.net/'>CRM114</a>, especially his paper 'The Spam-Filtering Accuracy Plateau at 99.9% Accuracy'.</p>
93 ");
94 }
95 return $output;
96 }
97
98 /* This modules defines three permissions:
99 * access spam rating - allows admin to see content's current spam status
100 * administer spam rating - allows admin to edit content's current spam status
101 * bypass spam filter - a role with this permission can submit content without
102 * being filtered for spam
103 */
104 function spam_perm() {
105 return array('access spam rating', 'administer spam rating', 'bypass spam filter');
106 }
107
108 function spam_cron() {
109 if (variable_get('spam_calculate_probabilities', 1)) {
110 spam_calculate_probabilities();
111 }
112 }
113
114 /* logic to detect spam comments */
115 function spam_comment($action, $comment) {
116 global $base_url;
117 if (user_access('bypass spam filter')) return;
118 $comment = array2object($comment);
119 // also scan anonymous user names and links
120 $comment->comment .= ' '. $comment->name .' '. $comment->mail .' '. $comment->homepage;
121 switch ($action) {
122 case 'insert':
123 spam_check($comment, 'comment', 'subject', 'spam_comment_actions', $action);
124 break;
125 case 'update':
126 $old = db_fetch_object(db_query('SELECT * FROM {spam_comments} WHERE cid = %d', $comment->cid));
127 spam_check($comment, 'comment', 'subject', 'spam_comment_actions', $action, $old->spam);
128 break;
129 case 'delete':
130 // this hook doesn't actually exist (in Drupal 4.4), leaving for future
131 db_query('DELETE FROM {spam_comments} WHERE cid = %d', $comment->cid);
132 break;
133 }
134 }
135
136 /* logic to detect spam nodes */
137 function spam_nodeapi(&$node, $op, $arg = 0) {
138 if (user_access('bypass spam filter')) return;
139 switch ($op) {
140 case 'insert':
141 if (variable_get("spam_filter_$node->type", 0)) {
142 spam_check($node, 'body', 'header', 'spam_node_actions', $op);
143 }
144 break;
145 case 'update':
146 // check if spam status of node has changed
147 if (variable_get("spam_filter_$node->type", 0)) {
148 $old = db_fetch_object(db_query('SELECT * FROM {spam_nodes} WHERE nid = %d', $node->nid));
149 spam_check($node, 'body', 'header', 'spam_node_actions', $op, $old->spam);
150 }
151 break;
152 case 'delete':
153 db_query('DELETE FROM {spam_nodes} WHERE nid = %d', $node->nid);
154 break;
155 }
156 }
157
158 function spam_filter_open_relay() {
159 // inspired by http://weblog.sinteur.com/index.php?p=7967
160 $weight = 0;
161 if (variable_get('spam_filter_open_relay', 0)) {
162 list($a, $b, $c, $d) = split('.', $_SERVER['REMOTE_ADDR']);
163 if(checkdnsrr("$d.$c.$b.$a.list.dsbl.org")) {
164 // this comment was submitted from an open relay, most probably spam
165 _count(array("+blocked_open_relay"));
166 $weight = $interesting * 200;
167 }
168 }
169 return $weight;
170 }
171
172 function spam_filter_urls($text) {
173 if (variable_get('spam_filter_urls', 1)) {
174 $interesting = variable_get('spam_interesting_tokens', 15);
175 $weight = 0;
176 $result = db_query("SELECT token FROM {spam_tokens} WHERE probability > %d AND token LIKE 'URL*%%'", variable_get('spam_threshold', 80));
177 while ($url = db_fetch_object($result)) {
178 $url = preg_replace('/^URL\*/', '', $url->token);
179 $match = preg_match_all("!$url!", $text, $matches);
180 if ($match) {
181 _count(array("+matched_spam_url"));
182 $weight = $weight + ($match * $interesting * 200);
183 }
184 }
185 }
186 return $weight;
187 }
188
189 function spam_settings() {
190 // general configuration
191 $group = form_checkbox(t('Filter comments'), 'spam_filter_comments', 1, variable_get('spam_filter_comments', 1), t('If checked, the spam module will check all new comments that are posted to this site and attempt to determine whether or not they are spam. If only trusted users are able to post comments, there is no need to enable this option.'));
192 $node_types = node_list();
193 foreach ($node_types as $type) {
194 $group .= form_checkbox(t("Filter $type content"), "spam_filter_$type", 1, variable_get("spam_filter_$type", 0), t("If checked, the spam module will check all new $type content that is posted to this site and attempt to determine whether or not it is spam. If only trusted users are able to post $type content, there is no need to enable this option."));
195 }
196 $group .= form_checkbox(t('Filter open relays'), 'spam_filter_open_relay', 1, variable_get('spam_filter_open_relay', 0), t('If checked, the spam module will mark as spam all new or updated comments or content that are being posted from an IP address that is a known open email relay. The determination of whether or not an IP address is an open relay or otherwise commonly used to generate spam is done with the <a href="http://dsbl.org/">Distributed Server Boycott List</a>. Note that if using custom filters, matching on a "never spam" rule or multiple "usually not spam" rules can permit postings even from open relays.'));
197 $group .= form_checkbox(t('Filter spammer URLs'), 'spam_filter_urls', 1, variable_get('spam_filter_urls', 1), t('If checked, the spam module will pay special attention to any URLs that are embedded within comments and other content. When URLs that were found within known spam are found in new comments and other new content, the new content is automatically considered to be spam. When this option is enabled, a single spam URL found within an otherwise spam-free comment or other content will cause the filter to mark the new content as spam.'));
198 // invoke _spam hook, allow external modules to define filter types
199 $updated = spam_invoke_spam_hook('filter_settings');
200 if ($updated['group']) {
201 $group .= $updated['group'];
202 }
203 $output = form_group ('Filter', $group);
204
205 // limits
206 $group = form_select(t('Total URLs per comment'), 'spam_comment_total_urls', variable_get('spam_comment_total_urls', 0), array(0 => 'disabled', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '15', '20'), t('Specificy the maximum number of URLs that are allowed in a single comment before the comment is considered to be spam. For example, if you select 5 from the pop down menu, and then a comment has 6 weblinks, the comment will be marked as spam. This option will only take affect if you enable comment filtering above.'));
207 $group .= form_select(t('Repeated URLs per comment'), 'spam_comment_repeat_urls', variable_get('spam_comment_repeat_urls', 0), array(0 => 'disabled', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '15', '20'), t('Specificy the maximum number of times that the same URL is allowed to appear in one single comment before the comment is considered to be spam. For example, if you select 5 from the pop down menu, and then a comment has 6 weblinks to the same exact location, the comment will be marked as spam. (The entire url is used, so "http://kerneltrap.org/journals/hacker" would be considered different than "http://kerneltrap.org/journals".) This option will only take affect if you enable comment filtering above.'));
208 $group .= form_select(t('Total URLs per post'), 'spam_content_total_urls', variable_get('spam_content_total_urls', 0), array(0 => 'disabled', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '15', '20'), t('Specificy the maximum number of URLs that are allowed in a single post before the post is considered to be spam. For example, if you select 5 from the pop down menu, and then a post has 6 weblinks, the post will be marked as spam. Posts are content other than comments, such as story content and page content. This option will only take affect if you enable content filtering above.'));
209 $group .= form_select(t('Repeated URLs per post'), 'spam_content_repeat_urls', variable_get('spam_content_repeat_urls', 0), array(0 => 'disabled', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '15', '20'), t('Specificy the maximum number of times that the same URL is allowed to appear in one single post before the post is considered to be spam. For example, if you select 5 from the pop down menu, and then a post has 6 weblinks to the same exact location, the post will be marked as spam. (The entire url is used, so "http://kerneltrap.org/journals/hacker" would be considered different than "http://kerneltrap.org/journals".) This option will only take affect if you enable content filtering above.'));
210 // invoke _spam hook, allow external modules to define url_count limits
211 $updated = spam_invoke_spam_hook('url_count_settings');
212 if ($updated['group']) {
213 $group .= $updated['group'];
214 }
215
216 $output .= form_group ('Limits', $group);
217
218 // actions
219 $group = form_checkbox(t('Automatically unpublish spam'), 'spam_unpublish', 1, variable_get('spam_unpublish', 0), t('Automatically unpublish spam content. This will prevent it from being displayed unless an administrator manually publishes it or marks the content as being not spam.'));
220 $group .= form_checkbox(t('Notify admin when spam detected'), 'spam_notify_admin', 1, variable_get('spam_notify_admin', 0), t('Send an email to the site administrator when the Bayesian filter detectes spam content.'));
221 // invoke _spam hook, allow external modules to define actions
222 $updated = spam_invoke_spam_hook('action_settings');
223 if ($updated['group']) {
224 $group .= $updated['group'];
225 }
226 $output .= form_group('Actions', $group);
227
228 // advanced configuration options
229 $group = form_checkbox(t('Advanced configuration'), 'spam_advanced_configuration', 1, variable_get('spam_advanced_configuration', 0), t('Enable the advanced configuration options allowing you to fine tune the Bayesion filter.'));
230 if (variable_get('spam_advanced_configuration', 0)) {
231 // developer tools
232 $inner_group = form_checkbox(t('Display spam rating'), 'spam_display_rating', 1, variable_get('spam_display_rating', 0), t('If enabled, the probability that a given piece of content is spam will be displayed next to the content. Useful when configuring your bayesian filter.'));
233 $inner_group .= form_checkbox(t('Collect statistics'), 'spam_statistics', 1, variable_get('spam_statistics', 1), t('Measure the effectiveness of the Bayesian filter. There is some performance overhead caused by enabling this option.'));
234 $inner_output = form_group('Tools', $inner_group);
235
236 // bayesion filter configuration
237 $inner_group = form_select(t('Threshold'), 'spam_threshold', variable_get('spam_threshold', 80), array(10 => 10, 20 => 20, 30 => 30, 40 => 40, 50 => 50, 60 => 60, 70 => 70, 80 => 80, 90 => 90), t('After content is passed through the bayesian filter, we get back a value from 1 to 99. 1 means that there is a 1% chance this content is spam. 99 means that there is a 99% chance this content is spam. The threshold defines at what probability we require content to be before labeling it as spam. It is best to keep this value high to minimize false positives.'));
238 $inner_group .= form_select(t('Assign unknown token probability'), 'spam_unknown_probability', variable_get('spam_unknown_probability', 40), array(10 => 10, 20 => 20, 30 => 30, 40 => 40, 50 => 50, 60 => 60, 70 => 70, 80 => 80, 90 => 90), t('To caculate whether or not given content is spam we look at each word and based on how often the words has been speen in spam content versus non-spam content. If we\'ve never seen the word before, we arbitrarily assign this value to it. The lower the value, the more you trust new content will not be spam.'));
239 $inner_group .= form_select(t('Examine how many words'), 'spam_interesting_tokens', variable_get('spam_interesting_tokens', 15), array(5 => 5, 10 => 10, 15 => 15, 25 => 25, 35 => 35, 50 => 50, 75 => 75, 100 => 100), t('When determining if content is spam or not we only pass a finite number of the "most interesting" words into the Bayesian filter. The more spam-like or non-spam-like the word, the "more interesting". That is, a word that has a 50% chance of being spam is "least interesting", whereas words that have 1% chance of being spam or 99% chance of being spam are "most interesting".'));
240 $inner_group .= form_select(t('Training method'), 'spam_train_method', variable_get('spam_train_method', 0), array('TOE (Train On Error)', 'TEFT (Train Everything)'), t('TOE, or Train on Error, means that the bayesian filter will only learn new words when it is told that it mislabeled content. TEFT, or Train Everything means that the Bayesian filter will automatically learn from every piece of content it sees. TOE is recommended as it tends to be more accurate, avoiding artificial predjudices often introduced by TEFT.'));
241 $inner_group .= form_select(t('Probability calculation method'), 'spam_waited_probability', variable_get('spam_waited_probability', 1), array(0 => 'simple', 1 => 'weighted'), t('Choosing the "simple" method will calculate the probability that each token is spam by dividing the number of times the token has been seen in spam content by the number of times the token has been seen in all content. Choosing the "weighted" method will use a different algorithm that is quicker to consider a word as more likely to be spam or non-spam.'));
242 $inner_output .= form_group('Bayesian logic (advanced)', $inner_group);
243 $group .= $inner_output;
244 }
245 $output .= form_group('Advanced configuration', $group);
246
247 return $output;
248 }
249
250 function spam_link($type, $node = 0, $main = 0) {
251 $links = array();
252 if ($type == 'comment') {
253 if ($spam = theme('spam_link', $node, 'comment')) {
254 $links[] = $spam;
255 }
256 }
257 if ($type == 'node') {
258 if ($spam = theme('spam_link', $node, 'node')) {
259 $links[] = $spam;
260 }
261 }
262
263 return ($links);
264 }
265
266 function spam_menu($may_cache) {
267 $items = array();
268 if ($may_cache) {
269 $items[] = array('path' => 'admin/comment/list/spam', 'title' => t('spam'),
270 'access' => user_access('administer spam rating'),
271 'callback' => 'spam_admin', 'type' => MENU_LOCAL_TASK);
272 $items[] = array('path' => 'admin/node/spam', 'title' => t('spam'),
273 'access' => user_access('administer spam rating'),
274 'callback' => 'spam_admin', 'type' => MENU_LOCAL_TASK);
275 $items[] = array('path' => 'admin/spam', 'title' => t('spam'),
276 'access' => user_access('administer spam rating'),
277 'callback' => 'spam_admin');
278 $items[] = array('path' => 'admin/spam/statistics',
279 'title' => t('statistics'), 'weight' => -1,
280 'access' => user_access('administer spam rating'),
281 'callback' => 'spam_admin', 'type' => MENU_LOCAL_TASK);
282 $items[] = array('path' => 'admin/spam/custom',
283 'title' => t('custom filters'),
284 'access' => user_access('administer spam rating'),
285 'callback' => 'spam_admin', 'type' => MENU_LOCAL_TASK);
286 $items[] = array('path' => 'admin/spam/urls',
287 'title' => t('URL filters'),
288 'access' => user_access('administer spam rating'),
289 'callback' => 'spam_admin', 'type' => MENU_LOCAL_TASK);
290
291 $items[] = array('path' => 'admin/spam/comment', 'title' => t('comment'),
292 'access' => user_access('administer spam rating'),
293 'callback' => 'spam_admin', 'type' => MENU_CALLBACK);
294 $items[] = array('path' => 'admin/spam/node', 'title' => t('node'),
295 'access' => user_access('administer spam rating'),
296 'callback' => 'spam_admin', 'type' => MENU_CALLBACK);
297 $items[] = array('path' => 'admin/spam/rebuild', 'title' => t('rebuild'),
298 'access' => user_access('administer spam rating'),
299 'callback' => 'spam_admin', 'type' => MENU_HIDE);
300 $items[] = array('path' => 'spam', 'title' => t('spam'),
301 'access' => user_access('administer spam rating'),
302 'callback' => 'spam_page', 'type' => MENU_CALLBACK);
303 }
304 return $items;
305 }
306
307 function spam_admin_comment_overview() {
308 drupal_set_title(t('Spam comments'));
309
310 $operations = array(
311 array(t('Mark the selected comments as not spam'), 'spam_admin_mark_comment_notspam'),
312 array(t('Unpublish the selected comments'), 'spam_admin_unpublish_comment'),
313 array(t('Publish the selected comments'), 'spam_admin_publish_comment'),
314 array(t('Delete the selected comments (no confirmation)'), 'spam_admin_delete_comment')
315 );
316
317 $op = $_POST['op'];
318
319 if ($op == t('Update comments') && isset($_POST['edit']['operation']) && isset($_POST['edit']['status'])) {
320 $function = $operations[$_POST['edit']['operation']][1];
321 foreach ($_POST['edit']['status'] as $cid => $value) {
322 if ($value) {
323 $function(spam_load_comment(($cid)));
324 }
325 }
326
327 drupal_set_message(t('the update has been performed.'));
328 }
329
330 $header = array(
331 array('data' => t('select')),
332 array('data' => t('subject'), 'field' => 'subject'),
333 array('data' => t('author'), 'field' => 'u.name'),
334 array('data' => t('hostname'), 'field' => 'c.hostname'),
335 array('data' => t('status'), 'field' => 'status'),
336 array('data' => t('time'), 'field' => 'c.timestamp', 'sort' => 'desc'),
337 array('data' => t('operations'), 'colspan' => 2)
338 );
339
340 $sql = "SELECT c.*, s.cid, s.spam FROM {spam_comments} s, {comments} c WHERE s.cid = c.cid AND s.spam = 1";
341 $sql .= tablesort_sql($header);
342 $result = pager_query($sql, 50);
343
344 // Make sure the update controls are disabled if we don't have any rows
345 // to select from.
346 $disabled = !db_num_rows($result);
347
348 $options = array();
349 foreach ($operations as $key => $value) {
350 $options[] = $value[0];
351 }
352
353 $form = form_select(NULL, 'operation', 0, $options, NULL, ($disabled ? 'disabled="disabled"' : ''));
354 $form .= form_submit(t('Update comments'), 'op', ($disabled ? array('disabled' => 'disabled') : array()));
355
356 $output .= '<h3>'. t('Update options') .'</h3>';
357 $output .= "<div class=\"container-inline\">$form</div>";
358
359 while ($comment = db_fetch_object($result)) {
360 $rows[] = array(form_checkbox(NULL, "status][$comment->cid", 1, 0), l($comment->subject, "node/$comment->nid", array('title' => htmlspecialchars(substr($comment->comment, 0, 128))), NULL, "comment-$comment->cid") .' '. (node_last_viewed($comment->nid) < $comment->timestamp ? theme('mark') : ''), format_name($comment), $comment->hostname, ($comment->status == 0 ? t('published') : t('not published')) .'</td><td>'. format_date($comment->timestamp, 'small'), l(t('edit'), "admin/comment/edit/$comment->cid"), l(t('delete'), "admin/comment/delete/$comment->cid"));
361 }
362
363 if ($pager = theme('pager', NULL, 50, 0, tablesort_pager())) {
364 $rows[] = array(array('data' => $pager, 'colspan' => 6));
365 }
366
367 $output .= theme('table', $header, $rows);
368 return form($output);
369 }
370
371 function spam_admin_node_overview() {
372 drupal_set_title(t('Spam nodes'));
373
374 $operations = array(
375 array(t('Mark the selected posts as not spam'), 'spam_admin_mark_node_notspam'),
376 array(t('Unpublish the selected posts'), 'spam_admin_unpublish_node'),
377 array(t('Publish the selected posts'), 'spam_admin_publish_node'),
378 array(t('Delete the selected posts (no confirmation)'), 'spam_admin_delete_node')
379 );
380
381 $op = $_POST['op'];
382
383 if ($op == t('Update nodes') && isset($_POST['edit']['operation']) && isset($_POST['edit']['status'])) {
384 $function = $operations[$_POST['edit']['operation']][1];
385 foreach ($_POST['edit']['status'] as $nid => $value) {
386 if ($value) {
387 $function(node_load(array('nid' => $nid)));
388 }
389 }
390
391 drupal_set_message(t('the update has been performed.'));
392 }
393
394 $header = array(
395 array('data' => t('select')),
396 array('data' => t('title'), 'field' => 'title'),
397 array('data' => t('type'), 'field' => 'type'),
398 array('data' => t('author'), 'field' => 'u.name'),
399 array('data' => t('hostname'), 'field' => 'hostname'),
400 array('data' => t('status'), 'field' => 'status'),
401 array('data' => t('time'), 'field' => 'n.changed', 'sort' => 'desc'),
402 array('data' => t('operations'), 'colspan' => 3)
403 );
404 $sql = 'SELECT n.*, u.name, u.uid, s.nid, s.spam, s.hostname FROM {spam_nodes} s, {node} n, {users} u WHERE n.uid = u.uid AND s.nid = n.nid AND s.spam = 1';
405 $sql .= tablesort_sql($header);
406 $result = pager_query($sql, 50);
407
408 // Make sure the update controls are disabled if we don't have any rows
409 // to select from.
410 $disabled = !db_num_rows($result);
411
412 $options = array();
413 foreach ($operations as $key => $value) {
414 $options[] = $value[0];
415 }
416
417 $form = form_select(NULL, 'operation', 0, $options, NULL, ($disabled ? 'disabled="disabled"' : ''));
418 $form .= form_submit(t('Update nodes'), 'op', ($disabled ? array('disabled' => 'disabled') : array()));
419
420 $output .= '<h3>'. t('Update options') .'</h3>';
421 $output .= "<div class=\"container-inline\">$form</div>";
422
423 // Overview table:
424 while ($node = db_fetch_object($result)) {
425 $rows[] = array(form_checkbox(NULL, "status][$node->nid", 1, 0), l($node->title, "node/$node->nid") .' '. (node_last_viewed($node->nid) < $node->changed ? theme_mark() : ''), node_invoke($node, 'node_name'), format_name($node), $node->hostname, ($node->status ? t('published') : t('not published')), format_date($node->changed, 'small'), l(t('edit node'), "node/$node->nid/edit"), l(t('delete node'), "admin/node/delete/$node->nid"));
426 }
427
428 if ($pager = theme('pager', NULL, 50, 0)) {
429 $rows[] = array(array('data' => $pager, 'colspan' => 7));
430 }
431
432 $output .= theme('table', $header, $rows);
433 return form($output);
434 }
435
436 function spam_admin_statistics() {
437 // TODO: cleanup this page:
438 // - get rid of hard-coded html
439 // - reveal more of the statistics, where possible
440 // - cleanup logic to determine accuracy
441 $stats = spam_get_statistics();
442
443 // overview
444 $output = '<p><b>'. t('Overview') .':</b><br />';
445 if (variable_get('spam_statistics', 1)) {
446 if ($stats['spam']->value > 0) {
447 $output .= t('Spam comments and content: %total<br />', array('%total' => (int)$stats['spam']->value));
448 $output .= t('Last spam posted: %time on %date<br />', array('%time' => format_date((int)$stats['spam']->last, 'custom', 'g:i a'), '%date' => format_date((int)$stats['spam']->last, 'custom', 'l, F j, Y')));
449 // TODO: fix rebuild logic
450 //$output .= ' <small>['. l(t('rebuild filter'), 'admin/spam/rebuild/all') .']</small>';
451 }
452
453 // comments
454 $output .= '</p><p><b>'. t('Spam comments') .':</b><br />';
455 if ($stats['spam_comment']->value > 0) {
456 $output .= t('Spam comments: %total<br />', array('%total' => (int)$stats['spam_comment']->value));
457 $output .= t('Last spam comment posted: %time on %date<br />', array('%time' => format_date((int)$stats['spam_comment']->last, 'custom', 'g:i a'), '%date' => format_date((int)$stats['spam_comment']->last, 'custom', 'l, F j, Y')));
458 $output .= t('Deleted spam comments: %total (%auto automatically)<br />', array('%total' => (int)$stats['delete_comment']->value, '%auto' => (int)$stats['auto_delete_comment']->value));
459 $output .= ' <small>['. l(t('view undeleted comment spam'), 'admin/comment/list/spam') .']</small>';
460 }
461 else {
462 $output .= t('This site has not had any spam comment postings.');
463 }
464
465 // content (nodes)
466 $output .= '</p><p><b>'. t('Spam content') .':</b><br />';
467 $total = 0;
468 $node_types = node_list();
469 foreach ($node_types as $type) {
470 $spam["$type"] = $stats["spam_$type"]->value;
471 $total = $total + $stats["spam_$type"]->value;
472 }
473 if ($total) {
474 $types = array();
475 foreach ($spam as $type => $num) {
476 if ($num) {
477 $output .= t('');
478 $types[] = t('%num spam %type %posting', array('%num' => (int)$num, '%type' => $type, '%posting' => format_plural($num, 'posting', 'postings')));
479 $last[] = t(' The last spam %type posting was at %time on %date.', array('%type' => $type, '%time' => format_date($stats["spam_$type"]->last, 'custom', 'g:i a'), '%date' => format_date($stats["spam_$type"]->last, 'custom', 'l, F j, Y')));
480 }
481 }
482 $n = count($types);
483 if ($n == 1) {
484 $spam_types = $types[0];
485 }
486 else {
487 for ($i = 0; $i < ($n - 1); $i++) {
488 $spam_types .= $types[$i] .', ';
489 }
490 $spam_types .= 'and '. $types[$n-1];
491 }
492 $output .= t('This site has had a combined total of %spam_content spam node %posting in the form of %spam_types.', array('%spam_content' => (int)$total, '%spam_types' => $spam_types, '%posting' => format_plural($total, 'posting', 'postings')));
493 foreach ($last as $l) {
494 $output .= $l;
495 }
496 $output .= ' <small>['. l(t('view content spam'), 'admin/node/spam') .']</small>';
497 }
498 else {
499 $output .= t('This site has not had any content spam.');
500 }
501 }
502 else {
503 $output .= t('This module is currently configured to not collect statistics.');
504 }
505 $output .= '</p>';
506 return $output;
507 }
508
509 function spam_get_statistics() {
510 $spam_statistics = array();
511
512 $result = db_query('SELECT name, value, last FROM {spam_statistics}');
513 while ($statistic = db_fetch_object($result)) {
514 $spam_statistics["$statistic->name"] = $statistic;
515 }
516
517 return ($spam_statistics);
518 }
519
520 function spam_admin_urls($edit = array()) {
521
522 if (variable_get('spam_filter_urls', 1) == 0) {
523 $group = t('The URL filtering functionality provided by this module is currently disabled. You can configure URL filters below, but they will not function until you check "Filter spammer URLs" at %url.', array('%url' => l(t('administer &raquo; settings &raquo spam'), 'admin/settings/spam')));
524 $output = form_group(t('Notice'), $group);
525 }
526
527 if (!empty($edit)) {
528 $edit['url'] = preg_replace('/^URL\*/', '', $edit['token']);
529 }
530
531 $header = array(
532 array('data' => t('domain'), 'field' => 'token', 'sort' => 'asc'),
533 array('data' => t('spam matches'), 'field' => 'spam'),
534 array('data' => t('not spam matches'), 'field' => 'notspam'),
535 array('data' => t('spam probability'), 'field' => 'probability'),
536 array('data' => t('last match'), 'field' => 'last'),
537 array('data' => t('operations'), 'colspan' => 2)
538 );
539
540 $sql = 'SELECT * FROM {spam_tokens} WHERE probability > '. variable_get('spam_threshold', 80) ." AND token LIKE 'URL%%'";
541 $sql .= tablesort_sql($header);
542 $result = pager_query($sql, 25);
543
544 while ($url = db_fetch_object($result)) {
545 $rows[] = array(
546 $url->url = htmlspecialchars(preg_replace('/^URL\*/', '', $url->token)),
547 $url->spam,
548 $url->notspam,
549 $url->probability .'%',
550 format_date($url->last, 'small'),
551 l(t('edit'), htmlspecialchars("admin/spam/urls/$url->url/edit")),
552 l(t('delete'), htmlspecialchars("admin/spam/urls/$url->url/delete"))
553 );
554 }
555
556 if ($pager = theme('pager', NULL, 25, 0, tablesort_pager())) {
557 $rows[] = array(array('data' => $pager, 'colspan' => 6));
558 }
559
560 $group = theme('table', $header, $rows);
561 $output .= form_group(t('URL filters'), $group);
562
563 $group = form_textfield(t('Domain'), 'url', $edit['url'], 45, 255, t('Enter a domain name that if found in new site content will cause the content to be marked as spam. For example if you enter "spam.com" as a domain name, a comment containing the URL "http://spam.com/stuff/for/sale" will be automatically marked as spam.'));
564 if (empty($edit['url'])) {
565 $group .= form_submit(t('Add URL filter'));
566 $output .= form_group(t('Add new URL filter'), $group);
567 }
568 else {
569 $group .= form_submit(t('Edit URL filter'));
570 $group .= form_hidden('token', $edit['token']);
571 $output .= form_group(t('Edit URL filter'), $group);
572 }
573
574 return form($output, 'post', url('admin/spam/urls'));
575 }
576
577 function _spam_load_urls($url, $type = 'object') {
578 $result = db_query("SELECT * FROM {spam_tokens} WHERE token = 'URL*%s'", $url);
579 if ($type == 'object') {
580 $url = db_fetch_object($result);
581 }
582 else {
583 $url = db_fetch_array($result);
584 }
585 return $url;
586 }
587
588 function spam_admin_urls_edit($edit = array(), $action = 'add') {
589 if (!empty($edit['url'])) {
590 if ($action == 'edit') {
591 db_query("UPDATE {spam_tokens} SET token = 'URL*%s' WHERE token = '%s'", $edit['url'], $edit['token']);
592 drupal_set_message(t('URL filter "%filter" updated.', array('%filter' => htmlspecialchars($edit['url']))));
593 }
594 else { // add
595 $duplicate = db_fetch_object(db_query("SELECT token FROM {spam_tokens} WHERE token = 'URL*%s'", $edit['url']));
596 // there's no reason to allow duplicate filters
597 if ($duplicate->token) {
598 drupal_set_message(t('URL filter "%filter" already exists.', array('%filter' => htmlspecialchars($edit['url']))), 'error');
599 }
600 else {
601 db_query("INSERT INTO {spam_tokens} (token, probability) VALUES('URL*%s', %d)", $edit['url'], 99);
602 drupal_set_message(t('URL filter "%filter" added.', array('%filter' => htmlspecialchars($edit['url']))));
603 }
604 }
605 }
606 }
607
608 function spam_admin_urls_delete_confirm($edit = array()) {
609 $edit['url'] = preg_replace('/^URL\*/', '', $edit['token']);
610 $group = t('Are you sure you want to delete the "%filter" URL filter?', array('%filter' => htmlspecialchars($edit['url']))) .'<br />';
611 $group .= form_hidden('token', $edit['token']);
612 $group .= form_submit(t('Delete URL filter'));
613 $output = form_group('Confirm URL filter deletion', $group);
614 return form($output, 'post', url('admin/spam/urls'));
615 }
616
617 function spam_admin_urls_delete($edit) {
618 db_query("DELETE FROM {spam_tokens} WHERE token = '%s'", $edit['token']);
619 drupal_set_message(t('URL filter deleted.'));
620 }
621
622 function spam_admin_custom_scan_comments($filter, $cid) {
623 $operations = array(
624 array(t('Mark the selected comments as spam'), 'spam_admin_mark_comment_spam'),
625 array(t('Mark the selected comments as not spam'), 'spam_admin_mark_comment_notspam'),
626 array(t('Unpublish the selected comments'), 'spam_admin_unpublish_comment'),
627 array(t('Publish the selected comments'), 'spam_admin_publish_comment'),
628 array(t('Delete the selected comments (no confirmation)'), 'spam_admin_delete_comment')
629 );
630 $op = $_POST['op'];
631
632 if ($op == t('Update scanned comments') && isset($_POST['edit']['operation']) && isset($_POST['edit']['status'])) {
633 $function = $operations[$_POST['edit']['operation']][1];
634 foreach ($_POST['edit']['status'] as $cid => $value) {
635 if ($value) {
636 $function(spam_load_comment(($cid)));
637 }
638 }
639
640 drupal_set_message(t('the update has been performed.'));
641 }
642
643 $options = array();
644 foreach ($operations as $key => $value) {
645 $options[] = $value[0];
646 }
647
648 $rows = array();
649 $header = array(
650 array('data' => ' '),
651 array('data' => t('rating'), 'field' => 'spam', 'sort' => 'asc'),
652 array('data' => t('status'), 'field' => 'status'),
653 array('data' => t('subject'), 'field' => 'subject'),
654 array('data' => t('comment'), 'field' => 'comment'),
655 array('data' => t('operations'), 'colspan' => 2)
656 );
657 if ($filter->regex) {
658 // try and convert perl regex to MySQL regex
659 $boundary = substr($filter->filter, 0, 1);
660 $contents = "/\\$boundary(.*)\\$boundary/";
661 preg_match($contents, $filter->filter, $regex);
662 // TODO: find out if posgresql supports regex?
663 $sql = "SELECT c.cid, c.status, c.nid, c.subject, c.comment, s.spam FROM {comments} c LEFT JOIN {spam_comments} s ON c.cid = s.cid WHERE subject REGEXP '". $regex[1] ."' OR comment REGEXP '". $regex[1] ."' OR name REGEXP '". $regex[1] ."' OR mail REGEXP '". $regex[1] ."' OR homepage REGEXP '". $regex[1] ."'";
664 }
665 else {
666 $sql = "SELECT c.cid, c.nid, c.status, c.subject, c.comment, s.spam FROM {comments} c LEFT JOIN {spam_comments} s ON c.cid = s.cid WHERE subject LIKE '%%". $filter->filter ."%%' OR comment LIKE '%%". $filter->filter ."%%' OR name LIKE '%%". $filter->filter ."%%' OR mail LIKE '%%". $filter->filter ."%%' OR homepage LIKE '%%". $filter->filter ."%%'";
667 }
668 $sql .= tablesort_sql($header);
669 $result = pager_query($sql, 10);
670
671 while ($comment = db_fetch_object($result)) {
672 $rows[] = array(
673 form_checkbox(NULL, "status][$comment->cid", 1, 0),
674 $comment->spam ? t('spam') : t('not spam'),
675 $comment->status == 0 ? t('published') : t('not published'),
676 strlen($comment->subject) > 128 ? htmlspecialchars(substr($comment->subject, 0, 128)). t('...') : htmlspecialchars($comment->subject),
677 strlen($comment->comment) > 256 ? htmlspecialchars(substr($comment->comment, 0, 256)). t('...') : htmlspecialchars($comment->comment),
678 l(t('view'), "node/$comment->nid#comment-$comment->cid"),
679 $comment->spam ? l(t('mark as not spam'), "spam/comment/$comment->cid/notspam") : l(t('mark as spam'), "spam/comment/$comment->cid/spam")
680 );
681 }
682
683 if ($pager = theme('pager', NULL, 10, 0, tablesort_pager())) {
684 $rows[] = array(array('data' => $pager, 'colspan' => 6));
685 }
686
687 $disabled = !db_num_rows($result);
688 $form = form_select(NULL, 'operation', 0, $options, NULL, ($disabled ? 'disabled="disabled"' : ''));
689 $form .= form_submit(t('Update scanned comments'), 'op', ($disabled ? array('disabled' => 'disabled') : array()));
690 $form .= form_hidden('scan', $filter->scid);
691
692 $output = '<h3>'. t('Scan result options') .'</h3>';
693 $output .= "<div class=\"container-inline\">$form</div>";
694
695 if ($disabled) {
696 $output .= '<p>'. t('No matching comments.') .'</p>';
697 }
698 else {
699 $group = theme('table', $header, $rows);
700 $output .= form_group(t('Matching comments'), $group);
701 }
702
703 return form($output);
704 }
705
706 // TODO:
707 /*
708 * function spam_admin_custom_scan_content($filter, $nid) {
709 * }
710 */
711
712 function spam_admin_custom($edit = array()) {
713
714 $header = array(
715 array('data' => t('filter'), 'field' => 'filter', 'sort' => 'asc'),
716 array('data' => t('type'), 'field' => 'regex'),
717 array('data' => t('effect'), 'field' => 'effect'),
718 array('data' => t('delete'), 'field' => 'autodelete'),
719 array('data' => t('matches'), 'field' => 'matches'),
720 array('data' => t('last match'), 'field' => 'last'),
721 array('data' => t('operations'), 'colspan' => 3)
722 );
723
724 $sql = 'SELECT * FROM {spam_custom}';
725 $sql .= tablesort_sql($header);
726 $result = pager_query($sql, 25);
727
728 $effects = array(t('always spam'), t('usually spam'), t('usually not spam'), t('never spam'));
729
730 while ($custom = db_fetch_object($result)) {
731 $rows[] = array(
732 htmlspecialchars($custom->filter),
733 $custom->regex ? t('regex') : t('plain text'),
734 $effects["$custom->effect"],
735 $custom->autodelete ? t('enabled') : t('disabled'),
736 $custom->matches,
737 format_date($custom->last, 'small'),
738 l(t('scan'), "admin/spam/custom/$custom->scid/scan#scan-results"),
739 l(t('edit'), "admin/spam/custom/$custom->scid/edit#edit-filter"),
740 l(t('delete'), "admin/spam/custom/$custom->scid/delete")
741 );
742 }
743
744 if ($pager = theme('pager', NULL, 25, 0, tablesort_pager())) {
745 $rows[] = array(array('data' => $pager, 'colspan' => 7));
746 }
747
748 $group = theme('table', $header, $rows);
749 $output = form_group(t('Custom filters'), $group);
750
751 if ((($id = (int)arg(3)) && (arg(4) == 'scan')) ||
752 ($id = $edit['scan'])) {
753 $output .= '<a name="scan-results"></a>';
754 $filter = db_fetch_object(db_query("SELECT scid,filter,regex FROM {spam_custom} WHERE scid = %d", $id));
755 $output .= spam_admin_custom_scan_comments($filter, $id);
756 }
757 else {
758 $group = '<a name="edit-filter"></a>';
759 $group .= form_textfield(t('Custom filter'), 'filter', $edit['filter'], 45, 255, t('Enter a custom filter string. You can enter a word, a phrase, or a complete regular expression. All new content that is being scanned for spam will also be tested against your custom filters.'));
760 $group .= form_checkbox(t('Regular expression'), 'regex', 1, $edit['regex'], t('Check this box if the above custom filter should be treated as a regular expression. This module uses <a href="http://www.php.net/manual/en/ref.pcre.php">Perl-compatible regular expressions</a>. As a simple example to do a case-insensitve match on the word "viagra", you would enter (without the quotes) "<code>/viagra/i</code>".'));
761 $group .= form_select(t('Match effect'), 'effect', $edit['effect'], array(t('always spam'), t('usually spam'), t('usually not spam'), t('never spam')), t('Define the effect when your custom filter matches on new content. If your filter defines "always spam", this increases the chances the new content will be marked spam by 200%. If your filter defines "usually spam", this increases the chances the new content will be marked spam by 50%. If your filter defines "usually not spam", this decreases the chances the new content will be marked spam by 50%. And if your filter defines "never spam", this decreases the chances the new content will be marked spam by 200%. Note that it is possible to match both an "always spam" and a "never spam" filter with the same content, and that then the filters will cancel each other out. Additionally, four "usually not spam" matches will cancel out one "always spam" match.'));
762 $group .= form_checkbox(t('Automatically delete spam'), 'autodelete', 1, $edit['autodelete'], t('Checking this box will cause any new content that matches this filter and ultimately is determined to be spam to be automatically (and silently) deleted. It is not recommended that you enable this option unless you are fully confident that it will never match non-spam content.'));
763 if (empty($edit['filter'])) {
764 $group .= form_submit(t('Add filter'));
765 $output .= form_group(t('Add new custom filter'), $group);
766 }
767 else {
768 $group .= form_hidden('scid', $edit['scid']);
769 $group .= form_submit(t('Edit filter'));
770 $output .= form_group(t('Edit custom filter'), $group);
771 }
772 }
773
774 return form($output, 'post', url('admin/spam/custom'));
775 }
776
777 function spam_admin_custom_add($edit = array()) {
778 if (!empty($edit['filter'])) {
779 // validate if regex
780 if ($edit['regex'] && preg_match($edit['filter'], 'test') === FALSE) {
781 /* failed regex validation is a critical error and things break, so we
782 ** just echo an error and exit. (If we don't exit, additional errors
783 ** appear about modifying headers making it confusing)
784 */
785 echo t('Your regular expression "%regex" does not validate. Please press the back button on your browser to fix the error you see above.', array('%regex' => $edit['filter']));
786 exit (1);
787 }
788 if ($edit['scid']) {
789 db_query("UPDATE {spam_custom} SET filter = '%s', regex = %d, effect = %d, autodelete = %d WHERE scid = %d", $edit['filter'], $edit['regex'], $edit['effect'], $edit['autodelete'], $edit['scid']);
790 drupal_set_message(t('Custom filter "%filter" updated.', array('%filter' => htmlspecialchars($edit['filter']))));
791 }
792 else {
793 // there's no reason to allow duplicate filters
794 $duplicate = db_fetch_object(db_query("SELECT scid FROM {spam_custom} WHERE filter = '%s'", $edit['filter']));
795 if ($duplicate->scid) {
796 drupal_set_message(t('Custom filter "%filter" already exists.', array('%filter' => htmlspecialchars($edit['filter']))), 'error');
797 }
798 else {
799 db_query("INSERT INTO {spam_custom} (filter, regex, effect, autodelete) VALUES('%s', %d, %d, %d)", $edit['filter'], $edit['regex'], $edit['effect'], $edit['autodelete']);
800 drupal_set_message(t('Custom filter "%filter" added.', array('%filter' => htmlspecialchars($edit['filter']))));
801 }
802 }
803 }
804 }
805
806 function _spam_load_custom($scid, $type = 'object') {
807 $result = db_query('SELECT * FROM {spam_custom} WHERE scid = %d', $scid);
808 if ($type == 'object') {
809 $custom = db_fetch_object($result);
810 }
811 else {
812 $custom = db_fetch_array($result);
813 }
814 return $custom;
815 }
816
817 function spam_admin_custom_delete_confirm($edit = array()) {
818 $group = t('Are you sure you want to delete the "%filter" filter?', array('%filter' => htmlspecialchars($edit['filter']))) .'<br />';
819 $group .= form_hidden('scid', $edit['scid']);
820 $group .= form_submit(t('Delete filter'));
821 $output = form_group('Confirm filter deletion', $group);
822 return form($output, 'post', url('admin/spam/custom'));
823 }
824
825 function spam_admin_custom_delete($scid) {
826 db_query('DELETE FROM {spam_custom} WHERE scid = %d', $scid);
827 drupal_set_message(t('Filter deleted.'));
828 }
829
830 function spam_admin() {
831 $op = $_POST['op'];
832 $edit = $_POST['edit'];
833
834 if (empty($op)) {
835 $op = arg(1);
836 }
837
838 switch ($op) {
839 case 'spam':
840 switch (arg(2)) {
841 case 'rebuild':
842 $output = spam_admin_rebuild_check(arg(3));
843 break;
844 case 'custom':
845 switch (arg(4)) {
846 case 'edit':
847 $edit = _spam_load_custom(arg(3), 'array');
848 $output = spam_admin_custom($edit);
849 break;
850 case 'delete':
851 $edit = _spam_load_custom(arg(3), 'array');
852 $output = spam_admin_custom_delete_confirm($edit);
853 break;
854 default:
855 $output = spam_admin_custom();
856 break;