PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/rest-api.php +161 -31 2.9.0 → 2.12.8 View file →
@@ -84,8 +84,34 @@
84 84 return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
85 85 }
86 86
87 87 /**
88 + * Return the visitor's detected country code.
89 + *
90 + * Public, read-only — resolves the country for the *current* request's IP via
91 + * Helper::get_geo_country(), so the phone field can fetch it per-visitor and
92 + * work on full-page-cached sites. Outbound geolocation calls are bounded by
93 + * the hourly cap inside Helper::get_geo_country().
94 + *
95 + * @since 2.11.1
96 + * @return \WP_REST_Response
97 + */
98 + public function get_geo_country() {
99 + // Pass an empty fallback so '' unambiguously means "not confidently
100 + // detected" (no CDN header and no successful IP lookup). The frontend then
101 + // falls back to a privacy-safe, network-free Intl guess in the browser.
102 + $country = Helper::get_geo_country( '' );
103 +
104 + return new \WP_REST_Response(
105 + [
106 + 'country' => $country,
107 + 'detected' => '' !== $country,
108 + ],
109 + 200
110 + );
111 + }
112 +
113 + /**
88 114 * Get the data for generating entries chart.
89 115 *
90 116 * @param \WP_REST_Request $request Full details about the request.
91 117 * @since 1.0.0
@@ -788,9 +814,9 @@
788 814 }
789 815
790 816 $label_parts = explode( '-lbl-', $field_name );
791 817 $label = isset( $label_parts[1] ) ? explode( '-', $label_parts[1] )[0] : '';
792 - $label = $label ? Helper::decrypt( $label ) : '';
818 + $label = $label ? Helper::decode( $label ) : '';
793 819 $field_block_name = Helper::get_block_name_from_field( $field_name );
794 820
795 821 /**
796 822 * Filter: 'srfm_entry_value'
@@ -796,9 +822,11 @@
796 822 * Filter: 'srfm_entry_value'
797 823 *
798 824 * This filter is used to allow 3rd party plugins or custom code to modify
799 825 * the entry field value in the entry details REST API response, if required.
800 - * For example, you may want to decrypt, format, or mask sensitive data before output.
826 + * For example, you may want to format, mask, or otherwise transform sensitive
827 + * data before output. Note the value reaching this filter is not encrypted by
828 + * SureForms — labels and values are carried as unkeyed base64 at most.
801 829 *
802 830 * @since 2.0.0
803 831 *
804 832 * @param mixed $value The original value for the field.
@@ -850,11 +878,14 @@
850 878 'created_at' => $entry['created_at'],
851 879 'form_data' => $form_data,
852 880 'form_content' => $form_fields,
853 881 'submission_info' => [
854 - 'user_ip' => $entry['submission_info']['user_ip'] ?? '',
855 - 'browser_name' => $entry['submission_info']['browser_name'] ?? '',
856 - 'device_name' => $entry['submission_info']['device_name'] ?? '',
882 + 'user_ip' => $entry['submission_info']['user_ip'] ?? '',
883 + 'browser_name' => $entry['submission_info']['browser_name'] ?? '',
884 + 'device_name' => $entry['submission_info']['device_name'] ?? '',
885 + // Re-sanitize at the exposure boundary in case the stored value
886 + // was written by a future code path that bypasses form-submit.php.
887 + 'submission_url' => esc_url_raw( $entry['submission_info']['submission_url'] ?? '', [ 'http', 'https' ] ),
857 888 ],
858 889 'user' => $user_info ? [
859 890 'id' => $user_id,
860 891 'display_name' => $user_info->display_name,
@@ -923,14 +954,26 @@
923 954 foreach ( $paginated_logs as $index => $log ) {
924 955 if ( ! is_array( $log ) ) {
925 956 continue;
926 957 }
927 - $formatted_logs[] = [
958 + $formatted_log = [
928 959 'id' => $offset + $index, // Use offset-based ID for consistent deletion.
929 960 'title' => $log['title'] ?? '',
930 961 'timestamp' => $log['timestamp'] ?? time(),
931 962 'messages' => $log['messages'] ?? [],
932 963 ];
964 +
965 + // Pass through (sanitized) retry metadata so an integration/webhook log row can
966 + // offer a "Retry" action for that specific failed trigger. Set by the Pro
967 + // webhook / native-integration dispatchers as [ 'type' => webhook|native, 'id' => <trigger id> ].
968 + if ( isset( $log['retry'] ) && is_array( $log['retry'] ) && ! empty( $log['retry']['type'] ) && isset( $log['retry']['id'] ) ) {
969 + $formatted_log['retry'] = [
970 + 'type' => sanitize_text_field( Helper::get_string_value( $log['retry']['type'] ) ),
971 + 'id' => sanitize_text_field( Helper::get_string_value( $log['retry']['id'] ) ),
972 + ];
973 + }
974 +
975 + $formatted_logs[] = $formatted_log;
933 976 }
934 977
935 978 $response_data = [
936 979 'logs' => $formatted_logs,
@@ -1197,9 +1240,9 @@
1197 1240 $field_name = '';
1198 1241 $base_field_name = '';
1199 1242
1200 1243 if ( ! empty( $label ) && ! empty( $slug ) && ! empty( $block_id ) ) {
1201 - $input_label = '-lbl-' . Helper::encrypt( $label );
1244 + $input_label = '-lbl-' . Helper::encode( $label );
1202 1245 $base_field_name = $input_label . '-' . $slug;
1203 1246
1204 1247 // Handle special case for dropdown with instance counter.
1205 1248 if ( 'dropdown' === $block_type ) {
@@ -1353,9 +1396,9 @@
1353 1396 */
1354 1397 return apply_filters(
1355 1398 'srfm_rest_api_endpoints',
1356 1399 [
1357 - 'generate-form' => [
1400 + 'generate-form' => [
1358 1401 'methods' => 'POST',
1359 1402 'callback' => [ AI_Form_Builder::get_instance(), 'generate_ai_form' ],
1360 1403 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1361 1404 'args' => [
@@ -1364,39 +1407,126 @@
1364 1407 ],
1365 1408 ],
1366 1409 ],
1367 1410 // This route is used to map the AI response to SureForms fields markup.
1368 - 'map-fields' => [
1411 + 'map-fields' => [
1369 1412 'methods' => 'POST',
1370 1413 'callback' => [ Field_Mapping::get_instance(), 'generate_gutenberg_fields_from_questions' ],
1371 1414 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1372 1415 ],
1416 + // Recreate the entries table when it has gone missing. The repair is
1417 + // idempotent (CREATE TABLE IF NOT EXISTS) so a double-click is safe.
1418 + 'database/repair-entries-table' => [
1419 + 'methods' => 'POST',
1420 + /**
1421 + * Resolved at dispatch, not while the route table is built:
1422 + * get_endpoints() runs on rest_api_init for every REST request,
1423 + * and Admin is only constructed under is_admin(). Naming the
1424 + * instance here would run Admin's constructor on the front-end
1425 + * submit path too.
1426 + *
1427 + * @param \WP_REST_Request<array<string,mixed>> $request Request.
1428 + * @return \WP_REST_Response|\WP_Error
1429 + */
1430 + 'callback' => static function ( $request ) {
1431 + $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) );
1432 +
1433 + if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) {
1434 + return new \WP_Error(
1435 + 'rest_cookie_invalid_nonce',
1436 + __( 'Security verification failed. Please refresh the page and try again.', 'sureforms' ),
1437 + [ 'status' => 403 ]
1438 + );
1439 + }
1440 +
1441 + // @phpstan-ignore-next-line -- PHPStan resolves SRFM\Admin\Admin via tests/php/stubs/srfm-stubs.php (admin/ is outside its `paths`) and that generated stub predates this method. Real location: admin/admin.php.
1442 + $repaired = \SRFM\Admin\Admin::get_instance()->do_database_repair();
1443 +
1444 + if ( ! $repaired ) {
1445 + return new \WP_Error(
1446 + 'srfm_database_repair_failed',
1447 + __( 'SureForms could not finish updating the database. Your hosting may not allow SureForms to create database tables — please contact your hosting provider or SureForms support.', 'sureforms' ),
1448 + [ 'status' => 500 ]
1449 + );
1450 + }
1451 +
1452 + return new \WP_REST_Response( [ 'success' => true ], 200 );
1453 + },
1454 + 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1455 + ],
1456 + // Record a "Finish setting up" card CTA click for a form (#3031).
1457 + // Per-form capability is re-checked in the handler.
1458 + 'dismiss-form-setup-card' => [
1459 + 'methods' => 'POST',
1460 + /**
1461 + * Resolve Admin at dispatch rather than while the route table is
1462 + * built. get_endpoints() runs on rest_api_init for *every* REST
1463 + * request, and plugin-loader.php only constructs Admin under
1464 + * is_admin() — which REST dispatch is not. Naming the instance
1465 + * here would therefore run Admin's constructor (40 admin hook
1466 + * registrations, an option read, the notices library, and the
1467 + * wpforms_current_user_can filter) on the front-end
1468 + * submit-form path too.
1469 + *
1470 + * @param \WP_REST_Request<array<string,mixed>> $request Request.
1471 + * @return \WP_REST_Response|\WP_Error
1472 + */
1473 + 'callback' => static function ( $request ) {
1474 + // @phpstan-ignore-next-line -- PHPStan resolves SRFM\Admin\Admin via tests/php/stubs/srfm-stubs.php (admin/ is outside its `paths`) and that generated stub predates this method. Real location: admin/admin.php:470.
1475 + return \SRFM\Admin\Admin::get_instance()->dismiss_form_setup_card( $request );
1476 + },
1477 + 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1478 + 'args' => [
1479 + 'form_id' => [
1480 + 'required' => true,
1481 + 'sanitize_callback' => 'absint',
1482 + ],
1483 + 'action' => [
1484 + 'required' => true,
1485 + 'type' => 'string',
1486 + 'enum' => [ 'edit_form', 'edit_thankyou', 'set_up_email', 'view_form' ],
1487 + // Core only enforces `enum` via the default arg sanitizer, which
1488 + // is skipped once a sanitize_callback is set — so pair it with an
1489 + // explicit validate_callback, matching this file's other routes.
1490 + 'validate_callback' => 'rest_validate_request_arg',
1491 + 'sanitize_callback' => 'sanitize_text_field',
1492 + ],
1493 + ],
1494 + ],
1373 1495 // This route is used to initiate auth process when user tries to authenticate on billing portal.
1374 - 'initiate-auth' => [
1496 + 'initiate-auth' => [
1375 1497 'methods' => 'GET',
1376 1498 'callback' => [ AI_Auth::get_instance(), 'get_auth_url' ],
1377 1499 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1378 1500 ],
1379 1501 // This route is to used to decrypt the access key and save it in the database.
1380 - 'handle-access-key' => [
1502 + 'handle-access-key' => [
1381 1503 'methods' => 'POST',
1382 1504 'callback' => [ AI_Auth::get_instance(), 'handle_access_key' ],
1383 1505 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1384 1506 ],
1507 + // Public route: returns the visitor's detected country code. Called
1508 + // per-visitor from the phone field so auto-country detection works
1509 + // on full-page-cached sites (the value isn't baked into cached HTML).
1510 + 'geo-country' => [
1511 + 'methods' => 'GET',
1512 + 'callback' => [ $this, 'get_geo_country' ],
1513 + 'permission_callback' => '__return_true',
1514 + ],
1385 1515 // This route is to get the form submissions for the last 30 days.
1386 - 'entries-chart-data' => [
1516 + 'entries-chart-data' => [
1387 1517 'methods' => 'GET',
1388 1518 'callback' => [ $this, 'get_entries_chart_data' ],
1389 1519 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1390 1520 ],
1391 1521 // This route is to get all forms data.
1392 - 'form-data' => [
1522 + 'form-data' => [
1393 1523 'methods' => 'GET',
1394 1524 'callback' => [ $this, 'get_form_data' ],
1395 1525 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1396 1526 ],
1397 1527 // Page search endpoint for async admin dropdowns.
1398 - 'pages/search' => [
1528 + 'pages/search' => [
1399 1529 'methods' => 'GET',
1400 1530 'callback' => [ $this, 'search_pages' ],
1401 1531 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1402 1532 'args' => [
@@ -1446,19 +1576,19 @@
1446 1576 ],
1447 1577 ],
1448 1578 ],
1449 1579 // Onboarding endpoints.
1450 - 'onboarding/set-status' => [
1580 + 'onboarding/set-status' => [
1451 1581 'methods' => 'POST',
1452 1582 'callback' => [ $this, 'set_onboarding_status' ],
1453 1583 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1454 1584 ],
1455 - 'onboarding/get-status' => [
1585 + 'onboarding/get-status' => [
1456 1586 'methods' => 'GET',
1457 1587 'callback' => [ $this, 'get_onboarding_status' ],
1458 1588 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1459 1589 ],
1460 - 'onboarding/user-details' => [
1590 + 'onboarding/user-details' => [
1461 1591 'methods' => 'POST',
1462 1592 'callback' => [ $this, 'save_onboarding_user_details' ],
1463 1593 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1464 1594 'args' => [
@@ -1482,9 +1612,9 @@
1482 1612 ],
1483 1613 ],
1484 1614 ],
1485 1615 // Plugin status endpoint.
1486 - 'plugin-status' => [
1616 + 'plugin-status' => [
1487 1617 'methods' => 'GET',
1488 1618 'callback' => [ $this, 'get_plugin_status' ],
1489 1619 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1490 1620 'args' => [
@@ -1494,9 +1624,9 @@
1494 1624 ],
1495 1625 ],
1496 1626 ],
1497 1627 // Entries endpoints.
1498 - 'entries/list' => [
1628 + 'entries/list' => [
1499 1629 'methods' => 'GET',
1500 1630 'callback' => [ $this, 'get_entries_list' ],
1501 1631 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1502 1632 'args' => [
@@ -1541,9 +1671,9 @@
1541 1671 'default' => 1,
1542 1672 ],
1543 1673 ],
1544 1674 ],
1545 - 'entries/read-status' => [
1675 + 'entries/read-status' => [
1546 1676 'methods' => 'POST',
1547 1677 'callback' => [ $this, 'update_entries_read_status' ],
1548 1678 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1549 1679 'args' => [
@@ -1557,9 +1687,9 @@
1557 1687 'validate_callback' => [ $this, 'validate_read_action' ],
1558 1688 ],
1559 1689 ],
1560 1690 ],
1561 - 'entries/trash' => [
1691 + 'entries/trash' => [
1562 1692 'methods' => 'POST',
1563 1693 'callback' => [ $this, 'update_entries_trash_status' ],
1564 1694 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1565 1695 'args' => [
@@ -1573,9 +1703,9 @@
1573 1703 'validate_callback' => [ $this, 'validate_trash_action' ],
1574 1704 ],
1575 1705 ],
1576 1706 ],
1577 - 'entries/delete' => [
1707 + 'entries/delete' => [
1578 1708 'methods' => 'POST',
1579 1709 'callback' => [ $this, 'delete_entries' ],
1580 1710 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1581 1711 'args' => [
@@ -1584,9 +1714,9 @@
1584 1714 'sanitize_callback' => [ $this, 'sanitize_entry_ids' ],
1585 1715 ],
1586 1716 ],
1587 1717 ],
1588 - 'entries/export' => [
1718 + 'entries/export' => [
1589 1719 'methods' => 'POST',
1590 1720 'callback' => [ $this, 'export_entries' ],
1591 1721 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1592 1722 'args' => [
@@ -1616,9 +1746,9 @@
1616 1746 ],
1617 1747 ],
1618 1748 ],
1619 1749 // Get Single Entry Form Data.
1620 - 'entry/(?P<id>\d+)/details' => [
1750 + 'entry/(?P<id>\d+)/details' => [
1621 1751 'methods' => 'GET',
1622 1752 'callback' => [ $this, 'get_entry_details' ],
1623 1753 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1624 1754 'args' => [
@@ -1628,9 +1758,9 @@
1628 1758 ],
1629 1759 ],
1630 1760 ],
1631 1761 // Get Single Entry Logs.
1632 - 'entry/(?P<id>\d+)/logs' => [
1762 + 'entry/(?P<id>\d+)/logs' => [
1633 1763 'methods' => 'GET',
1634 1764 'callback' => [ $this, 'get_entry_logs' ],
1635 1765 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1636 1766 'args' => [
@@ -1648,9 +1778,9 @@
1648 1778 ],
1649 1779 ],
1650 1780 ],
1651 1781 // Forms listing endpoint.
1652 - 'forms' => [
1782 + 'forms' => [
1653 1783 'methods' => 'GET',
1654 1784 'callback' => [ Forms_Data::get_instance(), 'get_forms_list' ],
1655 1785 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1656 1786 'args' => [
@@ -1674,9 +1804,9 @@
1674 1804 ],
1675 1805 'orderby' => [
1676 1806 'type' => 'string',
1677 1807 'default' => 'date',
1678 - 'enum' => [ 'date', 'id', 'title', 'modified' ],
1808 + 'enum' => [ 'date', 'id', 'title', 'modified', 'views', 'conversion_rate' ],
1679 1809 ],
1680 1810 'order' => [
1681 1811 'type' => 'string',
1682 1812 'default' => 'desc',
@@ -1706,9 +1836,9 @@
1706 1836 ],
1707 1837 ],
1708 1838 ],
1709 1839 // Export forms endpoint.
1710 - 'forms/export' => [
1840 + 'forms/export' => [
1711 1841 'methods' => 'POST',
1712 1842 'callback' => [ Export::get_instance(), 'handle_export_form_rest' ],
1713 1843 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1714 1844 'args' => [
@@ -1730,9 +1860,9 @@
1730 1860 ],
1731 1861 ],
1732 1862 ],
1733 1863 // Import forms endpoint.
1734 - 'forms/import' => [
1864 + 'forms/import' => [
1735 1865 'methods' => 'POST',
1736 1866 'callback' => [ Export::get_instance(), 'handle_import_form_rest' ],
1737 1867 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1738 1868 'args' => [
@@ -1752,9 +1882,9 @@
1752 1882 ],
1753 1883 ],
1754 1884 ],
1755 1885 // Form lifecycle management endpoint (trash/restore/delete/draft).
1756 - 'forms/manage' => [
1886 + 'forms/manage' => [
1757 1887 'methods' => 'POST',
1758 1888 'callback' => [ $this, 'manage_form_lifecycle' ],
1759 1889 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1760 1890 'args' => [
@@ -1782,9 +1912,9 @@
1782 1912 ],
1783 1913 ],
1784 1914 ],
1785 1915 // Form duplication endpoint.
1786 - 'forms/duplicate' => [
1916 + 'forms/duplicate' => [
1787 1917 'methods' => 'POST',
1788 1918 'callback' => [ Duplicate_Form::get_instance(), 'handle_duplicate_form_rest' ],
1789 1919 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ],
1790 1920 'args' => [