PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.0.13
Kirki – Freeform Page Builder, Website Builder & Customizer v6.0.13
6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / includes / Ajax / Collaboration / Collaboration.php
kirki / includes / Ajax / Collaboration Last commit date
Collaboration.php 3 weeks ago Sender.php 2 months ago
Collaboration.php
353 lines
1 <?php
2 /**
3 * Collaboration controller
4 *
5 * @package kirki
6 */
7
8 namespace Kirki\Ajax\Collaboration;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 use Kirki\HelperFunctions;
15
16 /**
17 * Collaboration class for running and getting the collaboration process.
18 */
19 class Collaboration {
20
21 /**
22 * This method will trigger from builder ajax actions.
23 * This method will save all type and action related data inside data column.
24 *
25 * @return void wp_send_json
26 */
27 public static function save_actions() {
28 // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
29 $data = isset( $_POST['data'] ) ? $_POST['data'] : null;
30 // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended
31 $session_id = HelperFunctions::sanitize_text( isset( $_POST['session_id'] ) ? sanitize_text_field( wp_unslash( $_POST['session_id'] ) ) : '' );
32 if ( empty( $data ) ) {
33 wp_send_json_error( array( 'message' => 'No data received' ) );
34 }
35 // Decode the JSON data
36 $data = json_decode( stripslashes( $data ), true );
37
38 $results = array();
39
40 // Handle both single action and batch of actions
41 if ( isset( $data[0] ) && is_array( $data[0] ) ) {
42 // Batch mode
43 foreach ( $data as $item ) {
44 $parent = isset( $item['parent'] ) ? $item['parent'] : '';
45 $parent_id = isset( $item['parent_id'] ) ? $item['parent_id'] : '';
46 $action = isset( $item['action'] ) ? $item['action'] : '';
47 $status = 1;
48
49 $result = self::save_action_to_db( $parent, $parent_id, $action, $status, $session_id );
50 if ( $result ) {
51 $results[] = $result;
52 }
53 }
54 } else {
55 // Single action (backward compatible)
56 $parent = isset( $data['parent'] ) ? $data['parent'] : '';
57 $parent_id = isset( $data['parent_id'] ) ? $data['parent_id'] : '';
58 $action = isset( $data['action'] ) ? $data['action'] : '';
59 $status = 1;
60
61 $result = self::save_action_to_db( $parent, $parent_id, $action, $status, $session_id );
62 if ( $result ) {
63 $results[] = $result;
64 }
65 }
66
67 wp_send_json_success( $results );
68 }
69
70
71 /**
72 * Save action to db function will save a event if more than one people connected.
73 *
74 * @param string $parent post | styleblock for now.
75 * @param string $parent_id post id or 0 if global data.
76 * @param array $data total event data.
77 * @param int $status event status.
78 *
79 * @return bool,array if success.
80 *
81 * @deprecated
82 * @see Kirki\App\Services\CollaborationService::save_action()
83 */
84 public static function save_action_to_db( $parent, $parent_id, $data, $status = 1, $session_id = '', $cleanup = true ) {
85 if ( count( self::get_all_connected_rows( $cleanup ) ) > 1 ) {
86 $user_id = get_current_user_id();
87
88 global $wpdb;
89 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
90 $wpdb->insert(
91 $wpdb->prefix . KIRKI_COLLABORATION_TABLE,
92 array(
93 'user_id' => (int) $user_id,
94 'session_id' => $session_id,
95 'parent' => $parent,
96 'parent_id' => (int) $parent_id,
97 'data' => wp_json_encode( $data ),
98 'status' => (int) $status,
99 ),
100 array(
101 '%d',
102 '%s',
103 '%s',
104 '%d',
105 '%s',
106 '%d',
107 )
108 );
109 return array(
110 'id' => $wpdb->insert_id,
111 'session_id' => $session_id,
112 );
113 }
114
115 return false;
116 }
117
118 /**
119 * Send action is The main eventsouce function.
120 * this method will start the eventsouce mechanism.
121 *
122 * @return void
123 */
124 public static function send_actions() {
125 self::save_connection_data();
126 $sender = new Sender();
127 $sender->start();
128 self::clean_disconnected_rows();
129 exit();
130 }
131
132 /**
133 * Save connection data
134 */
135 private static function save_connection_data() {
136 // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotValidated
137 $session_id = HelperFunctions::sanitize_text( sanitize_text_field( wp_unslash( $_GET['session_id'] ) ) );
138 // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotValidated
139 $post_id = HelperFunctions::sanitize_text( sanitize_text_field( wp_unslash( $_GET['post_id'] ) ) );
140 $user_id = get_current_user_id();
141
142 if ( ! self::get_connection( $session_id ) ) {
143 self::add_connection( $user_id, $session_id, $post_id );
144 }
145
146 }
147
148 /**
149 * Add connection if a user is give eventsource request.
150 *
151 * @param int $user_id wp user id.
152 * @param string $session_id current user session id.
153 * @param int $post_id current post id.
154 */
155 private static function add_connection( $user_id, $session_id, $post_id ) {
156 global $wpdb;
157 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
158 $wpdb->insert(
159 $wpdb->prefix . KIRKI_COLLABORATION_TABLE . '_connected',
160 array(
161 'user_id' => (int) $user_id,
162 'post_id' => (int) $post_id,
163 'session_id' => $session_id,
164 ),
165 array(
166 '%d',
167 '%d',
168 '%s',
169 )
170 );
171 if ( $wpdb->insert_id ) {
172 // TODO: send connection id to all connected users for add
173 $this_connection = self::get_connection( $session_id );
174
175 $data = array(
176 'type' => 'COLLABORATION_ADD_CONNECTION',
177 'payload' => array( 'data' => $this_connection ),
178 );
179 self::save_action_to_db( 'post', $post_id, $data, 1, $session_id );
180
181 return $this_connection;
182 }
183 return false;
184 }
185
186 /**
187 * Get single connection.
188 *
189 * @param string $session_id current user session id.
190 *
191 * @return bool,array
192 */
193 public static function get_connection( $session_id ) {
194 global $wpdb;
195 $query = $wpdb->prepare(
196 'SELECT * FROM %1s WHERE session_id = %s',
197 $wpdb->prefix . KIRKI_COLLABORATION_TABLE . '_connected',
198 $session_id
199 );
200 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
201 $this_connection = $wpdb->get_row( $query );
202 if ( $this_connection ) {
203 $date = gmdate( 'Y-m-d H:i:s' );
204 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
205 $wpdb->update(
206 $wpdb->prefix . KIRKI_COLLABORATION_TABLE . '_connected',
207 array(
208 'updated_at' => $date,
209 ),
210 array(
211 'session_id' => $session_id,
212 ),
213 array(
214 '%s',
215 ),
216 array(
217 '%s',
218 )
219 );
220 return self::format_connection_data( $this_connection );
221 }
222 return false;
223 }
224
225 /**
226 * Get all connected rows.
227 * first clean disconnected rows then get only recent connected rows.
228 *
229 * @param bool $cleanup if true will clean disconnected rows.
230 * @return array
231 *
232 * @deprecated
233 * @see Kirki\App\Services\CollaborationService::get_all_connected_rows()
234 */
235 public static function get_all_connected_rows( $cleanup = true ) {
236 if ( $cleanup ) {
237 self::clean_disconnected_rows();
238 }
239 global $wpdb;
240 $query2 = $wpdb->prepare(
241 'SELECT * FROM %1s',
242 $wpdb->prefix . KIRKI_COLLABORATION_TABLE . '_connected'
243 );
244 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
245 $res = $wpdb->get_results( $query2 );
246 return $res;
247 }
248
249 public static function get_connected_collaboration_users_list( $post_id ) {
250 global $wpdb;
251 $query = $wpdb->prepare(
252 'SELECT * FROM %1s WHERE post_id = %d',
253 $wpdb->prefix . KIRKI_COLLABORATION_TABLE . '_connected',
254 $post_id
255 );
256 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
257 $res = $wpdb->get_results( $query );
258
259 foreach ( $res as $key => $connection ) {
260 $res[ $key ] = self::format_connection_data( $connection );
261 }
262 return $res;
263 }
264
265 private static function format_connection_data( $connection ) {
266 $connection->user_name = get_the_author_meta( 'display_name', $connection->user_id );
267 return $connection;
268 }
269
270 /**
271 * Clean disconnected rows if inactive less then 50 seconds.
272 *
273 * @return void
274 *
275 * @deprecated
276 * @see Kirki\App\Services\CollaborationService::clean_disconnected_rows()
277 */
278 public static function clean_disconnected_rows() {
279 global $wpdb;
280
281 $fifty_seconds_ago = gmdate( 'Y-m-d H:i:s', strtotime( '-20 seconds' ) );
282 $table_name = $wpdb->prefix . KIRKI_COLLABORATION_TABLE . '_connected';
283
284 // Get all expired connections (session_id + post_id in one query)
285 $query = $wpdb->prepare(
286 "SELECT session_id, post_id FROM $table_name WHERE updated_at <= %s",
287 $fifty_seconds_ago
288 );
289
290 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
291 $connections = $wpdb->get_results( $query );
292
293 if ( empty( $connections ) ) {
294 return;
295 }
296
297 // Step 1: Broadcast removal for each connection
298 foreach ( $connections as $connection ) {
299 $data = array(
300 'type' => 'COLLABORATION_REMOVE_CONNECTION',
301 'payload' => array( 'session_id' => $connection->session_id ),
302 );
303 self::save_action_to_db( 'post', $connection->post_id, $data, 1, $connection->session_id, false );
304 }
305
306 // Step 2: Bulk delete all expired sessions in one query
307 $session_ids = wp_list_pluck( $connections, 'session_id' );
308 $placeholders = implode( ',', array_fill( 0, count( $session_ids ), '%s' ) );
309
310 $delete_sql = $wpdb->prepare(
311 "DELETE FROM $table_name WHERE session_id IN ($placeholders)",
312 $session_ids
313 );
314
315 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
316 $wpdb->query( $delete_sql );
317 }
318
319 /**
320 * Delete connection if a user gives event close request.
321 *
322 * @param string $session_id current user session id.
323 * @param bool $send whether to broadcast removal.
324 */
325 public static function delete_connection( $session_id ) {
326 global $wpdb;
327
328 $table_name = $wpdb->prefix . KIRKI_COLLABORATION_TABLE . '_connected';
329
330 $query = $wpdb->prepare(
331 "SELECT * FROM $table_name WHERE session_id = %s",
332 $session_id
333 );
334
335 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
336 $this_connection = $wpdb->get_row( $query );
337
338 if ( $this_connection ) {
339 $post_id = $this_connection->post_id;
340 $data = array(
341 'type' => 'COLLABORATION_REMOVE_CONNECTION',
342 'payload' => array( 'session_id' => $session_id ),
343 );
344
345 self::save_action_to_db( 'post', $post_id, $data, 1, $session_id );
346 }
347
348 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
349 $wpdb->delete( $table_name, array( 'session_id' => $session_id ) );
350 }
351
352 }
353