PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.8.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.8.0
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
← All changes | includes/Utils/Http.php +59 -86 3.7.33.8.0 View file →
@@ -1,8 +1,11 @@
1 1 <?php
2 2 namespace Templately\Utils;
3 3
4 -use Templately\API\Login;
4 +use Templately\Modules\Auth\REST\Login;
5 +use Templately\Utils\Response\ErrorCode;
6 +use Templately\Utils\Response\ResponseNormalizer;
7 +use Templately\Utils\Response\RetryPolicy;
5 8 use WP_Error;
6 9
7 10 class Http extends Base {
8 11 /**
@@ -216,16 +219,43 @@
216 219 'query' => $query
217 220 ] )
218 221 ];
219 222
220 - $retryCount = 0;
221 - $maxRetries = defined('TEMPLATELY_HTTP_RETRY') ? TEMPLATELY_HTTP_RETRY : 3;
222 - $args = wp_parse_args( $args, $_default_args );
223 - do {
224 - $response = wp_remote_post( $this->url(), $args );
225 - $retryCount++;
226 - } while ( is_wp_error( $response ) && $retryCount < $maxRetries );
223 + $args = wp_parse_args( $args, $_default_args );
227 224
225 + // 043 / PRD PHP-1 — the retry decision moved to RetryPolicy.
226 + //
227 + // This loop retried on WP_Error only, and with NO DELAY: three requests
228 + // within milliseconds at a server that had just failed to answer one. It
229 + // also treated every HTTP status as final, so a 502 from a restarting
230 + // gateway was never retried at all. RetryPolicy adds the transient
231 + // statuses and a jittered backoff.
232 + $attempt = 0;
233 + $maxRetries = defined( 'TEMPLATELY_HTTP_RETRY' ) ? (int) TEMPLATELY_HTTP_RETRY : RetryPolicy::MAX_ATTEMPTS;
234 +
235 + // Entry-point marker (engagement telemetry). A URL QUERY PARAM so the cloud's
236 + // access logs capture it with zero cloud-side code — never a GraphQL argument
237 + // (unknown arguments fail GraphQL validation; a query param on the endpoint
238 + // URL is ignored by the resolver). Appended here, NOT in url(): url() also
239 + // feeds google_auth_url(), which must stay clean.
240 + $request_url = $this->url();
241 + if ( '' !== Helper::get_request_source() ) {
242 + $request_url = add_query_arg( 'tl_source', Helper::get_request_source(), $request_url );
243 + }
244 +
245 + while ( true ) {
246 + $response = wp_remote_post( $request_url, $args );
247 +
248 + if ( $attempt + 1 >= $maxRetries || ! RetryPolicy::should_retry( $response, $attempt ) ) {
249 + break;
250 + }
251 +
252 + RetryPolicy::wait( $attempt );
253 + $attempt++;
254 + }
255 +
256 + $retryCount = $attempt + 1;
257 +
228 258 if ( defined( 'TEMPLATELY_DEBUG_LOG' ) && TEMPLATELY_DEBUG_LOG ) {
229 259 Helper::log( 'Retry Count: ' . $retryCount );
230 260 // Helper::log( 'RAW RESPONSE: ' );
231 261 // Helper::log( $response );
@@ -242,96 +272,38 @@
242 272 * @param array $args
243 273 * @return mixed
244 274 */
245 275 private function maybeErrors( &$response, $args = [] ) {
246 - if ( $response instanceof WP_Error ) {
247 - return $response; // Return WP_Error, if it is an error.
248 - }
276 + // 043 FR-003 — every shape the cloud can return is classified in ONE
277 + // place now. The hand-rolled cascade this replaced grew a branch per
278 + // discovered shape and still disagreed with the equivalent cascade in
279 + // `Helper::make_api_request()`; see the 28 captured fixtures in
280 + // `specs/043-core-api-response-contract/fixtures/`.
281 + $normalized = ResponseNormalizer::normalize( $response, [
282 + 'endpoint' => $this->endpoint,
283 + ] );
249 284
250 - // Check for verification header before processing response body
251 - Helper::check_verification_header( $response );
252 - Helper::check_site_disconnection( $response );
285 + if ( $normalized->is_error() ) {
286 + $error = $normalized->error();
253 287
254 - $response_code = wp_remote_retrieve_response_code( $response );
255 - $response_message = wp_remote_retrieve_response_message( $response );
256 -
257 - /**
258 - * Retrieve Data from Response Body.
259 - */
260 - $response = json_decode( wp_remote_retrieve_body( $response ), true );
261 - /**
262 - * If the graphql hit with any error.
263 - */
264 - if ( ! empty( $response['errors'] ) ) {
265 288 if ( defined( 'TEMPLATELY_DEBUG_LOG' ) && TEMPLATELY_DEBUG_LOG ) {
266 - Helper::log( 'ERROR: ' );
267 - Helper::log( $response['errors'] );
268 - Helper::log( 'END ERROR' );
289 + Helper::log( 'ERROR: ' . $error->code() . ' — ' . $error->message() );
269 290 }
270 - if ( is_array( $response['errors'] ) ) {
271 - $wp_error = new WP_Error;
272 - array_walk( $response['errors'], function ( $error ) use ( &$wp_error ) {
273 - if ( isset( $error['message'] ) ) {
274 - if ( $error['message'] === 'validation' ) {
275 - array_walk( $error['extensions'], function ( $_error, $_error_key ) use ( &$wp_error ) {
276 - if ( $_error_key == 'validation' ) {
277 - array_walk( $_error, function ( $v_error, $key ) use ( &$wp_error ) {
278 - $wp_error->add( "{$key}_error", $v_error[0] );
279 - } );
280 - }
281 - } );
282 - } else {
283 - $error_data = [];
284 - if(!empty($error["extensions"]["statusText"])) {
285 - $error_data["statusText"] = $error["extensions"]["statusText"];
286 - }
287 - if ( isset( $error['debugMessage'] ) ) {
288 - $wp_error->add( 'templately_graphql_error', $error['debugMessage'] );
289 - } else {
290 - $wp_error->add( 'templately_graphql_error', $error['message'], $error_data );
291 - }
292 - }
293 - }
294 - } );
295 291
296 - if( $wp_error->get_error_code() === 'templately_graphql_error' ) {
297 - if( $wp_error->get_error_message() == 'Unauthorized' ) {
298 - $global_user = Login::get_instance()->delete();
299 -
300 - return [
301 - 'redirect' => true,
302 - 'url' => 'sign-in',
303 - 'user' => $global_user,
304 - ];
305 - }
306 - }
307 -
308 - return $wp_error;
292 + // An expired session still tears down the stored login — but it now
293 + // ALSO returns a real error. It used to return a plain array
294 + // (`['redirect' => true, …]`), which every `is_wp_error()` caller
295 + // read as SUCCESS and happily passed on as a payload. That is the
296 + // INV-2 class of bug this contract exists to remove.
297 + if ( ErrorCode::AUTH_EXPIRED === $error->code() || ErrorCode::INVALID_API_KEY === $error->code() ) {
298 + Login::get_instance()->delete();
309 299 }
310 - } elseif ( ! empty( $response['status'] ) && $response['status'] === 'error' ) {
311 300
312 - if ( defined( 'TEMPLATELY_DEBUG_LOG' ) && TEMPLATELY_DEBUG_LOG ) {
313 - Helper::log( 'ERROR: ' );
314 - Helper::log( $response );
315 - Helper::log( 'END ERROR' );
316 - }
317 -
318 - $error_data = [];
319 - if ( ! empty( $response['statusText'] ) ) {
320 - $error_data['statusText'] = $response['statusText'];
321 - }
322 -
323 - $error_message = ! empty( $response['message'] ) ? $response['message'] : __( 'Unknown error occurred', 'templately' );
324 -
325 - return new WP_Error( 'templately_api_error', $error_message, $error_data );
301 + return $error;
326 302 }
327 303
328 - $_response = isset( $response['data'][$this->endpoint] ) ? $response['data'][$this->endpoint] : [];
329 - // {"data":{"connectWithApiKey":{"status":"error","message":"Invalid API key.","user":null}}}
330 - if ( ! empty( $response['status'] ) && $response['status'] === 'error' ) {
304 + $_response = $normalized->payload();
331 305
332 - }
333 -
334 306 if ( defined( 'TEMPLATELY_DEBUG_LOG' ) && TEMPLATELY_DEBUG_LOG ) {
335 307 Helper::log( 'RESPONSE: ' );
336 308 Helper::log( $_response );
337 309 Helper::log( 'END RESPONSE' );
@@ -338,5 +310,6 @@
338 310 }
339 311
340 312 return $_response;
341 313 }
314 +
342 315 }