Parent Directory
|
Revision Log
|
Revision Graph
- 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 » settings » spam'), 'admin/settings/spam') .'. View all comment spam at '. l(t('administer » comments » list » spam'), 'admin/comment/list/spam') .'. View all node spam at '. l(t('administer » content » 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 » spam » 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 » settings » 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 » user » configure » 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 » comments » spam'), 'admin/comment/list/spam') .". A complete listing of all spam posts can be viewed at ". l(t('administer » content » 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 » settings » 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; |