PluginProbe
MailPoet – Newsletters, Email Marketing, and Automation / 3.46.8
MailPoet – Newsletters, Email Marketing, and Automation v3.46.8
5.38.0 5.37.0 5.36.1 5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 All 542 releases
mailpoet / lib / API / JSON / API.php

API.php in MailPoet – Newsletters, Email Marketing, and Automation 3.46.8, at lib/API/JSON/API.php

259 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace MailPoet\API\JSON;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\API\JSON\Endpoint;
9 use MailPoet\Config\AccessControl;
10 use MailPoet\Settings\SettingsController;
11 use MailPoet\Subscription\Captcha;
12 use MailPoet\Tracy\ApiPanel\ApiPanel;
13 use MailPoet\Tracy\DIPanel\DIPanel;
14 use MailPoet\Util\Helpers;
15 use MailPoet\Util\Security;
16 use MailPoet\WP\Functions as WPFunctions;
17 use MailPoetVendor\Psr\Container\ContainerInterface;
18 use Tracy\Debugger;
19 use Tracy\ILogger;
20
21 class API {
22 private $requestApiVersion;
23 private $requestEndpoint;
24 private $requestMethod;
25 private $requestToken;
26 private $requestType;
27 private $requestEndpointClass;
28 private $requestData = [];
29 private $endpointNamespaces = [];
30 private $availableApiVersions = [
31 'v1',
32 ];
33 /** @var ContainerInterface */
34 private $container;
35
36 /** @var AccessControl */
37 private $accessControl;
38
39 /** @var WPFunctions */
40 private $wp;
41
42 /** @var SettingsController */
43 private $settings;
44
45 const CURRENT_VERSION = 'v1';
46
47 public function __construct(
48 ContainerInterface $container,
49 AccessControl $accessControl,
50 SettingsController $settings,
51 WPFunctions $wp
52 ) {
53 $this->container = $container;
54 $this->accessControl = $accessControl;
55 $this->settings = $settings;
56 $this->wp = $wp;
57 foreach ($this->availableApiVersions as $availableApiVersion) {
58 $this->addEndpointNamespace(
59 sprintf('%s\%s', __NAMESPACE__, $availableApiVersion),
60 $availableApiVersion
61 );
62 }
63 }
64
65 public function init() {
66 // admin security token and API version
67 WPFunctions::get()->addAction(
68 'admin_head',
69 [$this, 'setTokenAndAPIVersion']
70 );
71
72 // ajax (logged in users)
73 WPFunctions::get()->addAction(
74 'wp_ajax_mailpoet',
75 [$this, 'setupAjax']
76 );
77
78 // ajax (logged out users)
79 WPFunctions::get()->addAction(
80 'wp_ajax_nopriv_mailpoet',
81 [$this, 'setupAjax']
82 );
83 }
84
85 public function setupAjax() {
86 $this->wp->doAction('mailpoet_api_setup', [$this]);
87
88 if (isset($_POST['api_version'])) {
89 $this->setRequestData($_POST, Endpoint::TYPE_POST);
90 } else {
91 $this->setRequestData($_GET, Endpoint::TYPE_GET);
92 }
93
94 $ignoreToken = (
95 $this->settings->get('captcha.type') != Captcha::TYPE_DISABLED &&
96 $this->requestEndpoint === 'subscribers' &&
97 $this->requestMethod === 'subscribe'
98 );
99
100 if (!$ignoreToken && $this->checkToken() === false) {
101 $errorMessage = WPFunctions::get()->__("Sorry, but we couldn't connect to the MailPoet server. Please refresh the web page and try again.", 'mailpoet');
102 $errorResponse = $this->createErrorResponse(Error::UNAUTHORIZED, $errorMessage, Response::STATUS_UNAUTHORIZED);
103 return $errorResponse->send();
104 }
105
106 $response = $this->processRoute();
107 $response->send();
108 }
109
110 public function setRequestData($data, $requestType) {
111 $this->requestApiVersion = !empty($data['api_version']) ? $data['api_version'] : false;
112
113 $this->requestEndpoint = isset($data['endpoint'])
114 ? Helpers::underscoreToCamelCase(trim($data['endpoint']))
115 : null;
116
117 // JS part of /wp-admin/customize.php does not like a 'method' field in a form widget
118 $methodParamName = isset($data['mailpoet_method']) ? 'mailpoet_method' : 'method';
119 $this->requestMethod = isset($data[$methodParamName])
120 ? Helpers::underscoreToCamelCase(trim($data[$methodParamName]))
121 : null;
122 $this->requestType = $requestType;
123
124 $this->requestToken = isset($data['token'])
125 ? trim($data['token'])
126 : null;
127
128 if (!$this->requestEndpoint || !$this->requestMethod || !$this->requestApiVersion) {
129 $errorMessage = WPFunctions::get()->__('Invalid API request.', 'mailpoet');
130 $errorResponse = $this->createErrorResponse(Error::BAD_REQUEST, $errorMessage, Response::STATUS_BAD_REQUEST);
131 return $errorResponse;
132 } else if (!empty($this->endpointNamespaces[$this->requestApiVersion])) {
133 foreach ($this->endpointNamespaces[$this->requestApiVersion] as $namespace) {
134 $endpointClass = sprintf(
135 '%s\%s',
136 $namespace,
137 ucfirst($this->requestEndpoint)
138 );
139 if ($this->container->has($endpointClass)) {
140 $this->requestEndpointClass = $endpointClass;
141 break;
142 }
143 }
144 $this->requestData = isset($data['data'])
145 ? WPFunctions::get()->stripslashesDeep($data['data'])
146 : [];
147
148 // remove reserved keywords from data
149 if (is_array($this->requestData) && !empty($this->requestData)) {
150 // filter out reserved keywords from data
151 $reservedKeywords = [
152 'token',
153 'endpoint',
154 'method',
155 'api_version',
156 'mailpoet_method', // alias of 'method'
157 'mailpoet_redirect',
158 ];
159 $this->requestData = array_diff_key(
160 $this->requestData,
161 array_flip($reservedKeywords)
162 );
163 }
164 }
165 }
166
167 public function processRoute() {
168 try {
169 if (empty($this->requestEndpointClass) ||
170 !$this->container->has($this->requestEndpointClass)
171 ) {
172 throw new \Exception(__('Invalid API endpoint.', 'mailpoet'));
173 }
174
175 $endpoint = $this->container->get($this->requestEndpointClass);
176 if (!method_exists($endpoint, $this->requestMethod)) {
177 throw new \Exception(__('Invalid API endpoint method.', 'mailpoet'));
178 }
179
180 if (!$endpoint->isMethodAllowed($this->requestMethod, $this->requestType)) {
181 throw new \Exception(__('HTTP request method not allowed.', 'mailpoet'));
182 }
183
184 if (class_exists(Debugger::class)) {
185 ApiPanel::init($endpoint, $this->requestMethod, $this->requestData);
186 DIPanel::init();
187 }
188
189 // check the accessibility of the requested endpoint's action
190 // by default, an endpoint's action is considered "private"
191 if (!$this->validatePermissions($this->requestMethod, $endpoint->permissions)) {
192 $errorMessage = WPFunctions::get()->__('You do not have the required permissions.', 'mailpoet');
193 $errorResponse = $this->createErrorResponse(Error::FORBIDDEN, $errorMessage, Response::STATUS_FORBIDDEN);
194 return $errorResponse;
195 }
196 $response = $endpoint->{$this->requestMethod}($this->requestData);
197 return $response;
198 } catch (\Exception $e) {
199 if (class_exists(Debugger::class) && Debugger::$logDirectory) {
200 Debugger::log($e, ILogger::EXCEPTION);
201 }
202 $errorMessage = $e->getMessage();
203 $errorResponse = $this->createErrorResponse(Error::BAD_REQUEST, $errorMessage, Response::STATUS_BAD_REQUEST);
204 return $errorResponse;
205 }
206 }
207
208 public function validatePermissions($requestMethod, $permissions) {
209 // validate method permission if defined, otherwise validate global permission
210 return(!empty($permissions['methods'][$requestMethod])) ?
211 $this->accessControl->validatePermission($permissions['methods'][$requestMethod]) :
212 $this->accessControl->validatePermission($permissions['global']);
213 }
214
215 public function checkToken() {
216 return WPFunctions::get()->wpVerifyNonce($this->requestToken, 'mailpoet_token');
217 }
218
219 public function setTokenAndAPIVersion() {
220 $global = '<script type="text/javascript">';
221 $global .= 'var mailpoet_token = "%s";';
222 $global .= 'var mailpoet_api_version = "%s";';
223 $global .= '</script>';
224 echo sprintf(
225 $global,
226 Security::generateToken(),
227 self::CURRENT_VERSION
228 );
229 }
230
231 public function addEndpointNamespace($namespace, $version) {
232 if (!empty($this->endpointNamespaces[$version][$namespace])) return;
233 $this->endpointNamespaces[$version][] = $namespace;
234 }
235
236 public function getEndpointNamespaces() {
237 return $this->endpointNamespaces;
238 }
239
240 public function getRequestedEndpointClass() {
241 return $this->requestEndpointClass;
242 }
243
244 public function getRequestedAPIVersion() {
245 return $this->requestApiVersion;
246 }
247
248 public function createErrorResponse($errorType, $errorMessage, $responseStatus) {
249 $errorResponse = new ErrorResponse(
250 [
251 $errorType => $errorMessage,
252 ],
253 [],
254 $responseStatus
255 );
256 return $errorResponse;
257 }
258 }
259