PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
3.1.6 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 All 138 releases
wpforo / classes / Permissions.php

Permissions.php in wpForo Forum 3.1.6, at classes/Permissions.php

757 lines 23.2 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 stdClass;
6 use WP_User;
7
8 // Exit if accessed directly
9 if( ! defined( 'ABSPATH' ) ) exit;
10
11 class Permissions {
12 public $default;
13 public $accesses;
14 public $cans;
15
16 function __construct() {
17 $this->init_defaults();
18 $this->init_cans();
19 $this->init();
20 add_action( 'wpforo_after_init_classes', function() {
21 if( WPF()->is_installed() ) $this->init_current_user_accesses();
22 } );
23 add_action( 'wpforo_after_change_board', function() {
24 if( WPF()->is_installed() && ! is_null( WPF()->forum ) ) $this->init_current_user_accesses();
25 } );
26 }
27
28 private function init_defaults() {
29 $this->default = new stdClass;
30 $this->default->access = [
31 'accessid' => 0,
32 'access' => '',
33 'title' => '',
34 'cans' => '',
35 ];
36 $this->default->cans = [
37 'vf' => __( 'Can view forum', 'wpforo' ),
38 'enf' => __( 'Can enter forum', 'wpforo' ),
39 'ct' => __( 'Can create topic', 'wpforo' ),
40 'vt' => __( 'Can view topic', 'wpforo' ),
41 'ent' => __( 'Can enter topic', 'wpforo' ),
42 'et' => __( 'Can edit topic', 'wpforo' ),
43 'dt' => __( 'Can delete topic', 'wpforo' ),
44 'cr' => __( 'Can post reply', 'wpforo' ),
45 'ocr' => __( 'Can reply to own topic', 'wpforo' ),
46 'vr' => __( 'Can view replies', 'wpforo' ),
47 'er' => __( 'Can edit replies', 'wpforo' ),
48 'dr' => __( 'Can delete replies', 'wpforo' ),
49 'eot' => __( 'Can edit own topic', 'wpforo' ),
50 'eor' => __( 'Can edit own reply', 'wpforo' ),
51 'dot' => __( 'Can delete own topic', 'wpforo' ),
52 'dor' => __( 'Can delete own reply', 'wpforo' ),
53 'tag' => __( 'Can add tags', 'wpforo' ),
54 'sb' => __( 'Can subscribe', 'wpforo' ),
55 'l' => __( 'Can like', 'wpforo' ),
56 'r' => __( 'Can report', 'wpforo' ),
57 's' => __( 'Can set topic sticky', 'wpforo' ),
58 'p' => __( 'Can set topic private', 'wpforo' ),
59 'op' => __( 'Can set own topic private', 'wpforo' ),
60 'vp' => __( 'Can view private topic', 'wpforo' ),
61 'au' => __( 'Can approve/unapprove content', 'wpforo' ),
62 'sv' => __( 'Can set topic solved', 'wpforo' ),
63 'osv' => __( 'Can set own topic solved', 'wpforo' ),
64 'v' => __( 'Can vote', 'wpforo' ),
65 'vop' => __( 'Can leave voice posts', 'wpforo' ),
66 'vlp' => __( 'Can listen voice posts', 'wpforo' ),
67 'a' => __( 'Can attach file', 'wpforo' ),
68 'va' => __( 'Can view attached files', 'wpforo' ),
69 'at' => __( 'Can set topic answered', 'wpforo' ),
70 'oat' => __( 'Can set own topic answered', 'wpforo' ),
71 'aot' => __( 'Can answer own question', 'wpforo' ),
72 'cot' => __( 'Can close topic', 'wpforo' ),
73 'mt' => __( 'Can move topic', 'wpforo' ),
74 // Poll-related permissions moved to wpForo Polls plugin
75 ];
76 }
77
78 private function init_cans() {
79 $this->cans = apply_filters( 'wpforo_init_cans', $this->default->cans );
80 }
81
82 private function init() {
83 if( WPF()->is_installed() ) {
84 if( $accesses = $this->get_accesses() ) {
85 foreach( $accesses as $access ) {
86 $this->accesses[ intval( $access['accessid'] ) ] = $this->accesses[ $access['access'] ] = $access;
87 }
88 }
89 }
90 }
91
92 private function init_current_user_accesses() {
93 WPF()->current_user_accesses = $this->get_forum_accesses_by_usergroup();
94 }
95
96 public function fix_access( $access ) {
97 $access = wpforo_array_args_cast_and_merge( (array) $access, $this->default->access );
98 $cans = array_map( '__return_zero', $this->cans );
99 $access['cans'] = maybe_unserialize( $access['cans'] );
100 if( is_array( $access['cans'] ) ) {
101 $access['cans'] = wpforo_array_args_cast_and_merge( $access['cans'], $cans );
102 } else {
103 $access['cans'] = $cans;
104 }
105
106 return $access;
107 }
108
109 /**
110 *
111 * @param string|int $access
112 *
113 * @return array access row by access key
114 */
115 function get_access( $access ) {
116 if( wpforo_is_id( $access ) ) {
117 $access = intval( $access );
118 } else {
119 $access = sanitize_text_field( $access );
120 }
121 if( ! empty( $this->accesses[ $access ] ) ) return $this->accesses[ $access ];
122
123 $sql = "SELECT * FROM " . WPF()->tables->accesses;
124 if( is_int( $access ) ) {
125 $sql .= " WHERE `accessid` = %d";
126 } else {
127 $sql .= " WHERE `access` = %s";
128 }
129
130 $access_row = $this->fix_access( WPF()->db->get_row( WPF()->db->prepare( $sql, $access ), ARRAY_A ) );
131 /**
132 * Filter after getting an access row to allow add-ons to adjust permissions dynamically.
133 * Example: Polls add-on sets default poll permissions per access level.
134 *
135 * @since 2.4.0
136 * @param array $access_row
137 */
138 $access_row = apply_filters( 'wpforo_after_get_access', $access_row );
139 return $access_row;
140 }
141
142
143 /**
144 * get all accesses from accesses table
145 *
146 * @return array|null
147 */
148 function get_accesses() {
149 $sql = "SELECT * FROM " . WPF()->tables->accesses . " ORDER BY `accessid`";
150
151 $rows = array_map( [ $this, 'fix_access' ], WPF()->db->get_results( $sql, ARRAY_A ) );
152 // Allow add-ons to adjust each access after retrieval (e.g., apply default poll permissions)
153 $rows = array_map( function( $row ) { return apply_filters( 'wpforo_after_get_access', $row ); }, $rows );
154 return $rows;
155 }
156
157 /**
158 * @param array $access
159 *
160 * @return int|bool inserted id or false
161 */
162 function add( $access ) {
163 if( ! ( $access['title'] = sanitize_text_field( $access['title'] ) ) ) {
164 WPF()->notice->add( 'Access title is empty', 'error' );
165
166 return false;
167 }
168
169 if( ! $access['access'] ) $access['access'] = uniqid();
170
171 /**
172 * Allow add-ons to adjust access data before adding.
173 * Example: Polls add-on injects default poll permissions.
174 *
175 * @since 2.4.0
176 * @param array $access
177 */
178 $access = apply_filters( 'wpforo_before_add_access', $access );
179
180 $i = 2;
181 $slug = $access['access'];
182 while( WPF()->db->get_var( WPF()->db->prepare( "SELECT `access` FROM " . WPF()->tables->accesses . " WHERE `access` = %s", sanitize_text_field( $slug ) ) ) ) {
183 $slug = $access['access'] . '-' . $i;
184 $i ++;
185 }
186
187 if( WPF()->db->insert(
188 WPF()->tables->accesses, [
189 'title' => $access['title'],
190 'access' => sanitize_text_field(
191 $slug
192 ),
193 'cans' => serialize(
194 $access['cans']
195 ),
196 ], [ '%s', '%s', '%s', ]
197 ) ) {
198 $access['accessid'] = WPF()->db->insert_id;
199 WPF()->notice->add( 'Access successfully added', 'success' );
200
201 return $access['accessid'];
202 }
203
204 WPF()->notice->add( 'Access add error', 'error' );
205
206 return false;
207 }
208
209 /**
210 * @param array $access
211 *
212 * @return bool|int edited id or false
213 */
214 function edit( $access ) {
215 /**
216 * Allow add-ons to adjust access data before editing.
217 * Example: Polls add-on injects/ensures poll permissions are present.
218 *
219 * @since 2.4.0
220 * @param array $access
221 */
222 $access = apply_filters( 'wpforo_before_edit_access', $access );
223 if( false !== WPF()->db->update( WPF()->tables->accesses, [
224 'title' => sanitize_text_field( $access['title'] ),
225 'cans' => serialize( $access['cans'] ),
226 ], [
227 'accessid' => $access['accessid'],
228 ], [ '%s', '%s' ], [ '%d' ] ) ) {
229 WPF()->notice->add( 'Access successfully edited', 'success' );
230
231 return $access['accessid'];
232 }
233
234 WPF()->notice->add( 'Access edit error', 'error' );
235
236 return false;
237 }
238
239 /**
240 * @param int $accessid
241 *
242 * @return bool|int deleted id or false
243 */
244 function delete( $accessid ) {
245 $accessid = intval( $accessid );
246 if( ! $accessid ) {
247 WPF()->notice->add( 'Access delete error', 'error' );
248
249 return false;
250 }
251
252 if( false !== WPF()->db->delete( WPF()->tables->accesses, [ 'accessid' => $accessid ], [ '%d' ] ) ) {
253 WPF()->notice->add( 'Access successfully deleted', 'success' );
254
255 return $accessid;
256 }
257
258 WPF()->notice->add( 'Access delete error', 'error' );
259
260 return false;
261 }
262
263 function forum_can( $do, $forumid = null, $groupids = null ) {
264 /**
265 * filter for other add-ons to manage can_attach bool value.
266 * e.g. PM add-on attachment function.
267 */
268 $filter_forum_can = apply_filters( 'wpforo_permissions_forum_can', null, $do, $forumid, $groupids );
269 if( ! is_null( $filter_forum_can ) ) return (int) (bool) $filter_forum_can;
270
271 if( ( is_null( $groupids ) && ! WPF()->current_user_groupids ) || ! $do ) return 0;
272
273 //User Forum accesses from Current Object of Current user
274 if( is_null( $groupids ) && WPF()->current_user_accesses ) {
275 $forum_id = (int) ( is_null( $forumid ) ? wpfval( WPF()->current_object, 'forum', 'forumid' ) : ( wpfkey( $forumid, 'forumid' ) ? $forumid['forumid'] : $forumid ) );
276 if( $forum_id && ( $forum_accesses = wpfval( WPF()->current_user_accesses, $forum_id ) ) ) {
277 foreach( $forum_accesses as $cans ) {
278 if( (int) wpfval( $cans, $do ) ) return 1;
279 }
280 }
281
282 return 0;
283 }
284
285 //Use Custom User Forum Accesses
286 $forum = is_null( $forumid ) ? WPF()->current_object['forum'] : ( ! wpfkey( $forumid, 'forumid' ) ? WPF()->forum->get_forum( $forumid ) : $forumid );
287 if( $forum ) {
288 $permissions = maybe_unserialize( $forum['permissions'] );
289 if( is_null( $groupids ) ) $groupids = WPF()->current_user_groupids;
290 $groupids = array_map( 'intval', (array) $groupids );
291 foreach( $groupids as $groupid ) {
292 if( $_access = wpfval( $permissions, $groupid ) ) {
293 $access = $this->get_access( $_access );
294 if( (int) wpfval( $access, 'cans', $do ) ) return 1;
295 }
296 }
297 }
298
299 return 0;
300 }
301
302 function user_can_manage_user( $user_id, $managing_user_id ) {
303 if( ! $user_id || ! $managing_user_id ) return false;
304 if( $user_id == $managing_user_id ) return true;
305
306 $user = new WP_User( $user_id );
307 $user_level = $this->user_wp_level( $user );
308 if( ! empty( $user->roles ) && is_array( $user->roles ) ) $user_role = array_shift( $user->roles );
309
310 $managing_user = new WP_User( $managing_user_id );
311 $managing_user_level = $this->user_wp_level( $managing_user );
312 if( ! empty( $managing_user->roles ) && is_array( $managing_user->roles ) ) $managing_user_role = array_shift( $managing_user->roles );
313
314 if( (int) $user_level > (int) $managing_user_level ) {
315 return true;
316 } elseif( $user_id == 1 && $user_role === 'administrator' ) {
317 return true;
318 } elseif( (int) $user_level === (int) $managing_user_level ) {
319 $member = WPF()->member->get_member( $user_id );
320 $managing_member = WPF()->member->get_member( $managing_user_id );
321 $user_wpforo_can = WPF()->usergroup->can( 'em', $member['groupids'] );
322 $managing_user_wpforo_can = WPF()->usergroup->can( 'em', $managing_member['groupids'] );
323 if( $user_wpforo_can && ! $managing_user_wpforo_can ) {
324 return true;
325 } else {
326 return false;
327 }
328 } elseif( $user_id != 1 && $managing_user_id == 1 && $managing_user_role === 'administrator' ) {
329 return false;
330 } else {
331 return false;
332 }
333 }
334
335 function user_wp_level( $user_object ) {
336 $level = 0;
337 $levels = [];
338 if( is_int( $user_object ) ) {
339 $user_object = new WP_User( $user_object );
340 }
341 if( isset( $user_object->allcaps ) && is_array( $user_object->allcaps ) && ! empty( $user_object->allcaps ) ) {
342 foreach( $user_object->allcaps as $level_key => $level_value ) {
343 if( strpos( (string) $level_key, 'level_' ) !== false && $level_value == 1 ) {
344 $levels[] = intval( str_replace( 'level_', '', $level_key ) );
345 }
346 }
347 if( ! empty( $levels ) ) {
348 $level = max( $levels );
349 }
350 }
351
352 return $level;
353 }
354
355 function can_edit_user( $userid ) {
356 if( ! $userid ) return false;
357 if( ! $this->user_can_edit_account( $userid ) ) {
358 WPF()->notice->clear();
359 WPF()->notice->add( 'Permission denied', 'error' );
360 wp_safe_redirect( wpforo_get_request_uri() );
361 exit();
362 }
363
364 return true;
365 }
366
367 public function can_link() {
368 if( ! WPF()->usergroup->can( 'em' ) ) {
369 $posts = WPF()->member->member_approved_posts( WPF()->current_userid );
370 $posts = intval( $posts );
371 if( ( $min_posts = wpforo_setting( 'antispam', 'min_number_posts_to_link' ) ) && $posts <= $min_posts ) return false;
372 }
373
374 return true;
375 }
376
377 public function can_attach( $forumid = null ) {
378 if( ! $forumid ) $forumid = null;
379
380 /**
381 * filter for other add-ons to manage can_attach bool value.
382 * e.g. PM add-on attachment function.
383 */
384 $filter_wpforo_can_attach = apply_filters( 'wpforo_can_attach', null, $forumid );
385 if( ! is_null( $filter_wpforo_can_attach ) ) return (bool) $filter_wpforo_can_attach;
386
387 if( ! $this->forum_can( 'a', $forumid ) ) return false;
388 if( ! WPF()->usergroup->can( 'em' ) ) {
389 $posts = WPF()->member->member_approved_posts( WPF()->current_userid );
390 $posts = intval( $posts );
391 if( ( $min_posts = wpforo_setting( 'antispam', 'min_number_posts_to_attach' ) ) && $posts <= $min_posts ) return false;
392 }
393
394 return true;
395 }
396
397 public function can_attach_file_type( $ext = '' ) {
398 if( ! WPF()->usergroup->can( 'em' ) && WPF()->member->current_user_is_new() && in_array( $ext, wpforo_setting( 'antispam', 'limited_file_ext' ) ) ) return false;
399
400 return true;
401 }
402
403 /**
404 * Check if user can post now based on flood protection settings
405 *
406 * @param string $flood_reason Reference to store the reason if blocked
407 * @return bool|string True if can post, false if blocked, 'unapprove' if should be unapproved
408 */
409 public function can_post_now( &$flood_reason = '' ) {
410 if( wpforo_is_admin() || ( defined( 'IS_GO2WPFORO' ) && IS_GO2WPFORO ) ) {
411 return true;
412 }
413
414 // Users with "Dashboard - Moderate Topics & Posts" permission bypass flood protection
415 if( WPF()->usergroup->can( 'aum' ) ) {
416 return true;
417 }
418
419 $userid = WPF()->current_userid;
420 $email = $userid ? '' : WPF()->current_user_email;
421 $groupid = WPF()->current_user_groupid;
422 if( WPF()->member->current_user_is_new() ) {
423 $groupid = 0;
424 }
425
426 // Check for temporary ban first
427 if( $this->is_flood_banned() ) {
428 $flood_reason = 'temp_ban';
429 return false;
430 }
431
432 // Legacy flood interval check (per user group)
433 if( $flood_interval = WPF()->usergroup->get_flood_interval( $groupid ) ) {
434 $hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
435 $args = [
436 'userid' => $userid,
437 'email' => $email,
438 'orderby' => '`created` DESC, `postid` DESC',
439 'row_count' => 1,
440 'where' => "`created` >= '$hour_ago'",
441 ];
442 $items_count = 0;
443 $lastpost = WPF()->post->get_posts( $args, $items_count, false );
444 if( $lasttime = wpfval( $lastpost, 0, 'created' ) ) {
445 $lasttime = strtotime( $lasttime . ' GMT' );
446 $nowtime = time();
447 $diff = $nowtime - $lasttime;
448 if( $diff < $flood_interval ) {
449 $flood_reason = 'interval';
450 return false;
451 }
452 }
453 }
454
455 // Advanced flood protection checks
456 if( ! wpforo_setting( 'antispam', 'flood_protection_enabled' ) ) {
457 return true;
458 }
459
460 // Check posts per minute
461 $posts_per_minute = (int) wpforo_setting( 'antispam', 'flood_posts_per_minute' );
462 if( $posts_per_minute > 0 ) {
463 $minute_ago = gmdate( 'Y-m-d H:i:s', time() - 60 );
464 $count = $this->get_user_post_count_since( $userid, $email, $minute_ago );
465 if( $count >= $posts_per_minute ) {
466 $flood_reason = 'per_minute';
467 return $this->handle_flood_action();
468 }
469 }
470
471 // Check posts per hour
472 $posts_per_hour = (int) wpforo_setting( 'antispam', 'flood_posts_per_hour' );
473 if( $posts_per_hour > 0 ) {
474 $hour_ago = gmdate( 'Y-m-d H:i:s', time() - HOUR_IN_SECONDS );
475 $count = $this->get_user_post_count_since( $userid, $email, $hour_ago );
476 if( $count >= $posts_per_hour ) {
477 $flood_reason = 'per_hour';
478 return $this->handle_flood_action();
479 }
480 }
481
482 // IP-based flood protection (tracked via transients since posts table has no IP column)
483 if( wpforo_setting( 'antispam', 'flood_ip_protection_enabled' ) ) {
484 $posts_per_ip_hour = (int) wpforo_setting( 'antispam', 'flood_posts_per_ip_hour' );
485 if( $posts_per_ip_hour > 0 ) {
486 $ip = $this->get_user_ip();
487 if( $ip ) {
488 $count = $this->get_ip_post_count_in_window( $ip, HOUR_IN_SECONDS );
489 if( $count >= $posts_per_ip_hour ) {
490 $flood_reason = 'ip_per_hour';
491 return $this->handle_flood_action();
492 }
493 }
494 }
495 }
496
497 // All checks passed - record IP post attempt for IP-based tracking
498 if( wpforo_setting( 'antispam', 'flood_ip_protection_enabled' ) ) {
499 $ip = $this->get_user_ip();
500 if( $ip ) {
501 $this->record_ip_post_attempt( $ip );
502 }
503 }
504
505 return true;
506 }
507
508 /**
509 * Get the current user's IP address
510 *
511 * Uses REMOTE_ADDR only to prevent IP spoofing via X-Forwarded-For headers.
512 *
513 * @return string|null IP address or null if not available
514 */
515 private function get_user_ip() {
516 $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
517 return filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : null;
518 }
519
520 /**
521 * Get post count for a user since a specific time
522 *
523 * @param int $userid User ID
524 * @param string $email Email for guests
525 * @param string $since MySQL datetime string
526 * @return int Post count
527 */
528 private function get_user_post_count_since( $userid, $email, $since ) {
529 $posts_table = WPF()->tables->posts;
530
531 if( $userid ) {
532 $count = WPF()->db->get_var( WPF()->db->prepare(
533 "SELECT COUNT(*) FROM `{$posts_table}` WHERE `userid` = %d AND `created` >= %s",
534 $userid,
535 $since
536 ) );
537 } elseif( $email ) {
538 $count = WPF()->db->get_var( WPF()->db->prepare(
539 "SELECT COUNT(*) FROM `{$posts_table}` WHERE `email` = %s AND `created` >= %s",
540 $email,
541 $since
542 ) );
543 } else {
544 return 0;
545 }
546
547 return (int) $count;
548 }
549
550 /**
551 * Get post count for an IP address within a time window (using transients)
552 *
553 * @param string $ip IP address
554 * @param int $window_seconds Time window in seconds
555 * @return int Post count
556 */
557 private function get_ip_post_count_in_window( $ip, $window_seconds ) {
558 $key = 'wpforo_ip_posts_' . md5( $ip );
559 $timestamps = get_transient( $key );
560 if( ! is_array( $timestamps ) ) {
561 return 0;
562 }
563
564 // Filter to only include timestamps within the window
565 $cutoff = time() - $window_seconds;
566 $valid_timestamps = array_filter( $timestamps, function( $ts ) use ( $cutoff ) {
567 return $ts >= $cutoff;
568 });
569
570 return count( $valid_timestamps );
571 }
572
573 /**
574 * Record an IP post attempt for flood tracking
575 *
576 * @param string $ip IP address
577 */
578 private function record_ip_post_attempt( $ip ) {
579 $key = 'wpforo_ip_posts_' . md5( $ip );
580 $timestamps = get_transient( $key );
581 if( ! is_array( $timestamps ) ) {
582 $timestamps = [];
583 }
584
585 // Add current timestamp
586 $timestamps[] = time();
587
588 // Clean old entries (keep last hour only)
589 $cutoff = time() - HOUR_IN_SECONDS;
590 $timestamps = array_filter( $timestamps, function( $ts ) use ( $cutoff ) {
591 return $ts >= $cutoff;
592 });
593
594 // Re-index array and store with 1-hour expiration
595 $timestamps = array_values( $timestamps );
596 set_transient( $key, $timestamps, HOUR_IN_SECONDS );
597 }
598
599 /**
600 * Handle flood action based on settings
601 *
602 * @return bool|string False to block, 'unapprove' to allow but unapprove
603 */
604 private function handle_flood_action() {
605 $action = wpforo_setting( 'antispam', 'flood_action' );
606
607 if( $action === 'temp_ban' ) {
608 $this->set_flood_ban();
609 return false;
610 }
611
612 if( $action === 'unapprove' ) {
613 return 'unapprove';
614 }
615
616 // Default: block
617 return false;
618 }
619
620 /**
621 * Set a temporary flood ban for the current user/IP
622 */
623 private function set_flood_ban() {
624 $duration = (int) wpforo_setting( 'antispam', 'flood_temp_ban_duration' );
625 $ban_until = time() + ( $duration * 60 );
626
627 $userid = WPF()->current_userid;
628 $ip = $this->get_user_ip();
629
630 // Store ban in transient (keyed by user ID or IP)
631 if( $userid ) {
632 set_transient( 'wpforo_flood_ban_user_' . $userid, $ban_until, $duration * 60 );
633 }
634 if( $ip ) {
635 set_transient( 'wpforo_flood_ban_ip_' . md5( $ip ), $ban_until, $duration * 60 );
636 }
637 }
638
639 /**
640 * Check if the current user/IP is temporarily banned for flooding
641 *
642 * @return bool True if banned
643 */
644 public function is_flood_banned() {
645 $userid = WPF()->current_userid;
646 $ip = $this->get_user_ip();
647
648 // Check user ban
649 if( $userid ) {
650 $ban_until = get_transient( 'wpforo_flood_ban_user_' . $userid );
651 if( $ban_until && time() < $ban_until ) {
652 return true;
653 }
654 }
655
656 // Check IP ban
657 if( $ip ) {
658 $ban_until = get_transient( 'wpforo_flood_ban_ip_' . md5( $ip ) );
659 if( $ban_until && time() < $ban_until ) {
660 return true;
661 }
662 }
663
664 return false;
665 }
666
667 /**
668 * Get remaining flood ban time in seconds
669 *
670 * @return int Seconds remaining, 0 if not banned
671 */
672 public function get_flood_ban_remaining() {
673 $userid = WPF()->current_userid;
674 $ip = $this->get_user_ip();
675 $max_remaining = 0;
676
677 if( $userid ) {
678 $ban_until = get_transient( 'wpforo_flood_ban_user_' . $userid );
679 if( $ban_until && time() < $ban_until ) {
680 $max_remaining = max( $max_remaining, $ban_until - time() );
681 }
682 }
683
684 if( $ip ) {
685 $ban_until = get_transient( 'wpforo_flood_ban_ip_' . md5( $ip ) );
686 if( $ban_until && time() < $ban_until ) {
687 $max_remaining = max( $max_remaining, $ban_until - time() );
688 }
689 }
690
691 return $max_remaining;
692 }
693
694 public function get_forum_accesses_by_usergroup( $groupids = [] ) {
695 $forum_accesses = [];
696 if( ! $groupids ) $groupids = WPF()->current_user_groupids;
697 if( ( $groupids = array_map( 'intval', (array) $groupids ) ) && ( $forums = WPF()->forum->get_forums() ) ) {
698 foreach( $forums as $forum ) {
699 if( $permissions = maybe_unserialize( $forum['permissions'] ) ) {
700 foreach( $groupids as $groupid ) {
701 $access = wpfval( $permissions, $groupid );
702 if( $_access = $this->get_access( $access ) ) {
703 if( $cans = wpfval( $_access, 'cans' ) ) {
704 if( ! wpfkey( $forum_accesses, $forum['forumid'], $access ) ) $forum_accesses[ $forum['forumid'] ][ $access ] = $cans;
705 }
706 }
707 }
708 }
709 }
710 }
711
712 return apply_filters( 'wpforo_permissions_forum_accesses_by_usergroup', $forum_accesses, $groupids );
713 }
714
715 public function show_accesses_selectbox( $selected = [], $exclude = [] ) {
716 $accesses = $this->get_accesses();
717 foreach( $accesses as $accesse ) {
718 if( in_array( $accesse['access'], (array) $exclude ) ) continue;
719 printf(
720 '<option value="%1$s" %2$s>%3$s</option>',
721 esc_attr( $accesse['access'] ),
722 in_array( $accesse['access'], (array) $selected ) ? 'selected' : '',
723 esc_html( $accesse['title'] )
724 );
725 }
726 }
727
728 /**
729 * @param array|int $owner
730 * @param array|int $user
731 *
732 * @return bool
733 */
734 public function user_can_edit_account( $owner = [], $user = [] ) {
735 if( ! $user ) $user = WPF()->current_user;
736 if( ! $owner ) $owner = WPF()->current_object['user'];
737 if( wpforo_is_id( $owner ) ) $owner = WPF()->member->get_member( $owner );
738 if( wpforo_is_id( $user ) ) $user = WPF()->member->get_member( $user );
739 if( ! $user || ! $owner ) return false;
740 $is_users_same = wpforo_is_users_same( $user, $owner );
741
742 return wpforo_user_is( $user['userid'], 'admin' ) || ( WPF()->usergroup->can( 'em', $user['groupids'] ) && $this->user_can_manage_user(
743 $user['userid'],
744 $owner['userid']
745 ) ) || ( $is_users_same && wpforo_user_is( $user['userid'], 'moderator' ) ) || ( $is_users_same && $user['posts'] >= wpforo_setting( 'antispam', 'min_number_posts_to_edit_account' ) );
746 }
747
748 public function can_report( $forumid, $groupids = null ): bool {
749 if( ( is_null( $groupids ) && ! WPF()->current_user_groupids ) ) return false;
750 if( is_null( $groupids ) ) $groupids = WPF()->current_user_groupids;
751
752 $res = WPF()->current_userid && ! in_array( WPF()->current_user_status, [ 'banned', 'trashed' ] ) && ! WPF()->member->current_user_is_new() && $this->forum_can( 'r', $forumid, $groupids );
753
754 return apply_filters( 'wpforo_can_report', $res, $forumid, $groupids );
755 }
756 }
757