PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.4.5
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.4.5
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 3.1.10 All 111 releases
← All changes | includes/Utils/Helper.php +8 -186 3.6.63.4.5 View file →
@@ -56,49 +56,24 @@
56 56
57 57 /**
58 58 * Collect IP from request.
59 59 *
60 - * Prefers REMOTE_ADDR since it cannot be spoofed by the client. When it is
61 - * a private/reserved address (reverse proxy, Docker bridge gateway like
62 - * 192.168.65.1, local dev), the forwarded headers are scanned for the first
63 - * public IP. If nothing public is found, the request is local: 127.0.0.1.
64 - *
65 60 * @return string
66 61 */
67 62 public static function get_ip() {
68 - $remote_addr = ! empty($_SERVER['REMOTE_ADDR']) ? sanitize_text_field($_SERVER['REMOTE_ADDR']) : '';
69 -
70 - if (self::is_public_ip($remote_addr)) {
71 - return $remote_addr;
63 + $ip = '127.0.0.1'; // Local IP
64 + if (! empty($_SERVER['HTTP_CLIENT_IP'])) {
65 + $ip = $_SERVER['HTTP_CLIENT_IP'];
66 + } elseif (! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
67 + $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
68 + } else {
69 + $ip = ! empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : $ip;
72 70 }
73 71
74 - foreach (['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP'] as $header) {
75 - if (empty($_SERVER[$header])) {
76 - continue;
77 - }
78 - $candidates = explode(',', sanitize_text_field($_SERVER[$header]));
79 - foreach ($candidates as $candidate) {
80 - $candidate = trim($candidate);
81 - if (self::is_public_ip($candidate)) {
82 - return $candidate;
83 - }
84 - }
85 - }
86 -
87 - return '127.0.0.1';
72 + return sanitize_text_field($ip);
88 73 }
89 74
90 75 /**
91 - * Check whether a string is a valid public (non-private, non-reserved) IP.
92 - *
93 - * @param string $ip
94 - * @return bool
95 - */
96 - private static function is_public_ip($ip): bool {
97 - return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
98 - }
99 -
100 - /**
101 76 * Get views for front-end display
102 77 *
103 78 * @param string $name it will be file name only from the view's folder.
104 79 * @param array $data
@@ -121,17 +96,8 @@
121 96 * @return string Complete API URL
122 97 */
123 98 public static function get_api_url($endpoint): string {
124 99 $base_url = self::is_dev_api() ? 'https://app.templately.dev' : 'https://app.templately.com';
125 -
126 - /**
127 - * Filter the base URL for development API
128 - *
129 - * @since 3.5.0
130 - * @param string $base_url The default base URL
131 - */
132 - $base_url = apply_filters('templately_dev_api_base_url', $base_url);
133 -
134 100 return "{$base_url}/api/{$endpoint}";
135 101 }
136 102
137 103 /**
@@ -158,15 +124,8 @@
158 124 if (strtoupper($method) === 'POST') {
159 125 $headers['Content-Type'] = 'application/json';
160 126 }
161 127
162 - // Resolve requested platform: $_REQUEST wins (frontend-supplied), then caller's extra_headers, then default.
163 - if ( isset( $_REQUEST['requested_platform'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
164 - $extra_headers['x-templately-requested-platform'] = sanitize_text_field( wp_unslash( $_REQUEST['requested_platform'] ) );
165 - } elseif ( ! isset( $extra_headers['x-templately-requested-platform'] ) ) {
166 - $extra_headers['x-templately-requested-platform'] = 'templately';
167 - }
168 -
169 128 // Merge additional headers
170 129 $headers = array_merge($headers, $extra_headers);
171 130
172 131 $args = [
@@ -191,12 +150,8 @@
191 150
192 151 // Check for verification header in the response
193 152 self::check_verification_header($response);
194 153
195 -
196 - // Check for site disconnection in response body
197 - self::check_site_disconnection($response);
198 -
199 154 return $response;
200 155 }
201 156
202 157 /**
@@ -254,46 +209,8 @@
254 209 return $sanitized_value;
255 210 }
256 211
257 212 /**
258 - * Escape a string for safe embedding inside a GraphQL or JSON string literal.
259 - *
260 - * GraphQL string escaping rules are identical to JSON string escaping (per the
261 - * GraphQL spec), so wp_json_encode() is the authoritative escaper. We strip the
262 - * outer quotes it adds and return only the escaped inner content, ready to be
263 - * wrapped in your own quote pair.
264 - *
265 - * Handles pre-encoded JSON: when the caller has already run json_encode() +
266 - * wp_slash() on a value (e.g. categories, dependencies in Items.php), the
267 - * quotes are already escaped as \" and the string is ready to embed. Calling
268 - * wp_json_encode() again would double-escape those backslashes. We detect this
269 - * case by checking whether wp_unslash() produces valid JSON, and if so, return
270 - * the value directly without further encoding.
271 - *
272 - * @param string $value Raw string or wp_slash(json_encode()) output.
273 - * @return string Escaped string, safe to place between double quotes in GraphQL/JSON.
274 - */
275 - public static function esc_json_string( $value ) {
276 - $value = (string) $value;
277 -
278 - // If wp_slash() was applied to a JSON string upstream, the quotes are
279 - // already escaped (e.g. {\"key\":\"val\"}). Detect this by unslashing and
280 - // checking for valid JSON — if it matches, the value is already suitable
281 - // for embedding in a string literal; return it as-is to avoid doubling backslashes.
282 - $unslashed = wp_unslash( $value );
283 - if ( $unslashed !== $value ) {
284 - $decoded = json_decode( $unslashed, true );
285 - if ( json_last_error() === JSON_ERROR_NONE && null !== $decoded ) {
286 - return $value;
287 - }
288 - }
289 -
290 - $encoded = wp_json_encode( $value );
291 - // wp_json_encode wraps the value in "...", strip those outer quotes.
292 - return substr( $encoded, 1, -1 );
293 - }
294 -
295 - /**
296 213 * Check for X-Templately-Verified header and update user verification status
297 214 *
298 215 * @param array|WP_Error $response The HTTP response array from wp_remote_get/wp_remote_post
299 216 * @return void
@@ -342,103 +259,8 @@
342 259 }
343 260 }
344 261
345 262 return false;
346 - }
347 -
348 - /**
349 - * Check for site disconnection status in API response body
350 - *
351 - * Detects SiteNotConnected errors and updates user disconnection status.
352 - * Sends X-Templately-Disconnected header for frontend detection.
353 - *
354 - *
355 - * @param array|WP_Error|mixed $response The response object or body array
356 - * @return bool True if site is disconnected, false otherwise
357 - */
358 - public static function check_site_disconnection($response) {
359 - if (is_wp_error($response)) {
360 - return false;
361 - }
362 -
363 - $response_body = $response;
364 -
365 - // If it's a raw WP response array with body, decode it
366 - if (is_array($response) && isset($response['body']) && is_string($response['body'])) {
367 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
368 - }
369 -
370 - // Check if response body indicates site disconnection
371 - if (!is_array($response_body)) {
372 - return false;
373 - }
374 -
375 - $status = $response_body['status'] ?? null;
376 - $status_text = $response_body['statusText'] ?? null;
377 -
378 - // Check for SiteNotConnected error
379 - if ($status === 'error' && $status_text === 'SiteNotConnected') {
380 - try {
381 - // Get current user data
382 - $options = Options::get_instance();
383 - $user = $options->get('user');
384 -
385 - // Only update if user data exists
386 - if (!empty($user) && is_array($user)) {
387 - // Set disconnection flag
388 - $user['is_disconnected'] = true;
389 -
390 - // Save updated user data
391 - $options->set('user', $user);
392 -
393 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
394 - self::log('Site disconnection detected: SiteNotConnected status');
395 - }
396 - }
397 -
398 - // Send header for frontend detection
399 - if (!headers_sent()) {
400 - header('X-Templately-Disconnected: true');
401 - }
402 -
403 - return true;
404 - } catch (\Exception $e) {
405 - // Log error if debug logging is enabled
406 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
407 - self::log('Error updating site disconnection status: ' . $e->getMessage());
408 - }
409 - }
410 - }
411 -
412 - return false;
413 - }
414 -
415 - /**
416 - * Clear site disconnection status
417 - *
418 - * Called after successful site migration to reset the disconnection flag.
419 - *
420 - * @return void
421 - */
422 - public static function clear_site_disconnection() {
423 - try {
424 - $options = Options::get_instance();
425 - $user = $options->get('user');
426 -
427 - if (!empty($user) && is_array($user)) {
428 - $user['site_url'] = base64_encode( home_url('/') );
429 - $user['is_disconnected'] = false;
430 - $options->set('user', $user);
431 -
432 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
433 - self::log('Site disconnection status cleared and URL updated.');
434 - }
435 - }
436 - } catch (\Exception $e) {
437 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
438 - self::log('Error clearing site disconnection status: ' . $e->getMessage());
439 - }
440 - }
441 263 }
442 264
443 265 /**
444 266 * API Error Formatter