| @@ -6,8 +6,13 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | namespace CartflowsAdmin\AdminCore\Api; |
| 9 | 9 | |
| 10 | +use WP_REST_Controller; | |
| 11 | +use WP_REST_Request; | |
| 12 | +use WP_REST_Server; | |
| 13 | +use WP_Error; | |
| 14 | + | |
| 10 | 15 | // Exit if accessed directly. |
| 11 | 16 | if ( ! defined( 'ABSPATH' ) ) { |
| 12 | 17 | exit; |
| 13 | 18 | } |
| @@ -14,9 +19,9 @@ | ||
| 14 | 19 | |
| 15 | 20 | /** |
| 16 | 21 | * Class Admin_Menu. |
| 17 | 22 | */ |
| 18 | -abstract class ApiBase extends \WP_REST_Controller { | |
| 23 | +abstract class ApiBase extends WP_REST_Controller { | |
| 19 | 24 | |
| 20 | 25 | /** |
| 21 | 26 | * Endpoint namespace. |
| 22 | 27 | * |
| @@ -37,6 +42,98 @@ | ||
| 37 | 42 | */ |
| 38 | 43 | public function get_api_namespace() { |
| 39 | 44 | |
| 40 | 45 | return $this->namespace; |
| 46 | + } | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * Validate the nonce for REST API requests, then apply the | |
| 50 | + * capability + Pro filter chain via check_permission_for_action(). | |
| 51 | + * | |
| 52 | + * @param WP_REST_Request<array<string, mixed>> $request The REST request object. | |
| 53 | + * @return bool|WP_Error True if valid, WP_Error if invalid. | |
| 54 | + */ | |
| 55 | + public function validate_permission( $request ) { | |
| 56 | + // Retrieve the nonce from the request header. | |
| 57 | + $nonce = $request->get_header( 'X-WP-Nonce' ); | |
| 58 | + | |
| 59 | + // Check if nonce is null or empty. | |
| 60 | + if ( empty( $nonce ) || ! is_string( $nonce ) ) { | |
| 61 | + return new WP_Error( | |
| 62 | + 'cartflows_nonce_verification_failed', | |
| 63 | + __( 'Nonce is missing.', 'cartflows' ), | |
| 64 | + array( 'status' => rest_authorization_required_code() ) | |
| 65 | + ); | |
| 66 | + } | |
| 67 | + | |
| 68 | + // Verify the nonce. | |
| 69 | + if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { | |
| 70 | + return new WP_Error( | |
| 71 | + 'cartflows_nonce_verification_failed', | |
| 72 | + __( 'Nonce is invalid.', 'cartflows' ), | |
| 73 | + array( 'status' => rest_authorization_required_code() ) | |
| 74 | + ); | |
| 75 | + } | |
| 76 | + | |
| 77 | + return self::check_permission_for_action( $request ); | |
| 78 | + } | |
| 79 | + | |
| 80 | + /** | |
| 81 | + * Extracted from validate_permission() so the AJAX fallback in | |
| 82 | + * inc/ajax/save-endpoints.php can enforce the exact same policy | |
| 83 | + * that Pro plugins layer on top of the REST endpoints via the | |
| 84 | + * `cartflows_rest_api_permission` and | |
| 85 | + * `cartflows_rest_api_permission_check` filters. AJAX must not be | |
| 86 | + * a back door around a Pro licensing or role policy that blocks | |
| 87 | + * the REST endpoint. | |
| 88 | + * | |
| 89 | + * Callers are responsible for verifying request authenticity | |
| 90 | + * (X-WP-Nonce for REST, `_wpnonce` via check_ajax_referer() for | |
| 91 | + * AJAX) before invoking this helper. | |
| 92 | + * | |
| 93 | + * @param WP_REST_Request<array<string, mixed>> $request The REST request object (or a synthetic request built by the AJAX handler mirroring the equivalent REST route). | |
| 94 | + * @return bool|WP_Error True on pass, WP_Error on denial. | |
| 95 | + * @since x.x.x | |
| 96 | + */ | |
| 97 | + public static function check_permission_for_action( $request ) { | |
| 98 | + /** | |
| 99 | + * Filter to allow Pro plugin or extensions to override permission checks. | |
| 100 | + * | |
| 101 | + * @since x.x.x | |
| 102 | + * @param bool|WP_Error $has_permission True to allow access, WP_Error to deny with custom message, false to use default check. | |
| 103 | + * @param WP_REST_Request $request The REST request object. | |
| 104 | + */ | |
| 105 | + $has_permission = apply_filters( 'cartflows_rest_api_permission', false, $request ); | |
| 106 | + | |
| 107 | + if ( is_wp_error( $has_permission ) ) { | |
| 108 | + return $has_permission; | |
| 109 | + } | |
| 110 | + | |
| 111 | + // If filter didn't handle permission (Pro not active), fall back to admin capability check. | |
| 112 | + if ( true !== $has_permission ) { | |
| 113 | + if ( ! current_user_can( 'manage_options' ) ) { | |
| 114 | + return new WP_Error( | |
| 115 | + 'cartflows_rest_cannot_access', | |
| 116 | + __( 'You do not have permission to perform this action.', 'cartflows' ), | |
| 117 | + array( 'status' => rest_authorization_required_code() ) | |
| 118 | + ); | |
| 119 | + } | |
| 120 | + } | |
| 121 | + | |
| 122 | + /** | |
| 123 | + * Filter to allow additional permission checks (e.g., license validation). | |
| 124 | + * | |
| 125 | + * This runs AFTER the user capability check has passed. | |
| 126 | + * | |
| 127 | + * @since x.x.x | |
| 128 | + * @param bool|WP_Error $permission_status True if permission granted, WP_Error to deny. | |
| 129 | + * @param WP_REST_Request $request The REST request object. | |
| 130 | + */ | |
| 131 | + $permission_status = apply_filters( 'cartflows_rest_api_permission_check', true, $request ); | |
| 132 | + | |
| 133 | + if ( is_wp_error( $permission_status ) ) { | |
| 134 | + return $permission_status; | |
| 135 | + } | |
| 136 | + | |
| 137 | + return true; | |
| 41 | 138 | } |
| 42 | 139 | } |