PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.1
Forumax – AI Powered Advanced Community Forum Plugin v2.5.1
2.5.1 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 All 31 releases
bbp-core / includes / features / frmx-inline-edit.php

frmx-inline-edit.php in Forumax – AI Powered Advanced Community Forum Plugin 2.5.1, at includes/features/frmx-inline-edit.php

296 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Forumax Inline Edit Feature
4 *
5 * Provides AJAX-based inline editing for topics and replies
6 * so users can edit content without leaving the page.
7 *
8 * @package Forumax
9 */
10
11 defined( 'ABSPATH' ) || exit;
12
13 /**
14 * Class Forumax_Inline_Edit
15 *
16 * Handles inline editing for bbPress topics and replies.
17 */
18 class Forumax_Inline_Edit {
19
20 /**
21 * Constructor — register AJAX actions.
22 */
23 public function __construct() {
24 // Topic inline edit actions.
25 add_action( 'wp_ajax_forumax_get_topic_content', array( $this, 'get_topic_content' ) );
26 add_action( 'wp_ajax_forumax_save_topic_edit', array( $this, 'save_topic_edit' ) );
27
28 // Reply inline edit actions.
29 add_action( 'wp_ajax_forumax_get_reply_content', array( $this, 'get_reply_content' ) );
30 add_action( 'wp_ajax_forumax_save_reply_edit', array( $this, 'save_reply_edit' ) );
31 }
32
33 /**
34 * Revision created by the current wp_update_post() call.
35 *
36 * @var int
37 */
38 private $revision_id = 0;
39
40 /**
41 * Start recording the revision wp_update_post() creates.
42 *
43 * @return void
44 */
45 private function watch_revisions() {
46 $this->revision_id = 0;
47 add_action( '_wp_put_post_revision', array( $this, 'record_revision' ) );
48 }
49
50 /**
51 * Stop recording and return the recorded revision ID.
52 *
53 * @return int
54 */
55 private function stop_watching_revisions() {
56 remove_action( '_wp_put_post_revision', array( $this, 'record_revision' ) );
57 return (int) $this->revision_id;
58 }
59
60 /**
61 * Remember a freshly stored revision.
62 *
63 * @param int $revision_id Revision ID.
64 * @return void
65 */
66 public function record_revision( $revision_id ) {
67 $this->revision_id = (int) $revision_id;
68 }
69
70 /**
71 * AJAX handler: Get raw topic content for editing.
72 *
73 * Returns the topic title and raw content for the inline editor.
74 */
75 public function get_topic_content() {
76 check_ajax_referer( 'forumax-nonce', 'nonce' );
77
78 $topic_id = isset( $_POST['topic_id'] ) ? absint( $_POST['topic_id'] ) : 0;
79
80 if ( ! $topic_id ) {
81 wp_send_json_error( array( 'message' => esc_html__( 'Invalid topic ID.', 'forumax' ) ) );
82 }
83
84 // Verify the user can edit this topic.
85 if ( ! current_user_can( 'edit_post', $topic_id ) ) {
86 wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to edit this topic.', 'forumax' ) ) );
87 }
88
89 $topic = get_post( $topic_id );
90
91 if ( ! $topic || bbp_get_topic_post_type() !== $topic->post_type ) {
92 wp_send_json_error( array( 'message' => esc_html__( 'Topic not found.', 'forumax' ) ) );
93 }
94
95 wp_send_json_success( array(
96 'title' => $topic->post_title,
97 'content' => $topic->post_content,
98 ) );
99 }
100
101 /**
102 * AJAX handler: Save inline-edited topic.
103 *
104 * Updates the topic title and content via AJAX.
105 */
106 public function save_topic_edit() {
107 check_ajax_referer( 'forumax-nonce', 'nonce' );
108
109 $topic_id = isset( $_POST['topic_id'] ) ? absint( $_POST['topic_id'] ) : 0;
110
111 if ( ! $topic_id ) {
112 wp_send_json_error( array( 'message' => esc_html__( 'Invalid topic ID.', 'forumax' ) ) );
113 }
114
115 // Verify the user can edit this topic.
116 if ( ! current_user_can( 'edit_post', $topic_id ) ) {
117 wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to edit this topic.', 'forumax' ) ) );
118 }
119
120 $topic = get_post( $topic_id );
121
122 if ( ! $topic || bbp_get_topic_post_type() !== $topic->post_type ) {
123 wp_send_json_error( array( 'message' => esc_html__( 'Topic not found.', 'forumax' ) ) );
124 }
125
126 // Sanitize inputs.
127 $new_title = isset( $_POST['title'] ) ? sanitize_text_field( wp_unslash( $_POST['title'] ) ) : '';
128 $new_content = isset( $_POST['content'] ) ? wp_kses_post( wp_unslash( $_POST['content'] ) ) : '';
129
130 if ( empty( $new_title ) ) {
131 wp_send_json_error( array( 'message' => esc_html__( 'Title cannot be empty.', 'forumax' ) ) );
132 }
133
134 if ( empty( $new_content ) ) {
135 wp_send_json_error( array( 'message' => esc_html__( 'Content cannot be empty.', 'forumax' ) ) );
136 }
137
138 // Update the topic. wp_update_post() expects slashed data; passing
139 // unslashed values stripped every backslash from the content.
140 $this->watch_revisions();
141 $updated = wp_update_post( wp_slash( array(
142 'ID' => $topic_id,
143 'post_title' => $new_title,
144 'post_content' => $new_content,
145 ) ), true );
146
147 if ( is_wp_error( $updated ) ) {
148 wp_send_json_error( array( 'message' => $updated->get_error_message() ) );
149 }
150
151 // Log the revision (bbPress tracks edits). wp_update_post() already
152 // stored it, so a second wp_save_post_revision() call returns nothing.
153 $revision_id = $this->stop_watching_revisions() ?: wp_save_post_revision( $topic_id );
154 if ( $revision_id ) {
155 bbp_update_topic_revision_log( array(
156 'topic_id' => $topic_id,
157 'revision_id' => $revision_id,
158 'author_id' => bbp_get_current_user_id(),
159 'reason' => esc_html__( 'Inline edit', 'forumax' ),
160 ) );
161 }
162
163 // Return the rendered content for display.
164 $updated_topic = get_post( $topic_id );
165
166 wp_send_json_success( array(
167 'title' => esc_html( $updated_topic->post_title ),
168 'content' => apply_filters( 'bbp_get_topic_content', $updated_topic->post_content, $topic_id ),
169 'message' => esc_html__( 'Topic updated successfully.', 'forumax' ),
170 ) );
171 }
172
173 /**
174 * AJAX handler: Get raw reply content for editing.
175 *
176 * Returns the reply raw content for the inline editor.
177 */
178 public function get_reply_content() {
179 check_ajax_referer( 'forumax-nonce', 'nonce' );
180
181 $reply_id = isset( $_POST['reply_id'] ) ? absint( $_POST['reply_id'] ) : 0;
182
183 if ( ! $reply_id ) {
184 wp_send_json_error( array( 'message' => esc_html__( 'Invalid reply ID.', 'forumax' ) ) );
185 }
186
187 // Verify the user can edit this reply.
188 if ( ! current_user_can( 'edit_post', $reply_id ) ) {
189 wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to edit this reply.', 'forumax' ) ) );
190 }
191
192 $reply = get_post( $reply_id );
193
194 if ( ! $reply || bbp_get_reply_post_type() !== $reply->post_type ) {
195 wp_send_json_error( array( 'message' => esc_html__( 'Reply not found.', 'forumax' ) ) );
196 }
197
198 wp_send_json_success( array(
199 'content' => $reply->post_content,
200 ) );
201 }
202
203 /**
204 * AJAX handler: Save inline-edited reply.
205 *
206 * Updates the reply content via AJAX.
207 */
208 public function save_reply_edit() {
209 check_ajax_referer( 'forumax-nonce', 'nonce' );
210
211 $reply_id = isset( $_POST['reply_id'] ) ? absint( $_POST['reply_id'] ) : 0;
212
213 if ( ! $reply_id ) {
214 wp_send_json_error( array( 'message' => esc_html__( 'Invalid reply ID.', 'forumax' ) ) );
215 }
216
217 // Verify the user can edit this reply.
218 if ( ! current_user_can( 'edit_post', $reply_id ) ) {
219 wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to edit this reply.', 'forumax' ) ) );
220 }
221
222 $reply = get_post( $reply_id );
223
224 if ( ! $reply || bbp_get_reply_post_type() !== $reply->post_type ) {
225 wp_send_json_error( array( 'message' => esc_html__( 'Reply not found.', 'forumax' ) ) );
226 }
227
228 // Sanitize input.
229 $new_content = isset( $_POST['content'] ) ? wp_kses_post( wp_unslash( $_POST['content'] ) ) : '';
230
231 if ( empty( $new_content ) ) {
232 wp_send_json_error( array( 'message' => esc_html__( 'Content cannot be empty.', 'forumax' ) ) );
233 }
234
235 // Update the reply (slashed, see save_topic_edit()).
236 $this->watch_revisions();
237 $updated = wp_update_post( wp_slash( array(
238 'ID' => $reply_id,
239 'post_content' => $new_content,
240 ) ), true );
241
242 if ( is_wp_error( $updated ) ) {
243 wp_send_json_error( array( 'message' => $updated->get_error_message() ) );
244 }
245
246 // Log the revision (bbPress tracks edits).
247 $revision_id = $this->stop_watching_revisions() ?: wp_save_post_revision( $reply_id );
248 if ( $revision_id ) {
249 bbp_update_reply_revision_log( array(
250 'reply_id' => $reply_id,
251 'revision_id' => $revision_id,
252 'author_id' => bbp_get_current_user_id(),
253 'reason' => esc_html__( 'Inline edit', 'forumax' ),
254 ) );
255 }
256
257 // Return the rendered content for display.
258 $updated_reply = get_post( $reply_id );
259
260 // Set up global post data so action hooks can access the correct reply.
261 global $post;
262 $original_post = $post;
263 $post = $updated_reply;
264 setup_postdata( $post );
265
266 // Capture the full rendered content including action hooks.
267 ob_start();
268
269 // Trigger before content action (includes "Replying to" label).
270 do_action( 'bbp_theme_before_reply_content' );
271
272 // Output the reply content using bbPress function.
273 bbp_reply_content( $reply_id );
274
275 // Trigger after content action (includes nested reply button).
276 do_action( 'bbp_theme_after_reply_content' );
277
278 $rendered_content = ob_get_clean();
279
280 // Restore original post data.
281 $post = $original_post;
282 if ( $original_post ) {
283 setup_postdata( $original_post );
284 }
285 wp_reset_postdata();
286
287 wp_send_json_success( array(
288 'content' => $rendered_content,
289 'message' => esc_html__( 'Reply updated successfully.', 'forumax' ),
290 ) );
291 }
292 }
293
294 // Initialize the inline edit feature.
295 new Forumax_Inline_Edit();
296