PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.5.4
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.5.4
3.5.4 3.5.3 3.5.2 3.5.1 3.4.9 3.5.0 3.4.8 3.4.7 trunk 2.3.1 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6
← All changes | includes/class-wpsc-functions.php +268 -24 3.4.5 → 3.5.4 View file →
@@ -20,8 +20,12 @@
20 20 public static function init() {
21 21
22 22 // Load ref classes.
23 23 add_action( 'init', array( __CLASS__, 'load_ref_classes' ), 1 );
24 +
25 + // Refresh paid customer option when plugins state changes.
26 + add_action( 'activated_plugin', array( __CLASS__, 'update_paid_customer_status_option' ) );
27 + add_action( 'deactivated_plugin', array( __CLASS__, 'update_paid_customer_status_option' ), 99 );
24 28 }
25 29
26 30 /**
27 31 * Load ref classes
@@ -68,8 +72,23 @@
68 72 $object = WPSC_Customer::get_anonymous_customer();
69 73 }
70 74 break;
71 75
76 + case 'wpsc_ticket':
77 + if ( $value && isset( self::$ref_classes[ $ref_class ] ) ) {
78 + $class = self::$ref_classes[ $ref_class ]['class'];
79 + $object = new $class( $value );
80 + // A ticket referenced from another record (a todo, a thread, a timer log, ...)
81 + // may have since been archived - archived tickets are moved out of the live
82 + // tickets table entirely, so the lookup above resolves to an empty object. Fall
83 + // back to the archived-ticket table rather than silently returning a blank
84 + // ticket (blank id, blank subject, blank link) to every caller.
85 + if ( ! $object->id ) {
86 + $object = new WPSC_Archive_Ticket( $value );
87 + }
88 + }
89 + break;
90 +
72 91 default:
73 92 $object = apply_filters( 'wpsc_fun_get_object', $value, $ref_class );
74 93 if ( ! is_object( $object ) && isset( self::$ref_classes[ $ref_class ] ) ) {
75 94 $class = self::$ref_classes[ $ref_class ]['class'];
@@ -482,8 +501,11 @@
482 501 * @param string $format - date format.
483 502 * @return boolean
484 503 */
485 504 public static function is_valid_date( $date, $format = 'Y-m-d' ) {
505 + if ( preg_match( '/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}(:\d{2})?$/', $date ) ) {
506 + return self::is_valid_datetime( $date );
507 + }
486 508 $d = DateTime::createFromFormat( $format, $date );
487 509 return $d && $d->format( $format ) === $date;
488 510 }
489 511
@@ -497,8 +519,11 @@
497 519
498 520 if ( ! is_string( $datetime ) ) {
499 521 return false;
500 522 }
523 + if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $datetime ) ) {
524 + return self::is_valid_date( $datetime );
525 + }
501 526 $datetime = trim( $datetime );
502 527 $formats = array(
503 528 'Y-m-d H:i',
504 529 'Y-m-d H:i:s',
@@ -1243,35 +1268,254 @@
1243 1268 * @return string
1244 1269 */
1245 1270 public static function get_ticket_url( $ticket_id, $view ) {
1246 1271
1247 - $url = '';
1272 + $ticket_id = absint( $ticket_id );
1273 + $view = (int) $view;
1274 + $url = '';
1275 +
1276 + if ( ! $ticket_id ) {
1277 + return $url;
1278 + }
1279 +
1280 + $page_settings = get_option( 'wpsc-gs-page-settings', array() );
1281 +
1282 + // Detect ticket type.
1283 + $is_archive = false;
1284 + $ticket = new WPSC_Ticket( $ticket_id );
1285 + if ( ! $ticket->id ) {
1286 + $ticket = new WPSC_Archive_Ticket( $ticket_id );
1287 + if ( ! $ticket->id ) {
1288 + return $url;
1289 + }
1290 + $is_archive = true;
1291 + }
1292 +
1293 + $type = $is_archive
1294 + ? 'wpsc-archive-tickets&section=archive-ticket-list'
1295 + : 'wpsc-tickets&section=ticket-list';
1296 +
1297 + $admin_url = admin_url( 'admin.php?page=' . $type . '&id=' . $ticket_id );
1298 +
1299 + // Backend view always wins.
1300 + if ( $view === 0 || $is_archive ) {
1301 + return apply_filters( 'wpsc_get_ticket_url_by_view', $admin_url, $ticket_id, $view );
1302 + }
1303 +
1304 + // Frontend view (normal tickets only).
1305 + if ( $view === 1 ) {
1306 +
1307 + $ticket_url_page = $page_settings['ticket-url-page'] ?? '';
1308 + $support_page = absint( $page_settings['support-page'] ?? 0 );
1309 + $open_page = absint( $page_settings['open-ticket-page'] ?? 0 );
1310 +
1311 + if ( $ticket_url_page === 'support-page' && $support_page ) {
1312 +
1313 + $url = add_query_arg(
1314 + array(
1315 + 'wpsc-section' => 'ticket-list',
1316 + 'ticket-id' => $ticket_id,
1317 + ),
1318 + get_permalink( $support_page )
1319 + );
1320 +
1321 + } elseif ( $ticket_url_page === 'open-ticket-page' && $open_page && $ticket->auth_code ) {
1322 +
1323 + $url = add_query_arg(
1324 + array(
1325 + 'ticket-id' => $ticket_id,
1326 + 'auth-code' => $ticket->auth_code,
1327 + ),
1328 + get_permalink( $open_page )
1329 + );
1330 + }
1331 + }
1332 +
1333 + if ( ! $url ) {
1334 + $url = $admin_url;
1335 + }
1336 +
1337 + return apply_filters( 'wpsc_get_ticket_url_by_view', $url, $ticket_id, $view );
1338 + }
1339 +
1340 + /**
1341 + * Get unique, non-empty closed statuses from merged settings.
1342 + *
1343 + * Merges advanced and general settings, filters out empty values,
1344 + * ensures uniqueness, and returns an indexed array.
1345 + *
1346 + * @return array Unique, non-empty closed statuses.
1347 + */
1348 + public static function get_closed_statuses() {
1349 +
1350 + $tl_ms_advance_settings = get_option( 'wpsc-tl-ms-advanced' );
1351 + $general_settings = get_option( 'wpsc-gs-general' );
1352 +
1353 + // Merge, filter non-empty, get unique values, and convert all to string.
1354 + $closed_statuses = array_unique(
1355 + array_filter(
1356 + array_merge(
1357 + $tl_ms_advance_settings['closed-ticket-statuses'],
1358 + (array) $general_settings['close-ticket-status']
1359 + ),
1360 + function ( $statuses ) {
1361 + return ! empty( $statuses );
1362 + }
1363 + )
1364 + );
1365 + return array_map( 'strval', $closed_statuses ); // Ensure indexed array.
1366 + }
1367 +
1368 + /**
1369 + * Check whether any SupportCandy addon is active.
1370 + *
1371 + * @return bool
1372 + */
1373 + public static function is_paid_customer() {
1374 +
1375 + $cache_key = 'wpsc_is_paid_customer';
1376 + $cached_status = get_option( $cache_key, null );
1377 +
1378 + if ( null !== $cached_status ) {
1379 + return (bool) $cached_status;
1380 + }
1381 +
1382 + return self::update_paid_customer_status_option();
1383 + }
1384 +
1385 + /**
1386 + * Update the paid customer status option based on active addons.
1387 + *
1388 + * @param string $plugin - Optional plugin path to exclude from check (used during activation/deactivation).
1389 + * @return bool Updated paid customer status.
1390 + */
1391 + public static function update_paid_customer_status_option( $plugin = '' ) {
1392 + $cache_key = 'wpsc_is_paid_customer';
1393 +
1394 + $addon_plugins = array(
1395 + 'wpsc-agentgroup/wpsc-agentgroup.php',
1396 + 'wpsc-assign-agent-rules/wpsc-assign-agent-rules.php',
1397 + 'wpsc-automatic-close-ticket/wpsc-automatic-close-ticket.php',
1398 + 'wpsc-canned-reply/wpsc-canned-reply.php',
1399 + 'wpsc-edd/wpsc-edd.php',
1400 + 'wpsc-email-marketing-tools/wpsc-email-marketing-tools-integration.php',
1401 + 'wpsc-email-piping/wpsc-email-piping.php',
1402 + 'wpsc-export-ticket/wpsc-export-ticket.php',
1403 + 'wpsc-gravity-forms/wpsc-gravity-form-integration.php',
1404 + 'wpsc-lms/wpsc-lms.php',
1405 + 'wpsc-pressapps-knowledge-base/wpsc-pressapps-knowledge-base.php',
1406 + 'wpsc-print-ticket/wpsc-print-ticket.php',
1407 + 'wpsc-private-credentials/wpsc_private_credentials.php',
1408 + 'wpsc-productivity-suite/wpsc-productivity-suite.php',
1409 + 'wpsc-reports/wpsc-reports.php',
1410 + 'wpsc-satisfaction-survey/wpsc-satisfaction-survey.php',
1411 + 'wpsc-schedule-tickets/wpsc-schedule-tickets.php',
1412 + 'wpsc-sla/wpsc-sla.php',
1413 + 'wpsc-slack/wpsc-slack.php',
1414 + 'wpsc-timer/wpsc-timer.php',
1415 + 'wpsc-ultimate-faq/wpsc-ultimate-faq.php',
1416 + 'wpsc-usergroup/wpsc-usergroup.php',
1417 + 'wpsc-webhooks/wpsc-webhooks.php',
1418 + 'wpsc-woocommerce/wpsc-woocommerce.php',
1419 + 'wpsc-workflows/wpsc-workflows.php',
1420 + );
1421 +
1422 + $active_plugins = (array) get_option( 'active_plugins', array() );
1423 +
1424 + if ( current_filter() === 'deactivated_plugin' && ! empty( $plugin ) ) {
1425 + $active_plugins = array_diff( $active_plugins, array( $plugin ) );
1426 + }
1427 +
1428 + if ( is_multisite() ) {
1429 + $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
1430 + $active_plugins = array_merge( $active_plugins, array_keys( $active_network_plugins ) );
1431 + }
1432 +
1433 + $is_paid_customer = ! empty( array_intersect( $addon_plugins, $active_plugins ) );
1434 + update_option( $cache_key, $is_paid_customer ? 1 : 0 );
1435 + return (bool) $is_paid_customer;
1436 + }
1437 +
1438 + /**
1439 + * Whether SupportCandy front-end scripts/styles should load on the current page,
1440 + * based on Settings -> General -> Page settings -> Load scripts.
1441 + *
1442 + * Translation aware: if the current page is a WPML or Polylang translation of a
1443 + * page selected in the 'Custom' list, scripts still load - the settings screen can
1444 + * only ever store the ID of the page in the language active while saving.
1445 + *
1446 + * @return bool
1447 + */
1448 + public static function is_load_scripts_page() {
1449 +
1248 1450 $page_settings = get_option( 'wpsc-gs-page-settings' );
1249 - $view = (int) $view;
1250 - if ( $view === 0 ) {
1251 - $url = admin_url( 'admin.php?page=wpsc-tickets&section=ticket-list&id=' . $ticket_id );
1252 - } elseif ( $page_settings['ticket-url-page'] == 'support-page' && $page_settings['support-page'] ) {
1253 - $url = get_permalink( $page_settings['support-page'] );
1254 - $url = add_query_arg(
1255 - array(
1256 - 'wpsc-section' => 'ticket-list',
1257 - 'ticket-id' => $ticket_id,
1258 - ),
1259 - $url
1260 - );
1261 - } elseif ( $page_settings['ticket-url-page'] == 'open-ticket-page' && $page_settings['open-ticket-page'] ) {
1262 - $url = get_permalink( $page_settings['open-ticket-page'] );
1263 - $ticket = new WPSC_Ticket( $ticket_id );
1264 - $url = add_query_arg(
1265 - array(
1266 - 'ticket-id' => $ticket_id,
1267 - 'auth-code' => $ticket->auth_code,
1268 - ),
1269 - $url
1270 - );
1451 +
1452 + $load = true;
1453 +
1454 + if ( isset( $page_settings['load-scripts'] ) && $page_settings['load-scripts'] === 'custom' ) {
1455 +
1456 + $current_id = get_the_id();
1457 + $pages = isset( $page_settings['load-script-pages'] ) ? (array) $page_settings['load-script-pages'] : array();
1458 +
1459 + $load = $current_id && self::page_id_in_pages_or_translations( $current_id, $pages );
1271 1460 }
1272 1461
1273 - return apply_filters( 'wpsc_get_ticket_url_by_view', $url, $ticket_id, $view );
1462 + return (bool) apply_filters( 'wpsc_load_frontend_scripts', $load, $page_settings );
1463 + }
1464 +
1465 + /**
1466 + * Check if given post id matches (directly or via translation) any of the given page ids.
1467 + *
1468 + * @param int $current_id current post id.
1469 + * @param array $page_ids selected page ids from settings.
1470 + * @return bool
1471 + */
1472 + private static function page_id_in_pages_or_translations( $current_id, $page_ids ) {
1473 +
1474 + if ( in_array( $current_id, $page_ids ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
1475 + return true;
1476 + }
1477 +
1478 + foreach ( $page_ids as $page_id ) {
1479 +
1480 + // WPML.
1481 + if ( has_filter( 'wpml_object_id' ) ) {
1482 + $translated_id = apply_filters( 'wpml_object_id', $page_id, 'page', false );
1483 + if ( $translated_id && (int) $translated_id === (int) $current_id ) {
1484 + return true;
1485 + }
1486 + }
1487 +
1488 + // Polylang.
1489 + if ( function_exists( 'pll_get_post_translations' ) ) {
1490 + $translations = pll_get_post_translations( $page_id );
1491 + if ( in_array( $current_id, $translations ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
1492 + return true;
1493 + }
1494 + }
1495 + }
1496 +
1497 + return false;
1498 + }
1499 +
1500 + /**
1501 + * Get current active language code, if a multilingual plugin is active.
1502 + *
1503 + * @return string|null
1504 + */
1505 + public static function get_current_language_code() {
1506 +
1507 + if ( has_filter( 'wpml_current_language' ) ) {
1508 + $lang = apply_filters( 'wpml_current_language', null );
1509 + return $lang ? $lang : null;
1510 + }
1511 +
1512 + if ( function_exists( 'pll_current_language' ) ) {
1513 + $lang = pll_current_language();
1514 + return $lang ? $lang : null;
1515 + }
1516 +
1517 + return null;
1274 1518 }
1275 1519 }
1276 1520 endif;
1277 1521