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

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

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