PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.23
MxChat – AI Chatbot & Content Generation for WordPress v3.2.23
3.2.25 3.2.24 3.2.23 3.2.22 3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 All 156 releases
← All changes | includes/class-mxchat-privacy.php +50 -142 3.2.25 → 3.2.23 View file →
@@ -176,13 +176,9 @@
176 176
177 177 $page = max(1, (int) $page);
178 178 $offset = ($page - 1) * self::EXPORT_SESSIONS_PER_PAGE;
179 179
180 - // Every session held for the subject: conversations first, then the
181 - // pre-chat captures that never became one (fb8877). Computed whole on
182 - // each call and sliced, so the pages stay stable.
183 - $all_session_ids = self::get_export_session_ids($email_address);
184 - $session_ids = array_slice($all_session_ids, $offset, self::EXPORT_SESSIONS_PER_PAGE);
180 + $session_ids = self::get_session_ids($email_address, self::EXPORT_SESSIONS_PER_PAGE, $offset);
185 181
186 182 $items = array();
187 183 foreach ($session_ids as $sid) {
188 184 $rows = $wpdb->get_results($wpdb->prepare(
@@ -192,16 +188,8 @@
192 188 ORDER BY timestamp ASC, id ASC",
193 189 $sid
194 190 ));
195 191 if (empty($rows)) {
196 - // No conversation: a visitor who submitted the pre-chat form
197 - // and never sent a message. The plugin still holds their
198 - // email, name and consent record (shown on the Leads tab,
199 - // removed by the eraser), so the export must disclose them.
200 - $item = self::export_store_only_item($sid);
201 - if ($item !== null) {
202 - $items[] = $item;
203 - }
204 192 continue;
205 193 }
206 194
207 195 $emails = array();
@@ -243,9 +231,22 @@
243 231
244 232 // Lead-capture consent record (b062c4). If we hold a consent
245 233 // decision about the subject, the export must show it — same
246 234 // Article 15 parity rule as the click rows below.
247 - $data = array_merge($data, self::export_consent_fields($sid));
235 + if (class_exists('MxChat_Session_Store')) {
236 + $consent_state = MxChat_Session_Store::get($sid, 'consent', '');
237 + if ($consent_state !== '' && $consent_state !== false) {
238 + $data[] = array('name' => __('Lead-capture consent', 'mxchat'), 'value' => (string) $consent_state);
239 + $consent_at = MxChat_Session_Store::get($sid, 'consent_at', '');
240 + if (!empty($consent_at)) {
241 + $data[] = array('name' => __('Consent recorded', 'mxchat'), 'value' => (string) $consent_at);
242 + }
243 + $consent_label = MxChat_Session_Store::get($sid, 'consent_label', '');
244 + if (!empty($consent_label)) {
245 + $data[] = array('name' => __('Consent text shown', 'mxchat'), 'value' => wp_strip_all_tags((string) $consent_label));
246 + }
247 + }
248 + }
248 249
249 250 foreach ($rows as $row) {
250 251 $data[] = array(
251 252 'name' => sprintf('[%s] %s', $row->timestamp, self::role_label($row->role)),
@@ -254,142 +255,49 @@
254 255 }
255 256
256 257 // Link-click rows for this session (Article 15 parity with the
257 258 // eraser — same subject, same session set, plan 23c4a1).
258 - $data = array_merge($data, self::export_click_fields($sid));
259 -
260 - $items[] = self::export_item($sid, $data);
261 - }
262 -
263 - return array(
264 - 'data' => $items,
265 - 'done' => ($offset + self::EXPORT_SESSIONS_PER_PAGE) >= count($all_session_ids),
266 - );
267 - }
268 -
269 - /**
270 - * The subject's full session set for the exporter: transcript sessions in
271 - * their existing order (oldest conversation first), then sessions that
272 - * exist only in the session store — a pre-chat capture with no messages —
273 - * ordered by session id. The eraser already unions the store the same way
274 - * (erase_lingering_options). The store-only set is bounded by lead count.
275 - */
276 - private static function get_export_session_ids($email_address) {
277 - $session_ids = self::get_session_ids($email_address, PHP_INT_MAX, 0);
278 - if (!is_array($session_ids)) {
279 - $session_ids = array();
280 - }
281 -
282 - if (class_exists('MxChat_Session_Store') && method_exists('MxChat_Session_Store', 'find_by_email')) {
283 - $store_only = array_diff(MxChat_Session_Store::find_by_email($email_address), $session_ids);
284 - $store_only = array_values(array_unique(array_map('strval', $store_only)));
285 - sort($store_only, SORT_STRING);
286 - $session_ids = array_merge($session_ids, $store_only);
287 - }
288 -
289 - return $session_ids;
290 - }
291 -
292 - /**
293 - * Export item for a session with no transcript rows. Only what is held:
294 - * the identity the visitor typed, the consent record, and any link-click
295 - * rows. No conversation dates (there is no conversation) and no capture
296 - * time of its own — the store does not expose one, and the consent record
297 - * carries its own timestamp. Null when the store holds nothing to show.
298 - */
299 - private static function export_store_only_item($sid) {
300 - if (!class_exists('MxChat_Session_Store')) {
301 - return null;
302 - }
303 -
304 - $email = MxChat_Session_Store::get($sid, 'email', '');
305 - $name = MxChat_Session_Store::get($sid, 'name', '');
306 -
307 - $data = array(
308 - array('name' => __('Session ID', 'mxchat'), 'value' => $sid),
309 - );
310 - if (!empty($email)) {
311 - $data[] = array('name' => __('Email on record', 'mxchat'), 'value' => (string) $email);
312 - }
313 - if (!empty($name)) {
314 - $data[] = array('name' => __('Name on record', 'mxchat'), 'value' => (string) $name);
315 - }
316 -
317 - $data = array_merge($data, self::export_consent_fields($sid), self::export_click_fields($sid));
318 -
319 - if (count($data) < 2) {
320 - return null;
321 - }
322 -
323 - return self::export_item($sid, $data);
324 - }
325 -
326 - /** Lead-capture consent fields for one session (empty when none recorded). */
327 - private static function export_consent_fields($sid) {
328 - $fields = array();
329 -
330 - if (class_exists('MxChat_Session_Store')) {
331 - $consent_state = MxChat_Session_Store::get($sid, 'consent', '');
332 - if ($consent_state !== '' && $consent_state !== false) {
333 - $fields[] = array('name' => __('Lead-capture consent', 'mxchat'), 'value' => (string) $consent_state);
334 - $consent_at = MxChat_Session_Store::get($sid, 'consent_at', '');
335 - if (!empty($consent_at)) {
336 - $fields[] = array('name' => __('Consent recorded', 'mxchat'), 'value' => (string) $consent_at);
259 + if (self::url_clicks_table_exists()) {
260 + $clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
261 + $clicks = $wpdb->get_results($wpdb->prepare(
262 + "SELECT clicked_url, click_timestamp, user_ip, user_agent
263 + FROM {$clicks_table} WHERE session_id = %s
264 + ORDER BY click_timestamp ASC, id ASC",
265 + $sid
266 + ));
267 + foreach ($clicks as $click) {
268 + $details = $click->clicked_url;
269 + $meta = array();
270 + if (!empty($click->user_ip)) {
271 + /* translators: %s: IP address recorded with a link click */
272 + $meta[] = sprintf(__('IP address: %s', 'mxchat'), $click->user_ip);
273 + }
274 + if (!empty($click->user_agent)) {
275 + /* translators: %s: browser user-agent recorded with a link click */
276 + $meta[] = sprintf(__('Browser: %s', 'mxchat'), $click->user_agent);
277 + }
278 + if (!empty($meta)) {
279 + $details .= ' (' . implode('; ', $meta) . ')';
280 + }
281 + $data[] = array(
282 + 'name' => sprintf('[%s] %s', $click->click_timestamp, __('Link clicked', 'mxchat')),
283 + 'value' => $details,
284 + );
337 285 }
338 - $consent_label = MxChat_Session_Store::get($sid, 'consent_label', '');
339 - if (!empty($consent_label)) {
340 - $fields[] = array('name' => __('Consent text shown', 'mxchat'), 'value' => wp_strip_all_tags((string) $consent_label));
341 - }
342 286 }
343 - }
344 287
345 - return $fields;
346 - }
347 -
348 - /** Link-click fields for one session (empty when none, or no clicks table). */
349 - private static function export_click_fields($sid) {
350 - global $wpdb;
351 - $fields = array();
352 -
353 - if (self::url_clicks_table_exists()) {
354 - $clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
355 - $clicks = $wpdb->get_results($wpdb->prepare(
356 - "SELECT clicked_url, click_timestamp, user_ip, user_agent
357 - FROM {$clicks_table} WHERE session_id = %s
358 - ORDER BY click_timestamp ASC, id ASC",
359 - $sid
360 - ));
361 - foreach ($clicks as $click) {
362 - $details = $click->clicked_url;
363 - $meta = array();
364 - if (!empty($click->user_ip)) {
365 - /* translators: %s: IP address recorded with a link click */
366 - $meta[] = sprintf(__('IP address: %s', 'mxchat'), $click->user_ip);
367 - }
368 - if (!empty($click->user_agent)) {
369 - /* translators: %s: browser user-agent recorded with a link click */
370 - $meta[] = sprintf(__('Browser: %s', 'mxchat'), $click->user_agent);
371 - }
372 - if (!empty($meta)) {
373 - $details .= ' (' . implode('; ', $meta) . ')';
374 - }
375 - $fields[] = array(
376 - 'name' => sprintf('[%s] %s', $click->click_timestamp, __('Link clicked', 'mxchat')),
377 - 'value' => $details,
378 - );
379 - }
288 + $items[] = array(
289 + 'group_id' => 'mxchat-chat-transcripts',
290 + 'group_label' => __('Chat Conversations (MxChat)', 'mxchat'),
291 + 'group_description' => __('Chatbot conversations recorded by the MxChat plugin.', 'mxchat'),
292 + 'item_id' => 'mxchat-session-' . $sid,
293 + 'data' => $data,
294 + );
380 295 }
381 296
382 - return $fields;
383 - }
384 -
385 - private static function export_item($sid, array $data) {
386 297 return array(
387 - 'group_id' => 'mxchat-chat-transcripts',
388 - 'group_label' => __('Chat Conversations (MxChat)', 'mxchat'),
389 - 'group_description' => __('Chatbot conversations recorded by the MxChat plugin.', 'mxchat'),
390 - 'item_id' => 'mxchat-session-' . $sid,
391 - 'data' => $data,
298 + 'data' => $items,
299 + 'done' => count($session_ids) < self::EXPORT_SESSIONS_PER_PAGE,
392 300 );
393 301 }
394 302
395 303 private static function role_label($role) {