PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.0
Forumax – AI Powered Advanced Community Forum Plugin v2.5.0
2.5.0 2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 2.0.0 All 30 releases
← All changes | includes/bbpress-config.php +445 -268 2.0.0 → 2.5.0 View file →
@@ -1,268 +1,445 @@
1 -<?php
2 -
3 -/**
4 - * bbPress Replies
5 - */
6 -add_filter('bbp_show_lead_topic', function () {
7 - $show_lead[] = 'true';
8 - return $show_lead;
9 -});
10 -
11 -/**
12 - * bbPress User Role
13 - */
14 -if ( ! function_exists( 'forumax_bbp_user_role_icon' ) ) {
15 - function forumax_bbp_user_role_icon() {
16 - $author_role = bbp_get_user_display_role( bbp_get_reply_author_id() );
17 - switch ( $author_role ) {
18 - case 'Keymaster':
19 - get_image_from_settings( 'keymaster_icon' );
20 - break;
21 - case 'Moderator':
22 - get_image_from_settings( 'moderator_icon' );
23 - break;
24 - case 'Participant':
25 - get_image_from_settings( 'participant_icon' );
26 - break;
27 - case 'Spectator':
28 - get_image_from_settings( 'spectator_icon' );
29 - break;
30 - case 'Blocked':
31 - get_image_from_settings( 'blocked_icon' );
32 - break;
33 - }
34 - }
35 -}
36 -
37 -/**
38 - * Get author role
39 - */
40 -if (!function_exists('forumax_get_bbp_user_role')) {
41 -function forumax_get_bbp_user_role() {
42 - $role = bbp_get_topic_author_role( array(
43 - 'topic_id' => get_the_ID(),
44 - 'before' => '',
45 - 'after' => '',
46 - ));
47 - return $role;
48 -}
49 -}
50 -
51 -/**
52 - * Get topic reply count
53 - */
54 -if (!function_exists('forumax_topic_reply_count')) {
55 -function forumax_topic_reply_count() {
56 - $get_reply = get_post_meta( get_the_ID(), '_bbp_reply_count', true );
57 - $_reply_count = isset( $get_reply ) && !empty( $get_reply ) ? $get_reply : 0;
58 - echo esc_html( $_reply_count );
59 -}
60 -}
61 -
62 -/**
63 - * Display bbp_reply_admin_links() as a dropdown list. The dropdown will be shown on click on a vertical three dots icon.
64 - * Create a new function to display the dropdown.
65 - */
66 -if ( ! function_exists( 'forumax_get_reply_admin_links' ) ) {
67 -function forumax_get_reply_admin_links( $args = array() ) {
68 -
69 - // Parse arguments against default values
70 - $r = bbp_parse_args( $args, array(
71 - 'id' => 0,
72 - 'before' => '<div class="bbp-admin-links-dropdown">',
73 - 'after' => '</div>',
74 - 'sep' => '',
75 - 'links' => array()
76 - ), 'get_reply_admin_links' );
77 -
78 - $r['id'] = bbp_get_reply_id( $r['id'] );
79 -
80 - // If post is a topic, return the topic admin links instead
81 - if ( bbp_is_topic( $r['id'] ) ) {
82 - return forumax_get_topic_admin_links( $args );
83 - }
84 -
85 - // If post is not a reply, return
86 - if ( ! bbp_is_reply( $r['id'] ) ) {
87 - return;
88 - }
89 -
90 - // If topic is trashed, do not show admin links
91 - if ( bbp_is_topic_trash( bbp_get_reply_topic_id( $r['id'] ) ) ) {
92 - return;
93 - }
94 -
95 - // If no links were passed, default to the standard
96 - if ( empty( $r['links'] ) ) {
97 - $r['links'] = apply_filters( 'bbp_reply_admin_links', array(
98 - 'edit' => bbp_get_reply_edit_link ( $r ),
99 - 'move' => bbp_get_reply_move_link ( $r ),
100 - 'split' => bbp_get_topic_split_link ( $r ),
101 - 'trash' => bbp_get_reply_trash_link ( $r ),
102 - 'spam' => bbp_get_reply_spam_link ( $r ),
103 - 'approve' => bbp_get_reply_approve_link( $r ),
104 - 'reply' => bbp_get_reply_to_link ( $r )
105 - ), $r['id'] );
106 - }
107 -
108 - // See if links need to be unset
109 - $reply_status = bbp_get_reply_status( $r['id'] );
110 - if ( in_array( $reply_status, array( bbp_get_spam_status_id(), bbp_get_trash_status_id(), bbp_get_pending_status_id() ), true ) ) {
111 -
112 - // Spam link shouldn't be visible on trashed topics
113 - if ( bbp_get_trash_status_id() === $reply_status ) {
114 - unset( $r['links']['spam'] );
115 -
116 - // Trash link shouldn't be visible on spam topics
117 - } elseif ( bbp_get_spam_status_id() === $reply_status ) {
118 - unset( $r['links']['trash'] );
119 - }
120 - }
121 -
122 - // Build our dropdown menu structure
123 - $menu = '<select name="reply-admin-links" class="nice-select bbp-admin-dropdown-menu" onchange="if(this.value) window.location.href=this.value;">';
124 -
125 - // Process each link as a menu item
126 - $menu .= '<option value="" disabled selected>' . esc_html__( 'Select an option', 'forumax' ) . '</option>';
127 - foreach ( array_filter( $r['links'] ) as $link ) {
128 - // Extract href attribute from the link
129 - preg_match('/href=(["\'])([^"\']+)\\1/', $link, $matches);
130 - $href = !empty($matches[2]) ? $matches[2] : '';
131 -
132 - // Add the option with value attribute containing the URL
133 - $menu .= '<option value="' . esc_url($href) . '" class="bbp-admin-dropdown-item">' . wp_strip_all_tags($link) . '</option>';
134 - }
135 -
136 - $menu .= '</select>';
137 -
138 - $retval = $r['before'] . $menu . $r['after'];
139 -
140 - // Filter & return
141 - if ( is_user_logged_in() ) {
142 - return apply_filters( 'forumax_bbp_get_reply_admin_links', $retval, $r, $args );
143 - }
144 -}
145 -}
146 -
147 -/**
148 - * Similar function for topics - to handle the case where a topic is passed to the reply function
149 - */
150 -if ( ! function_exists( 'forumax_get_topic_admin_links' ) ) {
151 - function forumax_get_topic_admin_links( $args = array() ) {
152 - // Parse arguments against default values
153 - $r = bbp_parse_args( $args, array(
154 - 'id' => 0,
155 - 'before' => '<div class="bbp-admin-links-dropdown">',
156 - 'after' => '</div>',
157 - 'sep' => '',
158 - 'links' => array()
159 - ), 'get_topic_admin_links' );
160 -
161 - $r['id'] = bbp_get_topic_id( $r['id'] );
162 -
163 - // If not a topic, then return
164 - if ( ! bbp_is_topic( $r['id'] ) ) {
165 - return;
166 - }
167 -
168 - // If no links were passed, get the default ones
169 - if ( empty( $r['links'] ) ) {
170 - $r['links'] = apply_filters( 'bbp_topic_admin_links', array(
171 - 'edit' => bbp_get_topic_edit_link ( $r ),
172 - 'close' => bbp_get_topic_close_link ( $r ),
173 - 'stick' => bbp_get_topic_stick_link ( $r ),
174 - 'merge' => bbp_get_topic_merge_link ( $r ),
175 - 'trash' => bbp_get_topic_trash_link ( $r ),
176 - 'spam' => bbp_get_topic_spam_link ( $r ),
177 - 'approve' => bbp_get_topic_approve_link( $r ),
178 - 'reply' => bbp_get_topic_reply_link ( $r )
179 - ), $r['id'] );
180 - }
181 -
182 - // See if links need to be unset
183 - $topic_status = bbp_get_topic_status( $r['id'] );
184 - if ( in_array( $topic_status, array( bbp_get_spam_status_id(), bbp_get_trash_status_id(), bbp_get_pending_status_id() ), true ) ) {
185 -
186 - // Spam link shouldn't be visible on trashed topics
187 - if ( bbp_get_trash_status_id() === $topic_status ) {
188 - unset( $r['links']['spam'] );
189 -
190 - // Trash link shouldn't be visible on spam topics
191 - } elseif ( bbp_get_spam_status_id() === $topic_status ) {
192 - unset( $r['links']['trash'] );
193 - }
194 - }
195 -
196 - // Build our dropdown menu structure
197 - $menu = '<select name="topic-admin-links" class="nice-select bbp-admin-dropdown-menu" onchange="if(this.value) window.location.href=this.value;">';
198 - $menu .= '<option value="" disabled selected>Select an option</option>';
199 -
200 - // Process each link as a menu item
201 - foreach ( array_filter( $r['links'] ) as $link ) {
202 - // Extract href attribute from the link
203 - preg_match('/href=(["\'])([^"\']+)\\1/', $link, $matches);
204 - $href = !empty($matches[2]) ? $matches[2] : '';
205 -
206 - // Add the option with value attribute containing the URL
207 - $menu .= '<option value="' . esc_url($href) . '" class="bbp-admin-dropdown-item">' . wp_strip_all_tags($link) . '</option>';
208 - }
209 -
210 - $menu .= '</select>';
211 -
212 - $retval = $r['before'] . $menu . $r['after'];
213 -
214 - // Filter & return
215 - return apply_filters( 'forumax_bbp_get_topic_admin_links', $retval, $r, $args );
216 - }
217 -}
218 -/**
219 - * Enqueue the script for sorting bbPress replies
220 - */
221 -if ( ! function_exists( 'forumax_enqueue_bbp_replies_sorting_script' ) ) {
222 - function forumax_enqueue_bbp_replies_sorting_script() {
223 - wp_localize_script('bbp-replies-sorting', 'bbp_reply_ajax_obj', array(
224 - 'ajax_url' => admin_url('admin-ajax.php'),
225 - 'bbp_topic_id' => get_the_ID(),
226 - ));
227 - }
228 - add_action('wp_enqueue_scripts', 'forumax_enqueue_bbp_replies_sorting_script');
229 -}
230 -
231 -if ( ! function_exists( 'forumax_ajax_sort_bbp_replies' ) ) {
232 - function forumax_ajax_sort_bbp_replies() {
233 - if (empty($_POST['topic_id'])) {
234 - wp_send_json_error(['message' => 'No topic ID provided.']);
235 - }
236 - $topic_id = intval($_POST['topic_id']);
237 - $sort = isset($_POST['sort']) ? sanitize_text_field($_POST['sort']) : 'desc';
238 - // Set $_GET['sort'] for the filter to pick up
239 - $_GET['sort'] = $sort;
240 -
241 - // Start output buffering to capture the replies loop HTML
242 - ob_start();
243 - // Set up bbPress replies loop manually for AJAX context
244 - $replies_args = array(
245 - 'post_parent' => $topic_id,
246 - 'post_type' => bbp_get_reply_post_type(),
247 - 'orderby' => ($sort === 'vote' ? 'meta_value_num' : 'date'),
248 - 'order' => ($sort === 'asc' ? 'ASC' : 'DESC'),
249 - 'meta_key' => ($sort === 'vote' ? 'bbp_voting_score' : ''),
250 - 'posts_per_page' => -1,
251 - );
252 - if (bbp_has_replies($replies_args)) {
253 - while (bbp_replies()) : bbp_the_reply();
254 - $template_file = plugin_dir_path( __FILE__ ) . '../templates/loop-single-reply.php';
255 - if ( file_exists( $template_file ) ) {
256 - include $template_file;
257 - }
258 - endwhile;
259 - } else {
260 - echo '<div>No replies found.</div>';
261 - }
262 - $html = ob_get_clean();
263 -
264 - wp_send_json_success(['html' => $html]);
265 - }
266 - add_action('wp_ajax_sort_bbp_replies', 'forumax_ajax_sort_bbp_replies');
267 - add_action('wp_ajax_nopriv_sort_bbp_replies', 'forumax_ajax_sort_bbp_replies');
268 -}
1 +<?php
2 +/**
3 + * bbPress configuration for Forumax
4 + *
5 + * @package Forumax
6 + */
7 +
8 +defined( 'ABSPATH' ) || exit;
9 +
10 +/**
11 + * bbPress Replies
12 + */
13 +add_filter( 'bbp_show_lead_topic', function () {
14 + $show_lead[] = 'true';
15 + return $show_lead;
16 +} );
17 +
18 +/**
19 + * bbPress User Role
20 + */
21 +if ( ! function_exists( 'forumax_bbp_user_role_icon' ) ) {
22 + /**
23 + * Get modern inline SVG icon markup for a bbPress role.
24 + *
25 + * @param string $author_role User role label.
26 + * @return string
27 + */
28 + function forumax_get_bbp_role_icon_svg( $author_role = '' ) {
29 + switch ( $author_role ) {
30 + case 'Keymaster':
31 + return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 8l4 4 5-7 5 7 4-4-2 11H5L3 8z"></path><path d="M8 18h8"></path></svg>';
32 + case 'Moderator':
33 + return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l7 3v6c0 4.5-3 7.7-7 9-4-1.3-7-4.5-7-9V6l7-3z"></path><path d="M9 12l2 2 4-4"></path></svg>';
34 + case 'Participant':
35 + return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="8.5" r="2.5"></circle><path d="M6.5 17.5c1.3-2.4 3.2-3.5 5.5-3.5s4.2 1.1 5.5 3.5"></path></svg>';
36 + case 'Member':
37 + return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="8" r="2.75"></circle><path d="M6.5 18c1.2-2.6 3.1-3.8 5.5-3.8s4.3 1.2 5.5 3.8"></path><path d="M18.5 6.5l.9.6 1-.1-.6.9.1 1-.9-.6-.9.6.1-1-.6-.9 1 .1z"></path></svg>';
38 + case 'Spectator':
39 + return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z"></path><circle cx="12" cy="12" r="2.5"></circle></svg>';
40 + case 'Blocked':
41 + return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"></circle><path d="M8.5 8.5l7 7"></path></svg>';
42 + default:
43 + return '';
44 + }
45 + }
46 +
47 + /**
48 + * Display user role icon based on bbPress role.
49 + */
50 + function forumax_bbp_user_role_icon() {
51 + $author_id = 0;
52 +
53 + if ( bbp_get_reply_id() ) {
54 + $author_id = bbp_get_reply_author_id( bbp_get_reply_id() );
55 + } elseif ( bbp_get_topic_id() ) {
56 + $author_id = bbp_get_topic_author_id( bbp_get_topic_id() );
57 + }
58 +
59 + $author_role = ! empty( $author_id ) ? bbp_get_user_display_role( $author_id ) : '';
60 + $icon_svg = forumax_get_bbp_role_icon_svg( $author_role );
61 +
62 + if ( ! empty( $icon_svg ) ) {
63 + echo '<span class="forumax-role-icon forumax-role-icon--' . esc_attr( sanitize_title( $author_role ) ) . '" aria-hidden="true">' . $icon_svg . '</span>';
64 + }
65 + }
66 +}
67 +
68 +/**
69 + * Get author role
70 + */
71 +if ( ! function_exists( 'forumax_get_bbp_user_role' ) ) {
72 + /**
73 + * Get the author role for the current topic.
74 + *
75 + * @return string The author role.
76 + */
77 + function forumax_get_bbp_user_role( $topic_id = 0 ) {
78 + $role = bbp_get_topic_author_role( [
79 + 'topic_id' => $topic_id,
80 + 'before' => '',
81 + 'after' => '',
82 + ]);
83 + return $role;
84 + }
85 +}
86 +
87 +/**
88 + * Get reply author role
89 + */
90 +if ( ! function_exists( 'forumax_get_bbp_reply_user_role' ) ) {
91 + /**
92 + * Get the author role for the current reply.
93 + *
94 + * @param int $reply_id Reply ID.
95 + * @return string The author role.
96 + */
97 + function forumax_get_bbp_reply_user_role( $reply_id = 0 ) {
98 + $role = bbp_get_reply_author_role( [
99 + 'reply_id' => $reply_id,
100 + 'before' => '',
101 + 'after' => '',
102 + ] );
103 + return $role;
104 + }
105 +}
106 +
107 +/**
108 + * Get topic reply count
109 + */
110 +if ( ! function_exists( 'forumax_topic_reply_count' ) ) {
111 + /**
112 + * Output the topic reply count.
113 + */
114 + function forumax_topic_reply_count() {
115 + $get_reply = get_post_meta( get_the_ID(), '_bbp_reply_count', true );
116 + $_reply_count = isset( $get_reply ) && ! empty( $get_reply ) ? $get_reply : 0;
117 + echo esc_html( $_reply_count );
118 + }
119 +}
120 +
121 +/**
122 + * Display bbp_reply_admin_links() as a dropdown list.
123 + */
124 +if ( ! function_exists( 'forumax_get_reply_admin_links' ) ) {
125 + /**
126 + * Get reply admin links as a dropdown.
127 + *
128 + * @param array $args Arguments for the admin links.
129 + * @return string|void Admin links HTML.
130 + */
131 + function forumax_get_reply_admin_links( $args = [] ) {
132 +
133 + // Parse arguments against default values
134 + $r = bbp_parse_args( $args, [
135 + 'id' => 0,
136 + 'before' => '<div class="bbp-admin-links-dropdown">',
137 + 'after' => '</div>',
138 + 'sep' => '',
139 + 'links' => [],
140 + ], 'get_reply_admin_links' );
141 +
142 + $r['id'] = bbp_get_reply_id( $r['id'] );
143 +
144 + // If post is a topic, return the topic admin links instead
145 + if ( bbp_is_topic( $r['id'] ) ) {
146 + return forumax_get_topic_admin_links( $args );
147 + }
148 +
149 + // If post is not a reply, return
150 + if ( ! bbp_is_reply( $r['id'] ) ) {
151 + return;
152 + }
153 +
154 + // If topic is trashed, do not show admin links
155 + if ( bbp_is_topic_trash( bbp_get_reply_topic_id( $r['id'] ) ) ) {
156 + return;
157 + }
158 +
159 + // If no links were passed, default to the standard
160 + if ( empty( $r['links'] ) ) {
161 + $r['links'] = apply_filters( 'bbp_reply_admin_links', [
162 + 'edit' => bbp_get_reply_edit_link( $r ),
163 + 'move' => bbp_get_reply_move_link( $r ),
164 + 'split' => bbp_get_topic_split_link( $r ),
165 + 'trash' => bbp_get_reply_trash_link( $r ),
166 + 'spam' => bbp_get_reply_spam_link( $r ),
167 + 'approve' => bbp_get_reply_approve_link( $r ),
168 + 'reply' => bbp_get_reply_to_link( $r ),
169 + ], $r['id'] );
170 + }
171 +
172 + if ( bbp_get_user_role( get_current_user_id() ) === bbp_get_participant_role() ) {
173 + unset( $r['links']['edit'] );
174 + }
175 +
176 + // See if links need to be unset
177 + $reply_status = bbp_get_reply_status( $r['id'] );
178 + if ( in_array( $reply_status, [ bbp_get_spam_status_id(), bbp_get_trash_status_id(), bbp_get_pending_status_id() ], true ) ) {
179 +
180 + // Spam link shouldn't be visible on trashed topics
181 + if ( bbp_get_trash_status_id() === $reply_status ) {
182 + unset( $r['links']['spam'] );
183 +
184 + // Trash link shouldn't be visible on spam topics
185 + } elseif ( bbp_get_spam_status_id() === $reply_status ) {
186 + unset( $r['links']['trash'] );
187 + }
188 + }
189 +
190 + // Build our dropdown menu structure.
191 + $menu = '<select name="reply-admin-links" class="frmx-nice-select bbp-admin-dropdown-menu" onchange="if(this.value && this.options[this.selectedIndex].getAttribute(\'data-action\') !== \'edit\') window.location.href=this.value;">';
192 +
193 + // Placeholder (hidden trigger label).
194 + $menu .= '<option value="" disabled selected data-action="trigger">' . esc_html__( 'Actions', 'forumax' ) . '</option>';
195 +
196 + // Split links into primary and destructive groups.
197 + $destructive_keys = [ 'trash', 'spam', 'approve' ];
198 + $primary_links = [];
199 + $danger_links = [];
200 +
201 + foreach ( array_filter( $r['links'] ) as $key => $link ) {
202 + if ( in_array( $key, $destructive_keys, true ) ) {
203 + $danger_links[ $key ] = $link;
204 + } else {
205 + $primary_links[ $key ] = $link;
206 + }
207 + }
208 +
209 + // Render primary actions.
210 + foreach ( $primary_links as $key => $link ) {
211 + preg_match( '/href=(["\'])([^"\']+)\\1/', $link, $matches );
212 + $href = ! empty( $matches[2] ) ? $matches[2] : '';
213 + $menu .= '<option value="' . esc_url( $href ) . '" class="bbp-admin-dropdown-item" data-action="' . esc_attr( $key ) . '">' . wp_strip_all_tags( $link ) . '</option>';
214 + }
215 +
216 + // Visual separator between primary and destructive groups.
217 + if ( ! empty( $danger_links ) && ! empty( $primary_links ) ) {
218 + $menu .= '<option disabled data-action="separator">──────</option>';
219 + }
220 +
221 + // Render destructive actions.
222 + foreach ( $danger_links as $key => $link ) {
223 + preg_match( '/href=(["\'])([^"\']+)\\1/', $link, $matches );
224 + $href = ! empty( $matches[2] ) ? $matches[2] : '';
225 + $menu .= '<option value="' . esc_url( $href ) . '" class="bbp-admin-dropdown-item" data-action="' . esc_attr( $key ) . '">' . wp_strip_all_tags( $link ) . '</option>';
226 + }
227 +
228 + $menu .= '</select>';
229 +
230 + $retval = $r['before'] . $menu . $r['after'];
231 +
232 + // Filter & return
233 + if ( is_user_logged_in() ) {
234 + return apply_filters( 'forumax_bbp_get_reply_admin_links', $retval, $r, $args );
235 + }
236 + }
237 +}
238 +
239 +/**
240 + * Similar function for topics - to handle the case where a topic is passed to the reply function
241 + */
242 +if ( ! function_exists( 'forumax_get_topic_admin_links' ) ) {
243 + /**
244 + * Get topic admin links as a dropdown.
245 + *
246 + * @param array $args Arguments for the admin links.
247 + * @return string|void Admin links HTML.
248 + */
249 + function forumax_get_topic_admin_links( $args = [] ) {
250 + // Parse arguments against default values
251 + $r = bbp_parse_args( $args, [
252 + 'id' => 0,
253 + 'before' => '<div class="bbp-admin-links-dropdown">',
254 + 'after' => '</div>',
255 + 'sep' => '',
256 + 'links' => [],
257 + ], 'get_topic_admin_links' );
258 +
259 + $r['id'] = bbp_get_topic_id( $r['id'] );
260 +
261 + // If not a topic, then return
262 + if ( ! bbp_is_topic( $r['id'] ) ) {
263 + return;
264 + }
265 +
266 + // If no links were passed, get the default ones
267 + if ( empty( $r['links'] ) ) {
268 + $r['links'] = apply_filters( 'bbp_topic_admin_links', [
269 + 'edit' => bbp_get_topic_edit_link( $r ),
270 + 'close' => bbp_get_topic_close_link( $r ),
271 + 'stick' => bbp_get_topic_stick_link( $r ),
272 + 'merge' => bbp_get_topic_merge_link( $r ),
273 + 'trash' => bbp_get_topic_trash_link( $r ),
274 + 'spam' => bbp_get_topic_spam_link( $r ),
275 + 'approve' => bbp_get_topic_approve_link( $r ),
276 + 'reply' => bbp_get_topic_reply_link( $r ),
277 + ], $r['id'] );
278 + }
279 +
280 + if ( bbp_get_user_role( get_current_user_id() ) === bbp_get_participant_role() ) {
281 + unset( $r['links']['edit'] );
282 + }
283 +
284 + // See if links need to be unset
285 + $topic_status = bbp_get_topic_status( $r['id'] );
286 + if ( in_array( $topic_status, [ bbp_get_spam_status_id(), bbp_get_trash_status_id(), bbp_get_pending_status_id() ], true ) ) {
287 +
288 + // Spam link shouldn't be visible on trashed topics
289 + if ( bbp_get_trash_status_id() === $topic_status ) {
290 + unset( $r['links']['spam'] );
291 +
292 + // Trash link shouldn't be visible on spam topics
293 + } elseif ( bbp_get_spam_status_id() === $topic_status ) {
294 + unset( $r['links']['trash'] );
295 + }
296 + }
297 +
298 + // Build our dropdown menu structure.
299 + $menu = '<select name="topic-admin-links" class="frmx-nice-select bbp-admin-dropdown-menu" onchange="if(this.value && this.options[this.selectedIndex].getAttribute(\'data-action\') !== \'edit\') window.location.href=this.value;">';
300 +
301 + // Placeholder (hidden trigger label).
302 + $menu .= '<option value="" disabled selected data-action="trigger">' . esc_html__( 'Actions', 'forumax' ) . '</option>';
303 +
304 + // Split links into primary and destructive groups.
305 + $destructive_keys = [ 'trash', 'spam', 'approve' ];
306 + $primary_links = [];
307 + $danger_links = [];
308 +
309 + foreach ( array_filter( $r['links'] ) as $key => $link ) {
310 + if ( in_array( $key, $destructive_keys, true ) ) {
311 + $danger_links[ $key ] = $link;
312 + } else {
313 + $primary_links[ $key ] = $link;
314 + }
315 + }
316 +
317 + // Render primary actions.
318 + foreach ( $primary_links as $key => $link ) {
319 + preg_match( '/href=(["\'])([^"\']+)\\1/', $link, $matches );
320 + $href = ! empty( $matches[2] ) ? $matches[2] : '';
321 + $menu .= '<option value="' . esc_url( $href ) . '" class="bbp-admin-dropdown-item" data-action="' . esc_attr( $key ) . '">' . wp_strip_all_tags( $link ) . '</option>';
322 + }
323 +
324 + // Visual separator between primary and destructive groups.
325 + if ( ! empty( $danger_links ) && ! empty( $primary_links ) ) {
326 + $menu .= '<option disabled data-action="separator">──────</option>';
327 + }
328 +
329 + // Render destructive actions.
330 + foreach ( $danger_links as $key => $link ) {
331 + preg_match( '/href=(["\'])([^"\']+)\\1/', $link, $matches );
332 + $href = ! empty( $matches[2] ) ? $matches[2] : '';
333 + $menu .= '<option value="' . esc_url( $href ) . '" class="bbp-admin-dropdown-item" data-action="' . esc_attr( $key ) . '">' . wp_strip_all_tags( $link ) . '</option>';
334 + }
335 +
336 + $menu .= '</select>';
337 +
338 + $retval = $r['before'] . $menu . $r['after'];
339 +
340 + // Filter & return
341 + return apply_filters( 'forumax_bbp_get_topic_admin_links', $retval, $r, $args );
342 + }
343 +}
344 +
345 +/**
346 + * Enqueue the script for sorting bbPress replies
347 + */
348 +if ( ! function_exists( 'forumax_enqueue_bbp_replies_sorting_script' ) ) {
349 + /**
350 + * Enqueue replies sorting script.
351 + */
352 + function forumax_enqueue_bbp_replies_sorting_script() {
353 + wp_localize_script( 'bbp-replies-sorting', 'bbp_reply_ajax_obj', [
354 + 'ajax_url' => admin_url( 'admin-ajax.php' ),
355 + 'bbp_topic_id' => get_the_ID(),
356 + ] );
357 + }
358 + add_action( 'wp_enqueue_scripts', 'forumax_enqueue_bbp_replies_sorting_script' );
359 +}
360 +
361 +if ( ! function_exists( 'forumax_ajax_sort_bbp_replies' ) ) {
362 + /**
363 + * AJAX handler for sorting replies.
364 + */
365 + function forumax_ajax_sort_bbp_replies() {
366 + check_ajax_referer( 'forumax-nonce', 'nonce' );
367 +
368 + if ( empty( $_POST['topic_id'] ) ) {
369 + wp_send_json_error( [ 'message' => 'No topic ID provided.' ] );
370 + }
371 + $topic_id = intval( $_POST['topic_id'] );
372 + $sort = isset( $_POST['sort'] ) ? sanitize_text_field( wp_unslash( $_POST['sort'] ) ) : 'desc';
373 +
374 + // Set $_GET['sort'] for the filter to pick up
375 + $_GET['sort'] = $sort;
376 +
377 + // Start output buffering to capture the replies loop HTML
378 + ob_start();
379 + // Set up bbPress replies loop manually for AJAX context
380 + $replies_args = [
381 + 'post_parent' => $topic_id,
382 + 'post_type' => bbp_get_reply_post_type(),
383 + 'orderby' => ( 'vote' === $sort ? 'meta_value_num' : 'date' ),
384 + 'order' => ( 'asc' === $sort ? 'ASC' : 'DESC' ),
385 + 'meta_key' => ( 'vote' === $sort ? 'bbp_voting_score' : '' ),
386 + 'posts_per_page' => -1,
387 + ];
388 + if ( bbp_has_replies( $replies_args ) ) {
389 + while ( bbp_replies() ) :
390 + bbp_the_reply();
391 + $template_file = plugin_dir_path( __FILE__ ) . '../templates/loop-single-reply.php';
392 + if ( file_exists( $template_file ) ) {
393 + include $template_file;
394 + }
395 + endwhile;
396 + } else {
397 + echo '<div>No replies found.</div>';
398 + }
399 + $html = ob_get_clean();
400 +
401 + wp_send_json_success( [ 'html' => $html ] );
402 + }
403 + add_action( 'wp_ajax_sort_bbp_replies', 'forumax_ajax_sort_bbp_replies' );
404 + add_action( 'wp_ajax_nopriv_sort_bbp_replies', 'forumax_ajax_sort_bbp_replies' );
405 +}
406 +
407 +/**
408 + * Remove unnecessary pipe separator from bbPress subscription links.
409 + */
410 +add_filter( 'bbp_get_user_subscribe_link', 'forumax_remove_subscribe_separator', 10, 1 );
411 +
412 +/**
413 + * Remove subscribe separator.
414 + *
415 + * @param string $html The HTML.
416 + * @return string Modified HTML.
417 + */
418 +function forumax_remove_subscribe_separator( $html ) {
419 + $html = str_replace( '&nbsp;|&nbsp;', '', $html );
420 + return $html;
421 +}
422 +
423 +/**
424 + * Restrict visibility of the "modified" notice to the author only.
425 + *
426 + * @param string $log The log output.
427 + * @param int $post_id The post ID.
428 + * @return string The log if allowed, empty string otherwise.
429 + */
430 +function forumax_restrict_revision_log_visibility( $log, $post_id ) {
431 + if ( ! is_user_logged_in() ) {
432 + return '';
433 + }
434 +
435 + $current_user_id = bbp_get_current_user_id();
436 + $post = get_post( $post_id );
437 +
438 + if ( ! empty( $post ) && ( int ) $post->post_author === ( int ) $current_user_id ) {
439 + return $log;
440 + }
441 +
442 + return '';
443 +}
444 +add_filter( 'bbp_get_reply_revision_log', 'forumax_restrict_revision_log_visibility', 10, 2 );
445 +add_filter( 'bbp_get_topic_revision_log', 'forumax_restrict_revision_log_visibility', 10, 2 );