PluginProbe
wpForo Forum / 2.3.1
wpForo Forum v2.3.1
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.3.1, at classes/Moderation.php

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