| @@ -814,9 +814,9 @@ | ||
| 814 | 814 | } |
| 815 | 815 | |
| 816 | 816 | $label_parts = explode( '-lbl-', $field_name ); |
| 817 | 817 | $label = isset( $label_parts[1] ) ? explode( '-', $label_parts[1] )[0] : ''; |
| 818 | - $label = $label ? Helper::decrypt( $label ) : ''; | |
| 818 | + $label = $label ? Helper::decode( $label ) : ''; | |
| 819 | 819 | $field_block_name = Helper::get_block_name_from_field( $field_name ); |
| 820 | 820 | |
| 821 | 821 | /** |
| 822 | 822 | * Filter: 'srfm_entry_value' |
| @@ -822,9 +822,11 @@ | ||
| 822 | 822 | * Filter: 'srfm_entry_value' |
| 823 | 823 | * |
| 824 | 824 | * This filter is used to allow 3rd party plugins or custom code to modify |
| 825 | 825 | * the entry field value in the entry details REST API response, if required. |
| 826 | - * 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. | |
| 827 | 829 | * |
| 828 | 830 | * @since 2.0.0 |
| 829 | 831 | * |
| 830 | 832 | * @param mixed $value The original value for the field. |
| @@ -952,14 +954,26 @@ | ||
| 952 | 954 | foreach ( $paginated_logs as $index => $log ) { |
| 953 | 955 | if ( ! is_array( $log ) ) { |
| 954 | 956 | continue; |
| 955 | 957 | } |
| 956 | - $formatted_logs[] = [ | |
| 958 | + $formatted_log = [ | |
| 957 | 959 | 'id' => $offset + $index, // Use offset-based ID for consistent deletion. |
| 958 | 960 | 'title' => $log['title'] ?? '', |
| 959 | 961 | 'timestamp' => $log['timestamp'] ?? time(), |
| 960 | 962 | 'messages' => $log['messages'] ?? [], |
| 961 | 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; | |
| 962 | 976 | } |
| 963 | 977 | |
| 964 | 978 | $response_data = [ |
| 965 | 979 | 'logs' => $formatted_logs, |
| @@ -1226,9 +1240,9 @@ | ||
| 1226 | 1240 | $field_name = ''; |
| 1227 | 1241 | $base_field_name = ''; |
| 1228 | 1242 | |
| 1229 | 1243 | if ( ! empty( $label ) && ! empty( $slug ) && ! empty( $block_id ) ) { |
| 1230 | - $input_label = '-lbl-' . Helper::encrypt( $label ); | |
| 1244 | + $input_label = '-lbl-' . Helper::encode( $label ); | |
| 1231 | 1245 | $base_field_name = $input_label . '-' . $slug; |
| 1232 | 1246 | |
| 1233 | 1247 | // Handle special case for dropdown with instance counter. |
| 1234 | 1248 | if ( 'dropdown' === $block_type ) { |
| @@ -1382,9 +1396,9 @@ | ||
| 1382 | 1396 | */ |
| 1383 | 1397 | return apply_filters( |
| 1384 | 1398 | 'srfm_rest_api_endpoints', |
| 1385 | 1399 | [ |
| 1386 | - 'generate-form' => [ | |
| 1400 | + 'generate-form' => [ | |
| 1387 | 1401 | 'methods' => 'POST', |
| 1388 | 1402 | 'callback' => [ AI_Form_Builder::get_instance(), 'generate_ai_form' ], |
| 1389 | 1403 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1390 | 1404 | 'args' => [ |
| @@ -1393,21 +1407,100 @@ | ||
| 1393 | 1407 | ], |
| 1394 | 1408 | ], |
| 1395 | 1409 | ], |
| 1396 | 1410 | // This route is used to map the AI response to SureForms fields markup. |
| 1397 | - 'map-fields' => [ | |
| 1411 | + 'map-fields' => [ | |
| 1398 | 1412 | 'methods' => 'POST', |
| 1399 | 1413 | 'callback' => [ Field_Mapping::get_instance(), 'generate_gutenberg_fields_from_questions' ], |
| 1400 | 1414 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1401 | 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 | + ], | |
| 1402 | 1495 | // This route is used to initiate auth process when user tries to authenticate on billing portal. |
| 1403 | - 'initiate-auth' => [ | |
| 1496 | + 'initiate-auth' => [ | |
| 1404 | 1497 | 'methods' => 'GET', |
| 1405 | 1498 | 'callback' => [ AI_Auth::get_instance(), 'get_auth_url' ], |
| 1406 | 1499 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1407 | 1500 | ], |
| 1408 | 1501 | // This route is to used to decrypt the access key and save it in the database. |
| 1409 | - 'handle-access-key' => [ | |
| 1502 | + 'handle-access-key' => [ | |
| 1410 | 1503 | 'methods' => 'POST', |
| 1411 | 1504 | 'callback' => [ AI_Auth::get_instance(), 'handle_access_key' ], |
| 1412 | 1505 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1413 | 1506 | ], |
| @@ -1413,27 +1506,27 @@ | ||
| 1413 | 1506 | ], |
| 1414 | 1507 | // Public route: returns the visitor's detected country code. Called |
| 1415 | 1508 | // per-visitor from the phone field so auto-country detection works |
| 1416 | 1509 | // on full-page-cached sites (the value isn't baked into cached HTML). |
| 1417 | - 'geo-country' => [ | |
| 1510 | + 'geo-country' => [ | |
| 1418 | 1511 | 'methods' => 'GET', |
| 1419 | 1512 | 'callback' => [ $this, 'get_geo_country' ], |
| 1420 | 1513 | 'permission_callback' => '__return_true', |
| 1421 | 1514 | ], |
| 1422 | 1515 | // This route is to get the form submissions for the last 30 days. |
| 1423 | - 'entries-chart-data' => [ | |
| 1516 | + 'entries-chart-data' => [ | |
| 1424 | 1517 | 'methods' => 'GET', |
| 1425 | 1518 | 'callback' => [ $this, 'get_entries_chart_data' ], |
| 1426 | 1519 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1427 | 1520 | ], |
| 1428 | 1521 | // This route is to get all forms data. |
| 1429 | - 'form-data' => [ | |
| 1522 | + 'form-data' => [ | |
| 1430 | 1523 | 'methods' => 'GET', |
| 1431 | 1524 | 'callback' => [ $this, 'get_form_data' ], |
| 1432 | 1525 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1433 | 1526 | ], |
| 1434 | 1527 | // Page search endpoint for async admin dropdowns. |
| 1435 | - 'pages/search' => [ | |
| 1528 | + 'pages/search' => [ | |
| 1436 | 1529 | 'methods' => 'GET', |
| 1437 | 1530 | 'callback' => [ $this, 'search_pages' ], |
| 1438 | 1531 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1439 | 1532 | 'args' => [ |
| @@ -1483,19 +1576,19 @@ | ||
| 1483 | 1576 | ], |
| 1484 | 1577 | ], |
| 1485 | 1578 | ], |
| 1486 | 1579 | // Onboarding endpoints. |
| 1487 | - 'onboarding/set-status' => [ | |
| 1580 | + 'onboarding/set-status' => [ | |
| 1488 | 1581 | 'methods' => 'POST', |
| 1489 | 1582 | 'callback' => [ $this, 'set_onboarding_status' ], |
| 1490 | 1583 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1491 | 1584 | ], |
| 1492 | - 'onboarding/get-status' => [ | |
| 1585 | + 'onboarding/get-status' => [ | |
| 1493 | 1586 | 'methods' => 'GET', |
| 1494 | 1587 | 'callback' => [ $this, 'get_onboarding_status' ], |
| 1495 | 1588 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1496 | 1589 | ], |
| 1497 | - 'onboarding/user-details' => [ | |
| 1590 | + 'onboarding/user-details' => [ | |
| 1498 | 1591 | 'methods' => 'POST', |
| 1499 | 1592 | 'callback' => [ $this, 'save_onboarding_user_details' ], |
| 1500 | 1593 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1501 | 1594 | 'args' => [ |
| @@ -1519,9 +1612,9 @@ | ||
| 1519 | 1612 | ], |
| 1520 | 1613 | ], |
| 1521 | 1614 | ], |
| 1522 | 1615 | // Plugin status endpoint. |
| 1523 | - 'plugin-status' => [ | |
| 1616 | + 'plugin-status' => [ | |
| 1524 | 1617 | 'methods' => 'GET', |
| 1525 | 1618 | 'callback' => [ $this, 'get_plugin_status' ], |
| 1526 | 1619 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1527 | 1620 | 'args' => [ |
| @@ -1531,9 +1624,9 @@ | ||
| 1531 | 1624 | ], |
| 1532 | 1625 | ], |
| 1533 | 1626 | ], |
| 1534 | 1627 | // Entries endpoints. |
| 1535 | - 'entries/list' => [ | |
| 1628 | + 'entries/list' => [ | |
| 1536 | 1629 | 'methods' => 'GET', |
| 1537 | 1630 | 'callback' => [ $this, 'get_entries_list' ], |
| 1538 | 1631 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1539 | 1632 | 'args' => [ |
| @@ -1578,9 +1671,9 @@ | ||
| 1578 | 1671 | 'default' => 1, |
| 1579 | 1672 | ], |
| 1580 | 1673 | ], |
| 1581 | 1674 | ], |
| 1582 | - 'entries/read-status' => [ | |
| 1675 | + 'entries/read-status' => [ | |
| 1583 | 1676 | 'methods' => 'POST', |
| 1584 | 1677 | 'callback' => [ $this, 'update_entries_read_status' ], |
| 1585 | 1678 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1586 | 1679 | 'args' => [ |
| @@ -1594,9 +1687,9 @@ | ||
| 1594 | 1687 | 'validate_callback' => [ $this, 'validate_read_action' ], |
| 1595 | 1688 | ], |
| 1596 | 1689 | ], |
| 1597 | 1690 | ], |
| 1598 | - 'entries/trash' => [ | |
| 1691 | + 'entries/trash' => [ | |
| 1599 | 1692 | 'methods' => 'POST', |
| 1600 | 1693 | 'callback' => [ $this, 'update_entries_trash_status' ], |
| 1601 | 1694 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1602 | 1695 | 'args' => [ |
| @@ -1610,9 +1703,9 @@ | ||
| 1610 | 1703 | 'validate_callback' => [ $this, 'validate_trash_action' ], |
| 1611 | 1704 | ], |
| 1612 | 1705 | ], |
| 1613 | 1706 | ], |
| 1614 | - 'entries/delete' => [ | |
| 1707 | + 'entries/delete' => [ | |
| 1615 | 1708 | 'methods' => 'POST', |
| 1616 | 1709 | 'callback' => [ $this, 'delete_entries' ], |
| 1617 | 1710 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1618 | 1711 | 'args' => [ |
| @@ -1621,9 +1714,9 @@ | ||
| 1621 | 1714 | 'sanitize_callback' => [ $this, 'sanitize_entry_ids' ], |
| 1622 | 1715 | ], |
| 1623 | 1716 | ], |
| 1624 | 1717 | ], |
| 1625 | - 'entries/export' => [ | |
| 1718 | + 'entries/export' => [ | |
| 1626 | 1719 | 'methods' => 'POST', |
| 1627 | 1720 | 'callback' => [ $this, 'export_entries' ], |
| 1628 | 1721 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1629 | 1722 | 'args' => [ |
| @@ -1653,9 +1746,9 @@ | ||
| 1653 | 1746 | ], |
| 1654 | 1747 | ], |
| 1655 | 1748 | ], |
| 1656 | 1749 | // Get Single Entry Form Data. |
| 1657 | - 'entry/(?P<id>\d+)/details' => [ | |
| 1750 | + 'entry/(?P<id>\d+)/details' => [ | |
| 1658 | 1751 | 'methods' => 'GET', |
| 1659 | 1752 | 'callback' => [ $this, 'get_entry_details' ], |
| 1660 | 1753 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1661 | 1754 | 'args' => [ |
| @@ -1665,9 +1758,9 @@ | ||
| 1665 | 1758 | ], |
| 1666 | 1759 | ], |
| 1667 | 1760 | ], |
| 1668 | 1761 | // Get Single Entry Logs. |
| 1669 | - 'entry/(?P<id>\d+)/logs' => [ | |
| 1762 | + 'entry/(?P<id>\d+)/logs' => [ | |
| 1670 | 1763 | 'methods' => 'GET', |
| 1671 | 1764 | 'callback' => [ $this, 'get_entry_logs' ], |
| 1672 | 1765 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1673 | 1766 | 'args' => [ |
| @@ -1685,9 +1778,9 @@ | ||
| 1685 | 1778 | ], |
| 1686 | 1779 | ], |
| 1687 | 1780 | ], |
| 1688 | 1781 | // Forms listing endpoint. |
| 1689 | - 'forms' => [ | |
| 1782 | + 'forms' => [ | |
| 1690 | 1783 | 'methods' => 'GET', |
| 1691 | 1784 | 'callback' => [ Forms_Data::get_instance(), 'get_forms_list' ], |
| 1692 | 1785 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1693 | 1786 | 'args' => [ |
| @@ -1711,9 +1804,9 @@ | ||
| 1711 | 1804 | ], |
| 1712 | 1805 | 'orderby' => [ |
| 1713 | 1806 | 'type' => 'string', |
| 1714 | 1807 | 'default' => 'date', |
| 1715 | - 'enum' => [ 'date', 'id', 'title', 'modified' ], | |
| 1808 | + 'enum' => [ 'date', 'id', 'title', 'modified', 'views', 'conversion_rate' ], | |
| 1716 | 1809 | ], |
| 1717 | 1810 | 'order' => [ |
| 1718 | 1811 | 'type' => 'string', |
| 1719 | 1812 | 'default' => 'desc', |
| @@ -1743,9 +1836,9 @@ | ||
| 1743 | 1836 | ], |
| 1744 | 1837 | ], |
| 1745 | 1838 | ], |
| 1746 | 1839 | // Export forms endpoint. |
| 1747 | - 'forms/export' => [ | |
| 1840 | + 'forms/export' => [ | |
| 1748 | 1841 | 'methods' => 'POST', |
| 1749 | 1842 | 'callback' => [ Export::get_instance(), 'handle_export_form_rest' ], |
| 1750 | 1843 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1751 | 1844 | 'args' => [ |
| @@ -1767,9 +1860,9 @@ | ||
| 1767 | 1860 | ], |
| 1768 | 1861 | ], |
| 1769 | 1862 | ], |
| 1770 | 1863 | // Import forms endpoint. |
| 1771 | - 'forms/import' => [ | |
| 1864 | + 'forms/import' => [ | |
| 1772 | 1865 | 'methods' => 'POST', |
| 1773 | 1866 | 'callback' => [ Export::get_instance(), 'handle_import_form_rest' ], |
| 1774 | 1867 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1775 | 1868 | 'args' => [ |
| @@ -1789,9 +1882,9 @@ | ||
| 1789 | 1882 | ], |
| 1790 | 1883 | ], |
| 1791 | 1884 | ], |
| 1792 | 1885 | // Form lifecycle management endpoint (trash/restore/delete/draft). |
| 1793 | - 'forms/manage' => [ | |
| 1886 | + 'forms/manage' => [ | |
| 1794 | 1887 | 'methods' => 'POST', |
| 1795 | 1888 | 'callback' => [ $this, 'manage_form_lifecycle' ], |
| 1796 | 1889 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1797 | 1890 | 'args' => [ |
| @@ -1819,9 +1912,9 @@ | ||
| 1819 | 1912 | ], |
| 1820 | 1913 | ], |
| 1821 | 1914 | ], |
| 1822 | 1915 | // Form duplication endpoint. |
| 1823 | - 'forms/duplicate' => [ | |
| 1916 | + 'forms/duplicate' => [ | |
| 1824 | 1917 | 'methods' => 'POST', |
| 1825 | 1918 | 'callback' => [ Duplicate_Form::get_instance(), 'handle_duplicate_form_rest' ], |
| 1826 | 1919 | 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], |
| 1827 | 1920 | 'args' => [ |