PluginProbe
Parse.ly / 3.16.3
Parse.ly v3.16.3
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 / Endpoints / class-base-api-proxy.php

class-base-api-proxy.php in Parse.ly 3.16.3, at src/Endpoints/class-base-api-proxy.php

286 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 * Endpoints: Base API proxy endpoint class for all API proxy endpoints
4 *
5 * @package Parsely
6 * @since 3.4.0
7 */
8
9 declare(strict_types=1);
10
11 namespace Parsely\Endpoints;
12
13 use Parsely\Parsely;
14 use Parsely\RemoteAPI\Remote_API_Interface;
15 use stdClass;
16 use WP_Error;
17 use WP_REST_Request;
18 use WP_REST_Server;
19
20 use function Parsely\Utils\convert_endpoint_to_filter_key;
21 use function Parsely\Utils\get_date_format;
22 use function Parsely\Utils\get_formatted_duration;
23 use function Parsely\Utils\parsely_is_https_supported;
24
25 /**
26 * Configures a REST API endpoint for use.
27 */
28 abstract class Base_API_Proxy {
29 /**
30 * Parsely object instance.
31 *
32 * @var Parsely
33 */
34 protected $parsely;
35
36 /**
37 * Proxy object which does the actual calls to the Parse.ly API.
38 *
39 * @var Remote_API_Interface
40 */
41 private $api;
42
43 /**
44 * The itm_source value to be used for some of the returned URLs.
45 *
46 * @var string|null
47 */
48 protected $itm_source = null;
49
50 /**
51 * Registers the endpoint's WP REST route.
52 */
53 abstract public function run(): void;
54
55 /**
56 * Generates the final data from the passed response.
57 *
58 * @param array<stdClass> $response The response received by the proxy.
59 * @return array<stdClass> The generated data.
60 */
61 abstract protected function generate_data( $response ): array;
62
63 /**
64 * Cached "proxy" to the Parse.ly API endpoint.
65 *
66 * @param WP_REST_Request $request The request object.
67 * @return stdClass|WP_Error stdClass containing the data or a WP_Error object on failure.
68 */
69 abstract public function get_items( WP_REST_Request $request );
70
71 /**
72 * Returns whether the endpoint is available for access by the current
73 * user.
74 *
75 * @since 3.14.0 Renamed from `permission_callback()`.
76 *
77 * @return bool
78 */
79 public function is_available_to_current_user(): bool {
80 return $this->api->is_available_to_current_user();
81 }
82
83 /**
84 * Constructor.
85 *
86 * @param Parsely $parsely Instance of Parsely class.
87 * @param Remote_API_Interface $api API object which does the actual calls to the Parse.ly API.
88 */
89 public function __construct( Parsely $parsely, Remote_API_Interface $api ) {
90 $this->parsely = $parsely;
91 $this->api = $api;
92 }
93
94 /**
95 * Registers the endpoint's WP REST route.
96 *
97 * @param string $endpoint The endpoint's route (e.g. /stats/posts).
98 * @param array<string> $methods The HTTP methods to use for the endpoint.
99 */
100 protected function register_endpoint( string $endpoint, array $methods = array( WP_REST_Server::READABLE ) ): void {
101 if ( ! apply_filters( 'wp_parsely_enable_' . convert_endpoint_to_filter_key( $endpoint ) . '_api_proxy', true ) ) {
102 return;
103 }
104
105 $get_items_args = array(
106 'query' => array(
107 'default' => array(),
108 'sanitize_callback' => function ( array $query ) {
109 $sanitized_query = array();
110 foreach ( $query as $key => $value ) {
111 $sanitized_query[ sanitize_key( $key ) ] = sanitize_text_field( $value );
112 }
113
114 return $sanitized_query;
115 },
116 ),
117 );
118
119 $rest_route_args = array(
120 array(
121 'methods' => $methods,
122 'callback' => array( $this, 'get_items' ),
123 'permission_callback' => array( $this, 'is_available_to_current_user' ),
124 'args' => $get_items_args,
125 'show_in_index' => $this->is_available_to_current_user(),
126 ),
127 );
128
129 register_rest_route( 'wp-parsely/v1', $endpoint, $rest_route_args );
130 }
131
132 /**
133 * Cached "proxy" to the endpoint.
134 *
135 * @param WP_REST_Request $request The request object.
136 * @param bool $require_api_secret Specifies if the API Secret is required.
137 * @param string|null $param_item The param element to use to get the items.
138 * @return stdClass|WP_Error stdClass containing the data or a WP_Error object on failure.
139 */
140 protected function get_data( WP_REST_Request $request, bool $require_api_secret = true, string $param_item = null ) {
141 // Validate Site ID and secret.
142 $validation = $this->validate_apikey_and_secret( $require_api_secret );
143 if ( is_wp_error( $validation ) ) {
144 return $validation;
145 }
146
147 if ( null !== $param_item ) {
148 $params = $request->get_param( $param_item );
149 } else {
150 $params = $request->get_params();
151 }
152
153 if ( is_array( $params ) && isset( $params['itm_source'] ) ) {
154 $this->itm_source = $params['itm_source'];
155 }
156
157 // A proxy with caching behavior is used here.
158 $response = $this->api->get_items( $params );
159
160 if ( is_wp_error( $response ) ) {
161 return $response;
162 }
163
164 return (object) array(
165 'data' => $this->generate_data( $response ), // @phpstan-ignore-line.
166 );
167 }
168
169 /**
170 * Validates that the Site ID and secret are set.
171 * If the API secret is not required, it will not be validated.
172 *
173 * @since 3.13.0
174 *
175 * @param bool $require_api_secret Specifies if the API Secret is required.
176 * @return WP_Error|bool
177 */
178 protected function validate_apikey_and_secret( bool $require_api_secret = true ) {
179 if ( false === $this->parsely->site_id_is_set() ) {
180 return new WP_Error(
181 'parsely_site_id_not_set',
182 __( 'A Parse.ly Site ID must be set in site options to use this endpoint', 'wp-parsely' ),
183 array( 'status' => 403 )
184 );
185 }
186
187 if ( $require_api_secret && false === $this->parsely->api_secret_is_set() ) {
188 return new WP_Error(
189 'parsely_api_secret_not_set',
190 __( 'A Parse.ly API Secret must be set in site options to use this endpoint', 'wp-parsely' ),
191 array( 'status' => 403 )
192 );
193 }
194
195 return true;
196 }
197
198 /**
199 * Extracts the post data from the passed object.
200 *
201 * Should only be used with endpoints that return post data.
202 *
203 * @since 3.10.0
204 *
205 * @param stdClass $item The object to extract the data from.
206 * @return array<string, mixed> The extracted data.
207 */
208 protected function extract_post_data( stdClass $item ): array {
209 $data = array();
210
211 if ( isset( $item->author ) ) {
212 $data['author'] = $item->author;
213 }
214
215 if ( isset( $item->metrics->views ) ) {
216 $data['views'] = number_format_i18n( $item->metrics->views );
217 }
218
219 if ( isset( $item->metrics->visitors ) ) {
220 $data['visitors'] = number_format_i18n( $item->metrics->visitors );
221 }
222
223 // The avg_engaged metric can be in different locations depending on the
224 // endpoint and passed sort/url parameters.
225 $avg_engaged = $item->metrics->avg_engaged ?? $item->avg_engaged ?? null;
226 if ( null !== $avg_engaged ) {
227 $data['avgEngaged'] = get_formatted_duration( (float) $avg_engaged );
228 }
229
230 if ( isset( $item->pub_date ) ) {
231 $data['date'] = wp_date( get_date_format(), strtotime( $item->pub_date ) );
232 }
233
234 if ( isset( $item->title ) ) {
235 $data['title'] = $item->title;
236 }
237
238 if ( isset( $item->url ) ) {
239 $site_id = $this->parsely->get_site_id();
240 // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.url_to_postid_url_to_postid
241 $post_id = url_to_postid( $item->url ); // 0 if the post cannot be found.
242
243 $post_url = Parsely::get_url_with_itm_source( $item->url, null );
244 if ( parsely_is_https_supported() ) {
245 $post_url = str_replace( 'http://', 'https://', $post_url );
246 }
247
248 $data['rawUrl'] = $post_url;
249 $data['dashUrl'] = Parsely::get_dash_url( $site_id, $post_url );
250 $data['id'] = Parsely::get_url_with_itm_source( $post_url, null ); // Unique.
251 $data['postId'] = $post_id; // Might not be unique.
252 $data['url'] = Parsely::get_url_with_itm_source( $post_url, $this->itm_source );
253
254 // Set thumbnail URL, falling back to the Parse.ly thumbnail if needed.
255 $thumbnail_url = get_the_post_thumbnail_url( $post_id, 'thumbnail' );
256 if ( false !== $thumbnail_url ) {
257 $data['thumbnailUrl'] = $thumbnail_url;
258 } elseif ( isset( $item->thumb_url_medium ) ) {
259 $data['thumbnailUrl'] = $item->thumb_url_medium;
260 }
261 }
262
263 return $data;
264 }
265
266 /**
267 * Generates the post data from the passed response.
268 *
269 * Should only be used with endpoints that return post data.
270 *
271 * @since 3.10.0
272 *
273 * @param array<stdClass> $response The response received by the proxy.
274 * @return array<stdClass> The generated data.
275 */
276 protected function generate_post_data( array $response ): array {
277 $data = array();
278
279 foreach ( $response as $item ) {
280 $data [] = (object) $this->extract_post_data( $item );
281 }
282
283 return $data;
284 }
285 }
286