Router.php
130 lines
| 1 | <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing |
| 2 | |
| 3 | namespace MailPoet\Router; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use MailPoet\Config\AccessControl; |
| 9 | use MailPoet\Util\Headers; |
| 10 | use MailPoet\Util\Helpers; |
| 11 | use MailPoet\WP\Functions as WPFunctions; |
| 12 | use MailPoetVendor\Psr\Container\ContainerInterface; |
| 13 | |
| 14 | class Router { |
| 15 | public $apiRequest; |
| 16 | public $endpoint; |
| 17 | public $action; |
| 18 | public $data; |
| 19 | public $endpointAction; |
| 20 | public $accessControl; |
| 21 | /** @var ContainerInterface */ |
| 22 | private $container; |
| 23 | const NAME = 'mailpoet_router'; |
| 24 | const RESPONSE_ERROR = 404; |
| 25 | const RESPONE_FORBIDDEN = 403; |
| 26 | |
| 27 | public function __construct( |
| 28 | AccessControl $accessControl, |
| 29 | ContainerInterface $container, |
| 30 | $apiData = false |
| 31 | ) { |
| 32 | $apiData = ($apiData) ? $apiData : $_GET; |
| 33 | $this->apiRequest = is_array($apiData) && array_key_exists(self::NAME, $apiData); |
| 34 | $this->endpoint = (isset($apiData['endpoint']) && is_string($apiData['endpoint'])) ? |
| 35 | Helpers::underscoreToCamelCase($apiData['endpoint']) : |
| 36 | false; |
| 37 | $this->endpointAction = (isset($apiData['action']) && is_string($apiData['action'])) ? |
| 38 | Helpers::underscoreToCamelCase($apiData['action']) : |
| 39 | false; |
| 40 | $this->data = isset($apiData['data']) ? |
| 41 | self::decodeRequestData($apiData['data']) : |
| 42 | []; |
| 43 | $this->accessControl = $accessControl; |
| 44 | $this->container = $container; |
| 45 | } |
| 46 | |
| 47 | public function init() { |
| 48 | if (!$this->apiRequest) return; |
| 49 | |
| 50 | // The public MailPoet router is using GET requests, |
| 51 | // but we don't expect any caching of the responses. |
| 52 | Headers::setNoCacheHeaders(); |
| 53 | |
| 54 | $endpointClass = __NAMESPACE__ . "\\Endpoints\\" . ucfirst($this->endpoint); |
| 55 | |
| 56 | if (!$this->endpoint || !class_exists($endpointClass)) { |
| 57 | return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint', 'mailpoet')); |
| 58 | } |
| 59 | |
| 60 | $endpoint = $this->container->get($endpointClass); |
| 61 | |
| 62 | if (!is_object($endpoint) || !method_exists($endpoint, $this->endpointAction)) { |
| 63 | return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action', 'mailpoet')); |
| 64 | } |
| 65 | $allowedActions = property_exists($endpoint, 'allowedActions') && is_array($endpoint->allowedActions) ? $endpoint->allowedActions : []; |
| 66 | if (!in_array($this->endpointAction, $allowedActions)) { |
| 67 | return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action', 'mailpoet')); |
| 68 | } |
| 69 | $permissions = property_exists($endpoint, 'permissions') && is_array($endpoint->permissions) ? $endpoint->permissions : []; |
| 70 | if (!$this->validatePermissions($this->endpointAction, $permissions)) { |
| 71 | return $this->terminateRequest(self::RESPONE_FORBIDDEN, __('You do not have the required permissions.', 'mailpoet')); |
| 72 | } |
| 73 | WPFunctions::get()->doAction('mailpoet_conflict_resolver_router_url_query_parameters'); |
| 74 | $callback = [ |
| 75 | $endpoint, |
| 76 | $this->endpointAction, |
| 77 | ]; |
| 78 | if (is_callable($callback)) { |
| 79 | return call_user_func($callback, $this->data); |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | public static function decodeRequestData($data) { |
| 84 | $data = !is_array($data) ? json_decode(base64_decode($data), true) : []; |
| 85 | if (!is_array($data)) { |
| 86 | $data = []; |
| 87 | } |
| 88 | return $data; |
| 89 | } |
| 90 | |
| 91 | public static function encodeRequestData($data) { |
| 92 | $jsonEncoded = json_encode($data); |
| 93 | if ($jsonEncoded === false) { |
| 94 | return ''; |
| 95 | } |
| 96 | return rtrim(base64_encode($jsonEncoded), '='); |
| 97 | } |
| 98 | |
| 99 | public static function buildRequest($endpoint, $action, $data = false) { |
| 100 | $params = [ |
| 101 | self::NAME => '', |
| 102 | 'endpoint' => $endpoint, |
| 103 | 'action' => $action, |
| 104 | ]; |
| 105 | if ($data) { |
| 106 | $params['data'] = self::encodeRequestData($data); |
| 107 | } |
| 108 | return WPFunctions::get()->addQueryArg($params, WPFunctions::get()->homeUrl()); |
| 109 | } |
| 110 | |
| 111 | public function terminateRequest($code, $message) { |
| 112 | WPFunctions::get()->statusHeader($code, $message); |
| 113 | exit; |
| 114 | } |
| 115 | |
| 116 | public function validatePermissions($endpointAction, $permissions) { |
| 117 | if (!is_array($permissions)) { |
| 118 | return false; |
| 119 | } |
| 120 | $actionPermissions = $permissions['actions'] ?? null; |
| 121 | if (is_array($actionPermissions) && !empty($actionPermissions[$endpointAction])) { |
| 122 | return $this->accessControl->validatePermission($actionPermissions[$endpointAction]); |
| 123 | } |
| 124 | if (!isset($permissions['global'])) { |
| 125 | return false; |
| 126 | } |
| 127 | return $this->accessControl->validatePermission($permissions['global']); |
| 128 | } |
| 129 | } |
| 130 |