PluginProbe
Parse.ly / 3.7.0
Parse.ly v3.7.0
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
wp-parsely / src / RemoteAPI / class-remote-api-base.php

class-remote-api-base.php in Parse.ly 3.7.0, at src/RemoteAPI/class-remote-api-base.php

202 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Remote API: Base class for all Parse.ly API endpoints
4 *
5 * @package Parsely
6 * @since 3.2.0
7 */
8
9 declare(strict_types=1);
10
11 namespace Parsely\RemoteAPI;
12
13 use Parsely\Parsely;
14 use UnexpectedValueException;
15 use WP_Error;
16
17 use function Parsely\Utils\convert_endpoint_to_filter_key;
18 use function Parsely\Utils\convert_to_associative_array;
19
20 /**
21 * Base API for all Parse.ly API endpoints.
22 *
23 * Child classes must add a protected `ENDPOINT` constant, and a protected
24 * QUERY_FILTER constant.
25 *
26 * @since 3.2.0
27 *
28 * @phpstan-type Remote_API_Error array{
29 * code: int,
30 * message: string,
31 * htmlMessage: string,
32 * }
33 */
34 abstract class Remote_API_Base implements Remote_API_Interface {
35 protected const ENDPOINT = '';
36 protected const QUERY_FILTER = '';
37
38 /**
39 * Indicates whether the endpoint is public or protected behind permissions.
40 *
41 * @since 3.7.0
42 *
43 * @var bool
44 */
45 protected $is_public_endpoint = false;
46
47 /**
48 * Parsely Instance.
49 *
50 * @var Parsely
51 */
52 private $parsely;
53
54 /**
55 * User capability based on which we should allow access to the endpoint.
56 *
57 * `null` should be used for all public endpoints.
58 *
59 * @since 3.7.0
60 *
61 * @var string|null
62 */
63 private $user_capability;
64
65 /**
66 * Constructor.
67 *
68 * @param Parsely $parsely Parsely instance.
69 *
70 * @since 3.2.0
71 * @since 3.7.0 Added user capability checks based on `is_public_endpoint` attribute.
72 */
73 public function __construct( Parsely $parsely ) {
74 $this->parsely = $parsely;
75
76 if ( $this->is_public_endpoint ) {
77 $this->user_capability = null;
78 } else {
79 /**
80 * Filter to change the default user capability for all private remote apis.
81 *
82 * @var string
83 */
84 $default_user_capability = apply_filters( 'wp_parsely_user_capability_for_all_private_apis', 'publish_posts' );
85
86 /**
87 * Filter to change the user capability for specific remote api.
88 *
89 * @var string
90 */
91 $endpoint_specific_user_capability = apply_filters( 'wp_parsely_user_capability_for_' . convert_endpoint_to_filter_key( static::ENDPOINT ) . '_api', $default_user_capability );
92
93 $this->user_capability = $endpoint_specific_user_capability;
94 }
95 }
96
97 /**
98 * Gets Parse.ly API endpoint.
99 *
100 * @since 3.6.2
101 *
102 * @return string
103 */
104 public function get_endpoint(): string {
105 return static::ENDPOINT;
106 }
107
108 /**
109 * Gets the URL for a particular Parse.ly API endpoint.
110 *
111 * @since 3.2.0
112 *
113 * @throws UnexpectedValueException If the endpoint constant is not defined.
114 * @throws UnexpectedValueException If the query filter constant is not defined.
115 *
116 * @param array<string, mixed> $query The query arguments to send to the remote API.
117 * @return string
118 */
119 public function get_api_url( array $query ): string {
120 if ( static::ENDPOINT === '' ) {
121 throw new UnexpectedValueException( 'ENDPOINT constant must be defined in child class.' );
122 }
123 if ( static::QUERY_FILTER === '' ) {
124 throw new UnexpectedValueException( 'QUERY_FILTER constant must be defined in child class.' );
125 }
126
127 $query['apikey'] = $this->parsely->get_site_id();
128 if ( $this->parsely->api_secret_is_set() ) {
129 $query['secret'] = $this->parsely->get_api_secret();
130 }
131 $query = array_filter( $query );
132
133 // Sort by key so the query args are in alphabetical order.
134 ksort( $query );
135
136 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Hook names are defined in child classes.
137 $query = apply_filters( static::QUERY_FILTER, $query );
138 return add_query_arg( $query, Parsely::PUBLIC_API_BASE_URL . static::ENDPOINT );
139 }
140
141 /**
142 * Gets items from the specified endpoint.
143 *
144 * @since 3.2.0
145 * @since 3.7.0 Added $associative param.
146 *
147 * @param array<string, mixed> $query The query arguments to send to the remote API.
148 * @param bool $associative When TRUE, returned objects will be converted into associative arrays.
149 *
150 * @return WP_Error|array<string, mixed>
151 */
152 public function get_items( $query, $associative = false ) {
153 $full_api_url = $this->get_api_url( $query );
154
155 $result = wp_safe_remote_get( $full_api_url, array() );
156
157 if ( is_wp_error( $result ) ) {
158 return $result;
159 }
160
161 $body = wp_remote_retrieve_body( $result );
162 $decoded = json_decode( $body );
163
164 if ( ! is_object( $decoded ) ) {
165 return new WP_Error( 400, __( 'Unable to decode upstream API response', 'wp-parsely' ) );
166 }
167
168 if ( ! property_exists( $decoded, 'data' ) ) {
169 return new WP_Error( $decoded->code ?? 400, $decoded->message ?? __( 'Unable to read data from upstream API', 'wp-parsely' ) );
170 }
171
172 if ( ! is_array( $decoded->data ) ) {
173 return new WP_Error( 400, __( 'Unable to parse data from upstream API', 'wp-parsely' ) );
174 }
175
176 $response = $decoded->data;
177
178 return $associative ? convert_to_associative_array( $response ) : $response;
179 }
180
181 /**
182 * Checks if the current user is allowed to make the API call.
183 *
184 * @since 3.7.0
185 *
186 * @return bool
187 */
188 public function is_user_allowed_to_make_api_call(): bool {
189 // This endpoint does not require any capability checks.
190 if ( is_null( $this->user_capability ) ) {
191 return true;
192 }
193
194 // The user has the required capability to access this endpoint.
195 if ( current_user_can( $this->user_capability ) ) {
196 return true;
197 }
198
199 return false;
200 }
201 }
202