PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.4.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.4.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 3.1.10 All 111 releases
← All changes | includes/Utils/Helper.php +30 -300 3.6.73.4.0 View file →
@@ -19,13 +19,22 @@
19 19 class Helper extends Base {
20 20 /**
21 21 * Check if development API should be used
22 22 *
23 + * This method maintains backward compatibility by checking both TEMPLATELY_DEV_API
24 + * and falling back to TEMPLATELY_DEV if needed. This fallback logic should NOT be
25 + * removed as it ensures existing setups continue to work.
26 + *
23 27 * @return bool True if development API should be used
24 28 */
25 29 public static function is_dev_api(){
26 - // Only check TEMPLATELY_DEV_API constant - no fallback mechanisms
27 - return defined( 'TEMPLATELY_DEV_API' ) && constant( 'TEMPLATELY_DEV_API' );
30 + // Primary check: TEMPLATELY_DEV_API constant
31 + if ( defined( 'TEMPLATELY_DEV_API' ) ) {
32 + return constant( 'TEMPLATELY_DEV_API' );
33 + }
34 +
35 + // Fallback: Legacy TEMPLATELY_DEV constant for backward compatibility
36 + return defined( 'TEMPLATELY_DEV' ) && constant( 'TEMPLATELY_DEV' );
28 37 }
29 38
30 39 /**
31 40 * Get installed WordPress Plugin List
@@ -56,49 +65,24 @@
56 65
57 66 /**
58 67 * Collect IP from request.
59 68 *
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 69 * @return string
66 70 */
67 71 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;
72 + $ip = '127.0.0.1'; // Local IP
73 + if (! empty($_SERVER['HTTP_CLIENT_IP'])) {
74 + $ip = $_SERVER['HTTP_CLIENT_IP'];
75 + } elseif (! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
76 + $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
77 + } else {
78 + $ip = ! empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : $ip;
72 79 }
73 80
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';
81 + return sanitize_text_field($ip);
88 82 }
89 83
90 84 /**
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 85 * Get views for front-end display
102 86 *
103 87 * @param string $name it will be file name only from the view's folder.
104 88 * @param array $data
@@ -121,17 +105,8 @@
121 105 * @return string Complete API URL
122 106 */
123 107 public static function get_api_url($endpoint): string {
124 108 $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 109 return "{$base_url}/api/{$endpoint}";
135 110 }
136 111
137 112 /**
@@ -151,13 +126,8 @@
151 126 'Authorization' => 'Bearer ' . $api_key,
152 127 'x-templately-ip' => self::get_ip(),
153 128 'x-templately-url' => home_url('/'),
154 129 'x-templately-version' => defined( 'TEMPLATELY_VERSION' ) ? constant( 'TEMPLATELY_VERSION' ) : '1.0.0',
155 - // Force JSON responses so the cloud returns JSON errors instead of an HTML
156 - // error page (which json_decode() cannot parse). Binary/XML downloads
157 - // (zip pack, attachment WXR) use their own wp_remote_* calls and bypass
158 - // this helper, so they are unaffected. Callers can override via $extra_headers.
159 - 'Accept' => 'application/json',
160 130 ];
161 131
162 132 // Add Content-Type for POST requests
163 133 if (strtoupper($method) === 'POST') {
@@ -163,15 +133,8 @@
163 133 if (strtoupper($method) === 'POST') {
164 134 $headers['Content-Type'] = 'application/json';
165 135 }
166 136
167 - // Resolve requested platform: $_REQUEST wins (frontend-supplied), then caller's extra_headers, then default.
168 - if ( isset( $_REQUEST['requested_platform'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
169 - $extra_headers['x-templately-requested-platform'] = sanitize_text_field( wp_unslash( $_REQUEST['requested_platform'] ) );
170 - } elseif ( ! isset( $extra_headers['x-templately-requested-platform'] ) ) {
171 - $extra_headers['x-templately-requested-platform'] = 'templately';
172 - }
173 -
174 137 // Merge additional headers
175 138 $headers = array_merge($headers, $extra_headers);
176 139
177 140 $args = [
@@ -188,21 +151,12 @@
188 151 }
189 152
190 153 // Make the appropriate request
191 154 if (strtoupper($method) === 'POST') {
192 - $response = wp_remote_post($api_url, $args);
155 + return wp_remote_post($api_url, $args);
193 156 } else {
194 - $response = wp_remote_get($api_url, $args);
157 + return wp_remote_get($api_url, $args);
195 158 }
196 -
197 - // Check for verification header in the response
198 - self::check_verification_header($response);
199 -
200 -
201 - // Check for site disconnection in response body
202 - self::check_site_disconnection($response);
203 -
204 - return $response;
205 159 }
206 160
207 161 /**
208 162 * Make a GET request to Templately API
@@ -259,194 +213,8 @@
259 213 return $sanitized_value;
260 214 }
261 215
262 216 /**
263 - * Escape a string for safe embedding inside a GraphQL or JSON string literal.
264 - *
265 - * GraphQL string escaping rules are identical to JSON string escaping (per the
266 - * GraphQL spec), so wp_json_encode() is the authoritative escaper. We strip the
267 - * outer quotes it adds and return only the escaped inner content, ready to be
268 - * wrapped in your own quote pair.
269 - *
270 - * Handles pre-encoded JSON: when the caller has already run json_encode() +
271 - * wp_slash() on a value (e.g. categories, dependencies in Items.php), the
272 - * quotes are already escaped as \" and the string is ready to embed. Calling
273 - * wp_json_encode() again would double-escape those backslashes. We detect this
274 - * case by checking whether wp_unslash() produces valid JSON, and if so, return
275 - * the value directly without further encoding.
276 - *
277 - * @param string $value Raw string or wp_slash(json_encode()) output.
278 - * @return string Escaped string, safe to place between double quotes in GraphQL/JSON.
279 - */
280 - public static function esc_json_string( $value ) {
281 - $value = (string) $value;
282 -
283 - // If wp_slash() was applied to a JSON string upstream, the quotes are
284 - // already escaped (e.g. {\"key\":\"val\"}). Detect this by unslashing and
285 - // checking for valid JSON — if it matches, the value is already suitable
286 - // for embedding in a string literal; return it as-is to avoid doubling backslashes.
287 - $unslashed = wp_unslash( $value );
288 - if ( $unslashed !== $value ) {
289 - $decoded = json_decode( $unslashed, true );
290 - if ( json_last_error() === JSON_ERROR_NONE && null !== $decoded ) {
291 - return $value;
292 - }
293 - }
294 -
295 - $encoded = wp_json_encode( $value );
296 - // wp_json_encode wraps the value in "...", strip those outer quotes.
297 - return substr( $encoded, 1, -1 );
298 - }
299 -
300 - /**
301 - * Check for X-Templately-Verified header and update user verification status
302 - *
303 - * @param array|WP_Error $response The HTTP response array from wp_remote_get/wp_remote_post
304 - * @return void
305 - */
306 - public static function check_verification_header($response) {
307 - // Only process if response is not a WP_Error and contains headers
308 - if (is_wp_error($response)) {
309 - return;
310 - }
311 -
312 - // Retrieve the X-Templately-Verified header
313 - $verification_header = wp_remote_retrieve_header($response, 'X-Templately-Verified');
314 -
315 - // Check if header exists and has a truthy value
316 - if (!empty($verification_header) && filter_var($verification_header, FILTER_VALIDATE_BOOLEAN)) {
317 - try {
318 - // Get current user data
319 - $options = Options::get_instance();
320 - $user = $options->get('user');
321 -
322 - // Only update if user data exists and is not already verified
323 - if (!empty($user) && is_array($user) && empty($user['is_verified'])) {
324 - // Set verification flag
325 - $user['is_verified'] = true;
326 -
327 - // Save updated user data
328 - $options->set('user', $user);
329 -
330 - }
331 -
332 - if (!empty($user['is_verified'])){
333 - if(!headers_sent()){
334 - header( 'X-Templately-Verified: true' );
335 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
336 - self::log('User verification status already updated via X-Templately-Verified header');
337 - }
338 - }
339 -
340 - return true;
341 - }
342 - } catch (\Exception $e) {
343 - // Log error if debug logging is enabled
344 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
345 - self::log('Error updating user verification status: ' . $e->getMessage());
346 - }
347 - }
348 - }
349 -
350 - return false;
351 - }
352 -
353 - /**
354 - * Check for site disconnection status in API response body
355 - *
356 - * Detects SiteNotConnected errors and updates user disconnection status.
357 - * Sends X-Templately-Disconnected header for frontend detection.
358 - *
359 - *
360 - * @param array|WP_Error|mixed $response The response object or body array
361 - * @return bool True if site is disconnected, false otherwise
362 - */
363 - public static function check_site_disconnection($response) {
364 - if (is_wp_error($response)) {
365 - return false;
366 - }
367 -
368 - $response_body = $response;
369 -
370 - // If it's a raw WP response array with body, decode it
371 - if (is_array($response) && isset($response['body']) && is_string($response['body'])) {
372 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
373 - }
374 -
375 - // Check if response body indicates site disconnection
376 - if (!is_array($response_body)) {
377 - return false;
378 - }
379 -
380 - $status = $response_body['status'] ?? null;
381 - $status_text = $response_body['statusText'] ?? null;
382 -
383 - // Check for SiteNotConnected error
384 - if ($status === 'error' && $status_text === 'SiteNotConnected') {
385 - try {
386 - // Get current user data
387 - $options = Options::get_instance();
388 - $user = $options->get('user');
389 -
390 - // Only update if user data exists
391 - if (!empty($user) && is_array($user)) {
392 - // Set disconnection flag
393 - $user['is_disconnected'] = true;
394 -
395 - // Save updated user data
396 - $options->set('user', $user);
397 -
398 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
399 - self::log('Site disconnection detected: SiteNotConnected status');
400 - }
401 - }
402 -
403 - // Send header for frontend detection
404 - if (!headers_sent()) {
405 - header('X-Templately-Disconnected: true');
406 - }
407 -
408 - return true;
409 - } catch (\Exception $e) {
410 - // Log error if debug logging is enabled
411 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
412 - self::log('Error updating site disconnection status: ' . $e->getMessage());
413 - }
414 - }
415 - }
416 -
417 - return false;
418 - }
419 -
420 - /**
421 - * Clear site disconnection status
422 - *
423 - * Called after successful site migration to reset the disconnection flag.
424 - *
425 - * @return void
426 - */
427 - public static function clear_site_disconnection() {
428 - try {
429 - $options = Options::get_instance();
430 - $user = $options->get('user');
431 -
432 - if (!empty($user) && is_array($user)) {
433 - $user['site_url'] = base64_encode( home_url('/') );
434 - $user['is_disconnected'] = false;
435 - $options->set('user', $user);
436 -
437 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
438 - self::log('Site disconnection status cleared and URL updated.');
439 - }
440 - }
441 - } catch (\Exception $e) {
442 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
443 - self::log('Error clearing site disconnection status: ' . $e->getMessage());
444 - }
445 - }
446 - }
447 -
448 - /**
449 217 * API Error Formatter
450 218 *
451 219 * @param int $error_code
452 220 * @param mixed $error_message
@@ -540,59 +308,21 @@
540 308
541 309 /**
542 310 * Printing Error Logs in debug.log file.
543 311 *
544 - * @param mixed $log The data to log
545 - * @param string $context Optional context for categorizing log entries
546 - * @param string $level Optional log level (debug, info, warning, error)
312 + * @param mixed $log
547 313 * @return void
548 314 */
549 - public static function log($log, $context = '', $level = 'info') {
550 - // Allow complete override of logging behavior
551 - $override_result = apply_filters('templately_log_override', null, $log, $context, $level);
552 - if ($override_result !== null) {
553 - return;
315 + public static function log($log) {
316 + if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
317 + if (is_array($log) || is_object($log)) {
318 + error_log(print_r($log, true));
319 + } else {
320 + error_log($log ?: '');
321 + }
554 322 }
555 -
556 - // Only log if WP_DEBUG_LOG is enabled
557 - if (!defined('WP_DEBUG_LOG') || !WP_DEBUG_LOG) {
558 - return;
559 - }
560 -
561 - // Format the log message
562 - $formatted_message = self::format_log_message($log, $context, $level);
563 -
564 - // Write to error log
565 - error_log($formatted_message);
566 323 }
567 324
568 - /**
569 - * Format log message with context and level
570 - *
571 - * @param mixed $log The data to log
572 - * @param string $context Context for categorizing log entries
573 - * @param string $level Log level
574 - * @return string Formatted log message
575 - */
576 - private static function format_log_message($log, $context = '', $level = 'info') {
577 - // Convert arrays and objects to readable format
578 - if (is_array($log) || is_object($log)) {
579 - $log_content = print_r($log, true);
580 - } else {
581 - $log_content = (string) ($log ?: '');
582 - }
583 -
584 - // Build the formatted message
585 - $timestamp = current_time('Y-m-d H:i:s');
586 - $level_upper = strtoupper($level);
587 -
588 - if (!empty($context)) {
589 - return "[{$timestamp}] [{$level_upper}] [{$context}] {$log_content}";
590 - } else {
591 - return "[{$timestamp}] [{$level_upper}] {$log_content}";
592 - }
593 - }
594 -
595 325 public static function should_flush() {
596 326 if (isset($_REQUEST['is_lightspeed']) && $_REQUEST['is_lightspeed'] === 'true') {
597 327 return false;
598 328 }
@@ -707,9 +437,9 @@
707 437 */
708 438 public static function enable_elementor_container() {
709 439 if (class_exists('Elementor\Plugin')) {
710 440 $control_name = Plugin::instance()->experiments->get_feature_option_key('container');
711 - if (get_option($control_name) !== 'active') {
441 + if (get_option($control_name) === 'inactive') {
712 442 update_option($control_name, 'active');
713 443 return true;
714 444 }
715 445 }