PluginProbe
wpForo Forum / 2.4.13
wpForo Forum v2.4.13
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / classes / Moderation.php

Moderation.php in wpForo Forum 2.4.13, at classes/Moderation.php

503 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\classes;
4
5 use Akismet;
6 use wpforo\admin\listtables\Moderations as ModerationsListTable;
7
8 // Exit if accessed directly
9 if( ! defined( 'ABSPATH' ) ) exit;
10
11 class Moderation {
12 public $post_statuses;
13 public $list_table;
14
15 public function __construct() {
16 $this->post_statuses = apply_filters( 'wpforo_post_statuses', [ 0 => 'approved', 1 => 'unapproved' ] );
17 add_action( 'wpforo_after_change_board', function() {
18 if( ! is_null( WPF()->wp_current_user ) ) $this->init();
19 } );
20 add_action( 'wpforo_after_post_report', function( $postid ) {
21 $this->after_post_report( $postid );
22 } );
23 }
24
25 private function init() {
26 if( is_admin() ) add_action( 'wpforo_after_init', [ $this, 'init_list_table' ] );
27
28 if( ! WPF()->usergroup->can( 'aup' ) ) {
29 add_filter( 'wpforo_add_topic_data_filter', [ &$this, 'auto_moderate' ] );
30 add_filter( 'wpforo_add_post_data_filter', [ &$this, 'auto_moderate' ] );
31 } else {
32 if( WPF()->member->current_user_is_new() ) {
33 if( class_exists( 'Akismet' ) ) {
34 add_filter( 'wpforo_add_topic_data_filter', [ &$this, 'akismet_topic' ], 8 );
35 add_filter( 'wpforo_edit_topic_data_filter', [ &$this, 'akismet_topic' ], 8 );
36 add_filter( 'wpforo_add_post_data_filter', [ &$this, 'akismet_post' ], 8 );
37 add_filter( 'wpforo_edit_post_data_filter', [ &$this, 'akismet_post' ], 8 );
38 }
39 if( wpforo_setting( 'antispam', 'spam_filter' ) ) {
40 add_filter( 'wpforo_add_topic_data_filter', [ &$this, 'spam_topic' ], 9 );
41 add_filter( 'wpforo_edit_topic_data_filter', [ &$this, 'spam_topic' ], 9 );
42 add_filter( 'wpforo_add_topic_data_filter', [ &$this, 'spam_post' ], 9 );
43 add_filter( 'wpforo_edit_topic_data_filter', [ &$this, 'spam_post' ], 9 );
44 add_filter( 'wpforo_add_post_data_filter', [ &$this, 'spam_post' ], 9 );
45 add_filter( 'wpforo_edit_post_data_filter', [ &$this, 'spam_post' ], 9 );
46 }
47 }
48 if( wpforo_setting( 'antispam', 'spam_filter' ) ) {
49 add_filter( 'wpforo_add_topic_data_filter', [ &$this, 'auto_moderate' ], 10 );
50 add_filter( 'wpforo_add_post_data_filter', [ &$this, 'auto_moderate' ], 10 );
51 }
52 if( ! WPF()->perm->can_link() ) {
53 add_filter( 'wpforo_add_topic_data_filter', [ &$this, 'remove_links' ], 20 );
54 add_filter( 'wpforo_edit_topic_data_filter', [ &$this, 'remove_links' ], 20 );
55 add_filter( 'wpforo_add_post_data_filter', [ &$this, 'remove_links' ], 20 );
56 add_filter( 'wpforo_edit_post_data_filter', [ &$this, 'remove_links' ], 20 );
57 }
58 }
59 }
60
61 public function init_list_table() {
62 if( wpfval( $_GET, 'page' ) === wpforo_prefix_slug( 'moderations' ) ) {
63 $this->list_table = new ModerationsListTable();
64 $this->list_table->prepare_items();
65 }
66 }
67
68 public function get_post_status_dname( $status ) {
69 $status = intval( $status );
70
71 return ( isset( $this->post_statuses[ $status ] ) ? $this->post_statuses[ $status ] : $status );
72 }
73
74 public function get_moderations( $args, &$items_count = 0 ) {
75 if( isset( $_GET['filter_by_userid'] ) && wpforo_bigintval( $_GET['filter_by_userid'] ) ) $args['userid'] = wpforo_bigintval( $_GET['filter_by_userid'] );
76 $filter_by_status = intval( ( isset( $_GET['filter_by_status'] ) ? $_GET['filter_by_status'] : 1 ) );
77 $args['status'] = $filter_by_status;
78 if( ! isset( $_GET['order'] ) ) $args['orderby'] = '`created` DESC, `postid` DESC';
79
80 return WPF()->post->get_posts( $args, $items_count );
81 }
82
83 public function search( $needle, $fields = [] ) {
84 $pids = [];
85 if( $posts = WPF()->post->search( $needle ) ) {
86 foreach( $posts as $post ) {
87 $pids[] = $post['postid'];
88 }
89 }
90
91 return $pids;
92 }
93
94 public function post_approve( $postid ) {
95 return WPF()->post->set_status( $postid, 0 );
96 }
97
98 public function post_unapprove( $postid ) {
99 return WPF()->post->set_status( $postid, 1 );
100 }
101
102 public function get_view_url( $arg ) {
103 return WPF()->post->get_url( $arg );
104 }
105
106 public function akismet_topic( $item ) {
107 $post = [];
108 $post['user_ip'] = ( isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null );
109 $post['user_agent'] = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null );
110 $post['referrer'] = ( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : null );
111 $post['blog'] = get_option( 'home' );
112 $post['blog_lang'] = get_locale();
113 $post['blog_charset'] = get_option( 'blog_charset' );
114 $post['comment_type'] = 'forum-post';
115
116 if( empty( $item['forumid'] ) ) {
117 $topic = WPF()->topic->get_topic( $item['topicid'] );
118 $item['forumid'] = $topic['forumid'];
119 }
120
121 $post['comment_author'] = WPF()->current_user['user_nicename'];
122 $post['comment_author_email'] = WPF()->current_user['user_email'];
123 $post['comment_author_url'] = WPF()->member->get_profile_url( WPF()->current_userid );
124 $post['comment_post_modified_gmt'] = current_time( 'mysql', 1 );
125 $post['comment_content'] = $item['title'] . " \r\n " . $item['body'];
126 $post['permalink'] = WPF()->forum->get_forum_url( $item['forumid'] );
127
128 $response = Akismet::http_post( Akismet::build_query( $post ), 'comment-check' );
129 if( $response[1] == 'true' ) {
130 $this->ban_for_spam( WPF()->current_userid );
131 $item['status'] = 1;
132 }
133
134 return $item;
135 }
136
137 public function akismet_post( $item ) {
138 $post = [];
139 $post['user_ip'] = ( isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null );
140 $post['user_agent'] = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null );
141 $post['referrer'] = ( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : null );
142 $post['blog'] = get_option( 'home' );
143 $post['blog_lang'] = get_locale();
144 $post['blog_charset'] = get_option( 'blog_charset' );
145 $post['comment_type'] = 'forum-post';
146
147 $topic = WPF()->topic->get_topic( $item['topicid'] );
148
149 $post['comment_author'] = WPF()->current_user['user_nicename'];
150 $post['comment_author_email'] = WPF()->current_user['user_email'];
151 $post['comment_author_url'] = WPF()->member->get_profile_url( WPF()->current_userid );
152 $post['comment_post_modified_gmt'] = $topic['modified'];
153 $post['comment_content'] = $item['body'];
154 $post['permalink'] = WPF()->topic->get_url( $item['topicid'] );
155
156 $response = Akismet::http_post( Akismet::build_query( $post ), 'comment-check' );
157 if( $response[1] == 'true' ) {
158 $this->ban_for_spam( WPF()->current_userid );
159 $item['status'] = 1;
160 }
161
162 return $item;
163 }
164
165 public function spam_attachment() {
166 $default_attachments_dir = WPF()->folders['default_attachments']['dir'];
167 if( is_dir( $default_attachments_dir ) ) {
168 if( $handle = opendir( $default_attachments_dir ) ) {
169 while( false !== ( $filename = readdir( $handle ) ) ) {
170 if( $filename == '.' || $filename == '..' ) continue;
171 $file = $default_attachments_dir . DIRECTORY_SEPARATOR . $filename;
172 if( filesize( $file ) === 0 ) continue;
173 $level = $this->spam_file( $filename );
174 if( $level > 2 ) {
175 $link = '<a href="' . admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'tools' ) . '&tab=antispam#spam-files' ) . '"><strong>&gt;&gt;</strong></a>';
176 $phrase = '<strong>SPAM! - </strong>' . sprintf(
177 __(
178 'Probably spam file attachments have been detected by wpForo Spam Control. Please moderate suspected files in wpForo &gt; Settings &gt; Spam Protection.',
179 'wpforo'
180 ),
181 $link
182 );
183 WPF()->notice->add( $phrase, 'error' );
184
185 return true;
186 }
187 }
188 }
189 }
190
191 return false;
192 }
193
194 public function spam_file( $item, $type = 'file' ) {
195 if( ! isset( $item ) || ! $item ) return false;
196 $level = 0;
197 $item = strtolower( (string) $item );
198 $spam_file_phrases = [
199 0 => [ 'watch', 'movie' ],
200 1 => [ 'download', 'free' ],
201 ];
202 if( $type == 'file' ) {
203 $ext_whitelist = wpforo_setting( 'antispam', 'exclude_file_ext' );
204 $ext = strtolower( (string) pathinfo( $item, PATHINFO_EXTENSION ) );
205 $ext_risk = [ 'pdf', 'doc', 'docx', 'txt', 'htm', 'html', 'rtf', 'xml', 'xls', 'xlsx', 'php', 'cgi' ];
206 $ext_risk = wpforo_clear_array( $ext_risk, $ext_whitelist );
207 $ext_high_risk = [ 'php', 'cgi', 'exe' ];
208 $ext_high_risk = wpforo_clear_array( $ext_high_risk, $ext_whitelist );
209 if( in_array( $ext, $ext_risk ) ) {
210 $has_post = WPF()->db->get_var( "SELECT `postid` FROM `" . WPF()->tables->posts . "` WHERE `body` LIKE '%" . esc_sql( $item ) . "%' LIMIT 1" );
211 foreach( $spam_file_phrases as $phrases ) {
212 foreach( $phrases as $phrase ) {
213 if( strpos( (string) $item, $phrase ) !== false ) {
214 if( ! $has_post ) {
215 $level = 4;
216 break 2;
217 } else {
218 $level = 2;
219 break 2;
220 }
221 }
222 }
223 }
224 if( ! $level ) {
225 if( ! $has_post ) {
226 $level = 3;
227 } else {
228 if( in_array( $ext, $ext_high_risk ) ) {
229 $level = 5;
230 } else {
231 $level = 1;
232 }
233 }
234 }
235 }
236
237 return $level;
238 } elseif( $type == 'file-open' ) {
239 $ext = strtolower( (string) pathinfo( $item, PATHINFO_EXTENSION ) );
240 $allow_to_open = [ 'pdf', 'doc', 'docx', 'txt', 'rtf', 'xls', 'xlsx' ];
241 if( in_array( $ext, $allow_to_open ) ) {
242 return true;
243 } else {
244 return false;
245 }
246 }
247
248 return 0;
249 }
250
251 public function spam_topic( $topic ) {
252 if( empty( $topic ) ) return $topic;
253 if( isset( $topic['title'] ) ) {
254 $item = $topic['title'];
255 } else {
256 return $topic;
257 }
258 $len = wpforo_strlen( $item );
259 if( $len < 10 ) return $topic;
260 $item = strip_tags( (string) $item );
261 $is_similar = false;
262 $topic_args = [ 'userid' => $topic['userid'] ];
263 $topics = WPF()->topic->get_topics( $topic_args );
264 $sc_level = ( ! is_null( wpforo_setting( 'antispam', 'spam_filter_level_topic' ) ) ) ? intval( wpforo_setting( 'antispam', 'spam_filter_level_topic' ) ) : 100;
265 if( $sc_level > 100 ) $sc_level = 60;
266 $sc_level = ( 101 - $sc_level );
267 if( ! empty( $topics ) ) {
268 $count = count( $topics );
269 $keys[0] = array_rand( $topics );
270 if( $count > 1 ) $keys[1] = array_rand( $topics );
271 $check_1 = ( isset( $keys[0] ) ) ? strip_tags( (string) $topics[ $keys[0] ]['title'] ) : '';
272 $check_2 = ( isset( $keys[1] ) ) ? strip_tags( (string) $topics[ $keys[1] ]['title'] ) : '';
273 if( $check_1 ) {
274 similar_text( $item, $check_1, $percent );
275 if( $percent > $sc_level ) $is_similar = true;
276 }
277 if( $check_2 && ! $is_similar ) {
278 similar_text( $item, $check_2, $percent );
279 if( $percent > $sc_level ) $is_similar = true;
280 }
281 if( $is_similar ) {
282 $this->ban_for_spam( WPF()->current_userid );
283 $topic['status'] = 1;
284 }
285 }
286
287 return apply_filters( 'wpforo_spam_topic', $topic );
288 }
289
290 public function spam_post( $post ) {
291 if( empty( $post ) ) return $post;
292 if( isset( $post['body'] ) ) {
293 $item = $post['body'];
294 } else {
295 return $post;
296 }
297
298 $item = strip_tags( (string) $item );
299 $is_similar = false;
300 $post_args = [ 'userid' => $post['userid'] ];
301 $posts = WPF()->post->get_posts( $post_args );
302 $sc_level = ! is_null( wpforo_setting( 'antispam', 'spam_filter_level_post' ) ) ? intval( wpforo_setting( 'antispam', 'spam_filter_level_post' ) ) : 100;
303 if( $sc_level > 100 ) $sc_level = 70;
304 $sc_level = ( 101 - $sc_level );
305 if( ! empty( $posts ) ) {
306 $count = count( $posts );
307 $keys[0] = array_rand( $posts );
308 if( $count > 1 ) $keys[1] = array_rand( $posts );
309 $check_1 = ( isset( $keys[0] ) ) ? strip_tags( (string) $posts[ $keys[0] ]['body'] ) : '';
310 $check_2 = ( isset( $keys[1] ) ) ? strip_tags( (string) $posts[ $keys[1] ]['body'] ) : '';
311 if( $check_1 ) {
312 similar_text( $item, $check_1, $percent );
313 if( isset( $percent ) && $percent > $sc_level ) $is_similar = true;
314 }
315 if( $check_2 && ! $is_similar ) {
316 similar_text( $item, $check_2, $percent );
317 if( isset( $percent ) && $percent > $sc_level ) $is_similar = true;
318 }
319 if( $is_similar ) {
320 $this->ban_for_spam( WPF()->current_userid );
321 $post['status'] = 1;
322 }
323 }
324
325 return apply_filters( 'wpforo_spam_post', $post );
326 }
327
328 public function auto_moderate( $item ) {
329
330 if( empty( $item ) ) return $item;
331 if( WPF()->usergroup->can( 'em' ) ) {
332 $item['status'] = 0;
333
334 return $item;
335 }
336 if( ! WPF()->usergroup->can( 'aup' ) ) {
337 $item['status'] = 1;
338
339 return $item;
340 }
341
342 if( WPF()->member->current_user_is_new() ) {
343 if( wpforo_setting( 'antispam', 'unapprove_post_if_user_is_new' ) ) {
344 $item['status'] = 1;
345 } else {
346 $if_link_found = apply_filters( 'wpforo_new_user_post_unapproved_if_link_found', true );
347 if( $if_link_found && isset( $item['body'] ) && isset( $item['title'] ) && $this->has_link( $item ) ) {
348 $item['status'] = 1;
349 }
350 $unapproved_all = apply_filters( 'wpforo_new_user_post_unapproved_all', false );
351 if( $unapproved_all && ( ( isset( $item['status'] ) && $item['status'] == 1 ) || $this->has_unapproved( WPF()->current_userid ) ) ) {
352 $this->set_all_unapproved( WPF()->current_userid );
353 $item['status'] = 1;
354 }
355 }
356 }
357
358 // Don't track users as "a user without approved posts" if he/she has no posts.
359 // Just check the number of unapproved posts before initiating this rule,
360 // if no unapproved posts then we don't need to set the first post of this user unapproved.
361 // This checking is already done by New User options when we set "1" post for New User status and turn on the "must be manually approved" option.
362 $must_have_one_approved = apply_filters( 'wpforo_post_moderation_must_have_one_approved', true );
363 if( $must_have_one_approved && $this->has_unapproved( WPF()->current_userid ) ) {
364 // So this rule will only work from the second post,
365 // it'll always keep new posts unapproved if previous posts are not approved yet.
366 if( ! $this->has_approved( WPF()->current_userid ) ) {
367 $item['status'] = 1;
368 }
369 }
370
371 return $item;
372 }
373
374 public function has_approved( $user ) {
375 if( ! $user ) return false;
376 if( isset( $user['userid'] ) ) {
377 $userid = intval( $user['userid'] );
378 } else {
379 $userid = intval( $user );
380 }
381 $has_approved_post = WPF()->db->get_var( "SELECT `postid` FROM `" . WPF()->tables->posts . "` WHERE `userid` = '" . wpforo_bigintval( $userid ) . "' AND `status` = 0 LIMIT 1" );
382 if( $has_approved_post ) {
383 return true;
384 } else {
385 return false;
386 }
387 }
388
389 public function has_unapproved( $user ) {
390 if( empty( $user ) ) return false;
391 if( isset( $user['userid'] ) ) {
392 $userid = intval( $user['userid'] );
393 } else {
394 $userid = intval( $user );
395 }
396 $has_unapproved_post = WPF()->db->get_var( "SELECT `postid` FROM `" . WPF()->tables->posts . "` WHERE `userid` = '" . wpforo_bigintval( $userid ) . "' AND `status` = 1 LIMIT 1" );
397 if( $has_unapproved_post ) {
398 return true;
399 } else {
400 return false;
401 }
402 }
403
404 public function ban_for_spam( $userid ) {
405 if( isset( $userid ) && wpforo_setting( 'antispam', 'spam_user_ban' ) ) {
406 if( ! $this->has_approved( WPF()->current_userid ) ) {
407 WPF()->member->autoban( $userid );
408 }
409 }
410 }
411
412 public function set_all_unapproved( $userid ) {
413 if( isset( $userid ) ) {
414 WPF()->db->update( WPF()->tables->topics, [ 'status' => 1 ], [ 'userid' => intval( $userid ) ], [ '%d' ], [ '%d' ] );
415 WPF()->db->update( WPF()->tables->posts, [ 'status' => 1 ], [ 'userid' => intval( $userid ) ], [ '%d' ], [ '%d' ] );
416 }
417 }
418
419 public function remove_links( $item ) {
420 if( wpfval( $item, 'body' ) ) {
421 $domain = wpforo_get_request_uri();
422 $urls = wp_extract_urls( $item['body'] );
423 if( ! empty( $urls ) ) {
424 foreach( $urls as $k => $url ) {
425 $url = parse_url( $url );
426 if( wpfval( $url, 'host' ) ) {
427 if( strpos( (string) $domain, $url['host'] ) !== false ) unset( $urls[ $k ] );
428 }
429 }
430 if( ! empty( $urls ) ) {
431 $replace = apply_filters( 'wpforo_moderation_replace_body_links', ' <span style="color:#aaa;">' . wpforo_phrase( 'removed link', false, false ) . '</span> ', $item, $urls );
432 $item['body'] = str_replace( $urls, $replace, $item['body'] );
433 do_action( 'wpforo_moderation_remove_body_links', $item, $urls );
434 }
435 }
436 }
437 if( wpfval( $item, 'title' ) ) {
438 $domain = wpforo_get_request_uri();
439 $urls = wp_extract_urls( $item['title'] );
440 if( ! empty( $urls ) ) {
441 foreach( $urls as $k => $url ) {
442 $url = parse_url( $url );
443 if( wpfval( $url, 'host' ) ) {
444 if( strpos( (string) $domain, $url['host'] ) !== false ) unset( $urls[ $k ] );
445 }
446 }
447 if( ! empty( $urls ) ) {
448 $replace = apply_filters( 'wpforo_moderation_replace_title_links', ' -' . wpforo_phrase( 'removed link', false, false ) . '- ', $item, $urls );
449 $item['title'] = str_replace( $urls, $replace, $item['title'] );
450 do_action( 'wpforo_moderation_remove_title_links', $item, $urls );
451 }
452 }
453 }
454
455 return $item;
456 }
457
458 public function has_link( $item ) {
459 $field_urls = [];
460 $domain = wpforo_get_request_uri();
461 $title_urls = wp_extract_urls( $item['title'] );
462 $body_urls = wp_extract_urls( $item['body'] );
463 $user = ( wpfval( $item, 'userid' ) ) ? wpforo_member( $item['userid'] ) : [];
464 $signature_urls = ( wpfval( $user, 'signature' ) ) ? wp_extract_urls( $user['signature'] ) : [];
465
466 if( $fields = wpfval( $item, 'postmetas' ) ) {
467 foreach( $fields as $field ) {
468 if( ! is_scalar( $field ) ) continue;
469 $_urls = wp_extract_urls( $field );
470 if( ! empty( $_urls ) ) {
471 foreach( $_urls as $_url ) $field_urls[] = $_url;
472 }
473 }
474 }
475
476 $urls = array_merge( $title_urls, $body_urls, $signature_urls, $field_urls );
477
478 if( ! empty( $urls ) ) {
479 foreach( $urls as $k => $url ) {
480 $url = parse_url( $url );
481 if( wpfval( $url, 'host' ) ) {
482 if( strpos( (string) $domain, $url['host'] ) !== false ) unset( $urls[ $k ] );
483 }
484 }
485 }
486 if( ! empty( $urls ) ) {
487 return true;
488 }
489
490 return false;
491 }
492
493 public function get_distinct_userids( $status = 1 ) {
494 return WPF()->db->get_col( "SELECT DISTINCT `userid` FROM `" . WPF()->tables->posts . "` WHERE `status` = " . intval( $status ) );
495 }
496
497 public function after_post_report( $postid ) {
498 if( wpforo_setting( 'antispam', 'should_unapprove_after_report' ) ) {
499 $this->post_unapprove( $postid );
500 }
501 }
502 }
503