PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / classes / frontend-request-handler.php

frontend-request-handler.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/classes/frontend-request-handler.php

201 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\Classes;
4
5 use StoreEngine\Classes\Exceptions\StoreEngineException;
6 use StoreEngine\Classes\OrderStatus\OrderStatus;
7 use StoreEngine\Traits\Singleton;
8 use StoreEngine\Utils\Caching;
9 use StoreEngine\Utils\Helper;
10 use WP_Error;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 class FrontendRequestHandler extends AbstractRequestHandler {
17 use Singleton;
18
19 /**
20 * Default Nonce Action.
21 *
22 * @var string
23 */
24 protected string $nonce_action = 'storeengine_nonce';
25
26 public function __construct() {
27 $this->dispatch_actions();
28 }
29
30 final public function dispatch_actions() {
31 add_action( 'wp', [ $this, 'handle_request' ], 20 );
32 }
33
34 /**
35 * Handle action callback.
36 *
37 * @return void
38 */
39 public function handle_request() {
40 global $wp;
41
42 $this->actions = array_filter( Helper::get_frontend_dashboard_menu_items(), fn( $item ) => ! $item['public'] );
43
44 if ( ! empty( $wp->query_vars['storeengine_dashboard_page'] ) && array_key_exists( $wp->query_vars['storeengine_dashboard_page'], $this->actions ) ) {
45 $type = sanitize_text_field( $wp->query_vars['storeengine_dashboard_page'] );
46 $value = sanitize_text_field( $wp->query_vars['storeengine_dashboard_sub_page'] ?? '' );
47 $details = $this->actions[ $type ];
48
49 // Prevent caching.
50 Caching::nocache_headers();
51
52 try {
53 $this->respond_request( $type, $value, $details );
54 } catch ( StoreEngineException $e ) {
55 $title = _x( 'The', 'error title', 'storeengine' );
56 $error = $e->toWpError();
57 if ( ! array_key_exists( 'title', $error->get_error_data() ) ) {
58 $title = $this->actions[ $type ]['label'] ?? str_replace( '_', ' ', $type );
59 }
60
61 wp_die(
62 $error, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP_Error object.
63 sprintf(
64 /* translators: %s. Error page title. */
65 esc_html__( 'Error Processing: %s Request', 'storeengine' ),
66 esc_html( ucwords( $title ) )
67 )
68 );
69 }
70 }
71
72 $this->handle_cancel_order();
73 }
74
75 /**
76 * @param string $type
77 * @param string $value
78 * @param array $details
79 *
80 * @return void
81 * @throws StoreEngineException
82 */
83 protected function respond_request( string $type, string $value, array $details ) {
84 if ( has_action( "storeengine_dashboard_handle_{$type}_request" ) ) {
85 $this->validate_request( $type, $value, $details );
86 do_action( "storeengine_dashboard_handle_{$type}_request", $value ?: null );
87 } else {
88 if ( ! empty( $details['callback'] ) && is_callable( $details['callback'] ) ) {
89 $this->validate_request( $type, $value, $details );
90 if ( ! empty( $details['fields'] ) ) {
91 $this->respond( $details['callback'], $this->prepare_payload( $details['fields'] ) );
92 } else {
93 call_user_func( $details['callback'], $value ?: null );
94 }
95 }
96 }
97 }
98
99 /**
100 * @param string $type
101 * @param string $value
102 * @param array $details
103 *
104 * @return void
105 * @throws StoreEngineException
106 */
107 protected function validate_request( string $type, string $value, array $details ) {
108 $nonce = isset( $_REQUEST['security'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['security'] ) ) : '';
109
110 if ( empty( $nonce ) && isset( $_REQUEST['_wpnonce'] ) ) {
111 $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
112 }
113
114 if ( ! $nonce ) {
115 throw new StoreEngineException(
116 esc_html__( 'Missing security token data.', 'storeengine' ),
117 'missing_nonce_field',
118 [
119 'status' => rest_authorization_required_code(), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
120 'title' => esc_html__( 'Security token is missing.', 'storeengine' ),
121 ]
122 );
123 }
124
125 if ( ! wp_verify_nonce( $nonce, $this->nonce_action ) && ! wp_verify_nonce( $nonce, $type . '-' . $value ) ) {
126 throw new StoreEngineException(
127 esc_html__( 'Invalid Security token data.', 'storeengine' ),
128 'invalid_nonce',
129 [
130 'status' => rest_authorization_required_code(), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
131 'title' => esc_html__( 'Invalid Security token.', 'storeengine' ),
132 ]
133 );
134 }
135
136 if ( ! isset( $details['allow_visitor_action'] ) ) {
137 $this->is_visitor_action = true;
138 } else {
139 $this->is_visitor_action = (bool) $details['allow_visitor_action'];
140 }
141 $user_cap = ! empty( $details['capability'] ) ? (string) $details['capability'] : '';
142 $has_permission = $this->check_permission( $user_cap, $this->is_visitor_action );
143
144 if ( is_wp_error( $has_permission ) ) {
145 throw StoreEngineException::from_wp_error( $has_permission ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
146 }
147 }
148
149 protected function handle_cancel_order() {
150 if (
151 isset( $_GET['cancel_order'], $_GET['order'], $_GET['order_id'], $_GET['_wpnonce'] ) &&
152 wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'storeengine-cancel_order' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
153 && is_user_logged_in()
154 ) {
155 // Prevent caching.
156 Caching::nocache_headers();
157
158 $order_key = wp_unslash( $_GET['order'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
159 $order_id = absint( $_GET['order_id'] );
160 $order = Helper::get_order( $order_id );
161
162 /**
163 * Filter valid order statuses for cancel.
164 *
165 * @param array $valid_statuses Array of valid order statuses for cancel.
166 * @param Order $order Order object.
167 */
168 $valid_statuses = apply_filters( 'storeengine/order/valid_statuses_for_cancel', [ OrderStatus::PAYMENT_PENDING, OrderStatus::PAYMENT_FAILED ], $order );
169 $user_can_cancel = get_current_user_id() === $order->get_user_id();
170 $order_can_cancel = $order->has_status( $valid_statuses );
171 $redirect = isset( $_GET['redirect'] ) ? wp_unslash( $_GET['redirect'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
172
173 if ( $user_can_cancel && $order_can_cancel && $order->get_id() === $order_id && hash_equals( $order->get_order_key(), $order_key ) ) {
174 // Cancel the order + restore stock.
175 $order->update_status( OrderStatus::CANCELLED, __( 'Order cancelled by customer.', 'storeengine' ) );
176
177 // info -> Your order was cancelled.
178
179 do_action( 'storeengine/order/order_cancelled', $order->get_id() );
180 } elseif ( $user_can_cancel && ! $order_can_cancel ) {
181 wp_die(
182 esc_html__( 'Your order can no longer be cancelled. Please contact us if needed.', 'storeengine' ),
183 esc_html__( 'Invalid action.', 'storeengine' )
184 );
185 } else {
186 wp_die(
187 esc_html__( 'Invalid order.', 'storeengine' ),
188 esc_html__( 'Invalid order.', 'storeengine' )
189 );
190 }
191
192 if ( $redirect ) {
193 wp_safe_redirect( $redirect );
194 exit;
195 }
196 }
197 }
198 }
199
200 // End of file frontend-request-handler.php.
201