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