PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.6.4
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.6.4
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
templately / includes / Utils / Helper.php

Helper.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.6.4, at includes/Utils/Helper.php

724 lines 21.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Utils;
4
5 use Elementor\Plugin;
6 use Templately\Core\Importer\Utils\Utils;
7 use WP_Error;
8 use WP_REST_Response;
9 use function get_plugins;
10 use function is_plugin_active;
11
12 /**
13 * Utility Helper for Templately
14 *
15 * This class contains some helper functions for easy access.
16 *
17 * @since 1.0.0
18 */
19 class Helper extends Base {
20 /**
21 * Check if development API should be used
22 *
23 * @return bool True if development API should be used
24 */
25 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' );
28 }
29
30 /**
31 * Get installed WordPress Plugin List
32 * @return array
33 */
34 public static function get_plugins() {
35 if (! function_exists('get_plugins')) {
36 require_once ABSPATH . 'wp-admin/includes/plugin.php';
37 }
38 return get_plugins();
39 }
40 public static function is_plugins_installed($plugin_file) {
41 $_plugins = self::get_plugins();
42 $is_installed = isset($_plugins[$plugin_file]);
43 return $is_installed;
44 }
45
46 /**
47 * Get installed WordPress Plugin List
48 * @return boolean
49 */
50 public static function is_plugin_active($plugin) {
51 if (! function_exists('is_plugin_active')) {
52 require_once ABSPATH . 'wp-admin/includes/plugin.php';
53 }
54 return is_plugin_active($plugin);
55 }
56
57 /**
58 * Collect IP from request.
59 *
60 * @return string
61 */
62 public static function get_ip() {
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;
70 }
71
72 return sanitize_text_field($ip);
73 }
74
75 /**
76 * Get views for front-end display
77 *
78 * @param string $name it will be file name only from the view's folder.
79 * @param array $data
80 * @return void
81 */
82 public static function views($name, $data = []) {
83 extract($data);
84 $helper = self::class;
85 $file = TEMPLATELY_PATH . 'views/' . $name . '.php';
86
87 if (is_readable($file)) {
88 include_once $file;
89 }
90 }
91
92 /**
93 * Get API URL for Templately endpoints
94 *
95 * @param string $endpoint API endpoint path (e.g., 'v2/import/pack/123')
96 * @return string Complete API URL
97 */
98 public static function get_api_url($endpoint): string {
99 $base_url = self::is_dev_api() ? 'https://app.templately.dev' : 'https://app.templately.com';
100
101 /**
102 * Filter the base URL for development API
103 *
104 * @since 3.5.0
105 * @param string $base_url The default base URL
106 */
107 $base_url = apply_filters('templately_dev_api_base_url', $base_url);
108
109 return "{$base_url}/api/{$endpoint}";
110 }
111
112 /**
113 * Make a unified API request to Templately API
114 *
115 * @param string $method HTTP method (GET or POST)
116 * @param string $api_url Complete API URL
117 * @param array $body Request body data (for POST requests)
118 * @param array $extra_headers Additional headers beyond the standard ones
119 * @param int $timeout Request timeout in seconds (default: 30)
120 * @return array|WP_Error Response array or WP_Error on failure
121 */
122 private static function make_api_request($method, $api_url, $body = [], $extra_headers = [], $timeout = 30) {
123 $api_key = Options::get_instance()->get('api_key');
124
125 $headers = [
126 'Authorization' => 'Bearer ' . $api_key,
127 'x-templately-ip' => self::get_ip(),
128 'x-templately-url' => home_url('/'),
129 'x-templately-version' => defined( 'TEMPLATELY_VERSION' ) ? constant( 'TEMPLATELY_VERSION' ) : '1.0.0',
130 ];
131
132 // Add Content-Type for POST requests
133 if (strtoupper($method) === 'POST') {
134 $headers['Content-Type'] = 'application/json';
135 }
136
137 // Resolve requested platform: $_REQUEST wins (frontend-supplied), then caller's extra_headers, then default.
138 if ( isset( $_REQUEST['requested_platform'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
139 $extra_headers['x-templately-requested-platform'] = sanitize_text_field( wp_unslash( $_REQUEST['requested_platform'] ) );
140 } elseif ( ! isset( $extra_headers['x-templately-requested-platform'] ) ) {
141 $extra_headers['x-templately-requested-platform'] = 'templately';
142 }
143
144 // Merge additional headers
145 $headers = array_merge($headers, $extra_headers);
146
147 $args = [
148 'timeout' => $timeout,
149 'headers' => $headers,
150 ];
151
152 // Apply filter to allow network admin or other functionality to modify request args
153 $args = apply_filters( 'templately_api_request_params', $args, $method, $api_url );
154
155 // Add body for POST requests
156 if (strtoupper($method) === 'POST') {
157 $args['body'] = is_array($body) ? json_encode($body) : $body;
158 }
159
160 // Make the appropriate request
161 if (strtoupper($method) === 'POST') {
162 $response = wp_remote_post($api_url, $args);
163 } else {
164 $response = wp_remote_get($api_url, $args);
165 }
166
167 // Check for verification header in the response
168 self::check_verification_header($response);
169
170
171 // Check for site disconnection in response body
172 self::check_site_disconnection($response);
173
174 return $response;
175 }
176
177 /**
178 * Make a GET request to Templately API
179 *
180 * @param string $endpoint API endpoint path (e.g., 'v2/import/pack/123')
181 * @param array $query_params Query parameters as key-value pairs
182 * @param array $extra_headers Additional headers beyond the standard ones
183 * @param int $timeout Request timeout in seconds (default: 30)
184 * @return array|WP_Error Response array or WP_Error on failure
185 */
186 public static function make_api_get_request($endpoint, $query_params = [], $extra_headers = [], $timeout = 30) {
187 $api_url = self::get_api_url($endpoint);
188
189 // Add query parameters if provided
190 if (!empty($query_params)) {
191 $api_url = add_query_arg($query_params, $api_url);
192 }
193
194 return self::make_api_request('GET', $api_url, [], $extra_headers, $timeout);
195 }
196
197 /**
198 * Make a POST request to Templately API
199 *
200 * @param string $endpoint API endpoint path (e.g., 'v2/feedback/store')
201 * @param array $body Request body data
202 * @param array $extra_headers Additional headers beyond the standard ones
203 * @param int $timeout Request timeout in seconds (default: 30)
204 * @return array|WP_Error Response array or WP_Error on failure
205 */
206 public static function make_api_post_request($endpoint, $body = [], $extra_headers = [], $timeout = 30) {
207 $api_url = self::get_api_url($endpoint);
208 return self::make_api_request('POST', $api_url, $body, $extra_headers, $timeout);
209 }
210
211 /**
212 * Sanitize Helper
213 *
214 * @param mixed $value
215 * @param string $type
216 *
217 * @return bool|string
218 */
219 public static function sanitize($value, $type = 'text') {
220 switch ($type) {
221 case 'boolean':
222 $sanitized_value = rest_sanitize_boolean($value);
223 break;
224 default:
225 $sanitized_value = sanitize_text_field($value);
226 break;
227 }
228
229 return $sanitized_value;
230 }
231
232 /**
233 * Escape a string for safe embedding inside a GraphQL or JSON string literal.
234 *
235 * GraphQL string escaping rules are identical to JSON string escaping (per the
236 * GraphQL spec), so wp_json_encode() is the authoritative escaper. We strip the
237 * outer quotes it adds and return only the escaped inner content, ready to be
238 * wrapped in your own quote pair.
239 *
240 * Handles pre-encoded JSON: when the caller has already run json_encode() +
241 * wp_slash() on a value (e.g. categories, dependencies in Items.php), the
242 * quotes are already escaped as \" and the string is ready to embed. Calling
243 * wp_json_encode() again would double-escape those backslashes. We detect this
244 * case by checking whether wp_unslash() produces valid JSON, and if so, return
245 * the value directly without further encoding.
246 *
247 * @param string $value Raw string or wp_slash(json_encode()) output.
248 * @return string Escaped string, safe to place between double quotes in GraphQL/JSON.
249 */
250 public static function esc_json_string( $value ) {
251 $value = (string) $value;
252
253 // If wp_slash() was applied to a JSON string upstream, the quotes are
254 // already escaped (e.g. {\"key\":\"val\"}). Detect this by unslashing and
255 // checking for valid JSON — if it matches, the value is already suitable
256 // for embedding in a string literal; return it as-is to avoid doubling backslashes.
257 $unslashed = wp_unslash( $value );
258 if ( $unslashed !== $value ) {
259 $decoded = json_decode( $unslashed, true );
260 if ( json_last_error() === JSON_ERROR_NONE && null !== $decoded ) {
261 return $value;
262 }
263 }
264
265 $encoded = wp_json_encode( $value );
266 // wp_json_encode wraps the value in "...", strip those outer quotes.
267 return substr( $encoded, 1, -1 );
268 }
269
270 /**
271 * Check for X-Templately-Verified header and update user verification status
272 *
273 * @param array|WP_Error $response The HTTP response array from wp_remote_get/wp_remote_post
274 * @return void
275 */
276 public static function check_verification_header($response) {
277 // Only process if response is not a WP_Error and contains headers
278 if (is_wp_error($response)) {
279 return;
280 }
281
282 // Retrieve the X-Templately-Verified header
283 $verification_header = wp_remote_retrieve_header($response, 'X-Templately-Verified');
284
285 // Check if header exists and has a truthy value
286 if (!empty($verification_header) && filter_var($verification_header, FILTER_VALIDATE_BOOLEAN)) {
287 try {
288 // Get current user data
289 $options = Options::get_instance();
290 $user = $options->get('user');
291
292 // Only update if user data exists and is not already verified
293 if (!empty($user) && is_array($user) && empty($user['is_verified'])) {
294 // Set verification flag
295 $user['is_verified'] = true;
296
297 // Save updated user data
298 $options->set('user', $user);
299
300 }
301
302 if (!empty($user['is_verified'])){
303 if(!headers_sent()){
304 header( 'X-Templately-Verified: true' );
305 if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
306 self::log('User verification status already updated via X-Templately-Verified header');
307 }
308 }
309
310 return true;
311 }
312 } catch (\Exception $e) {
313 // Log error if debug logging is enabled
314 if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
315 self::log('Error updating user verification status: ' . $e->getMessage());
316 }
317 }
318 }
319
320 return false;
321 }
322
323 /**
324 * Check for site disconnection status in API response body
325 *
326 * Detects SiteNotConnected errors and updates user disconnection status.
327 * Sends X-Templately-Disconnected header for frontend detection.
328 *
329 *
330 * @param array|WP_Error|mixed $response The response object or body array
331 * @return bool True if site is disconnected, false otherwise
332 */
333 public static function check_site_disconnection($response) {
334 if (is_wp_error($response)) {
335 return false;
336 }
337
338 $response_body = $response;
339
340 // If it's a raw WP response array with body, decode it
341 if (is_array($response) && isset($response['body']) && is_string($response['body'])) {
342 $response_body = json_decode(wp_remote_retrieve_body($response), true);
343 }
344
345 // Check if response body indicates site disconnection
346 if (!is_array($response_body)) {
347 return false;
348 }
349
350 $status = $response_body['status'] ?? null;
351 $status_text = $response_body['statusText'] ?? null;
352
353 // Check for SiteNotConnected error
354 if ($status === 'error' && $status_text === 'SiteNotConnected') {
355 try {
356 // Get current user data
357 $options = Options::get_instance();
358 $user = $options->get('user');
359
360 // Only update if user data exists
361 if (!empty($user) && is_array($user)) {
362 // Set disconnection flag
363 $user['is_disconnected'] = true;
364
365 // Save updated user data
366 $options->set('user', $user);
367
368 if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
369 self::log('Site disconnection detected: SiteNotConnected status');
370 }
371 }
372
373 // Send header for frontend detection
374 if (!headers_sent()) {
375 header('X-Templately-Disconnected: true');
376 }
377
378 return true;
379 } catch (\Exception $e) {
380 // Log error if debug logging is enabled
381 if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
382 self::log('Error updating site disconnection status: ' . $e->getMessage());
383 }
384 }
385 }
386
387 return false;
388 }
389
390 /**
391 * Clear site disconnection status
392 *
393 * Called after successful site migration to reset the disconnection flag.
394 *
395 * @return void
396 */
397 public static function clear_site_disconnection() {
398 try {
399 $options = Options::get_instance();
400 $user = $options->get('user');
401
402 if (!empty($user) && is_array($user)) {
403 $user['site_url'] = base64_encode( home_url('/') );
404 $user['is_disconnected'] = false;
405 $options->set('user', $user);
406
407 if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
408 self::log('Site disconnection status cleared and URL updated.');
409 }
410 }
411 } catch (\Exception $e) {
412 if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
413 self::log('Error clearing site disconnection status: ' . $e->getMessage());
414 }
415 }
416 }
417
418 /**
419 * API Error Formatter
420 *
421 * @param int $error_code
422 * @param mixed $error_message
423 * @param string $endpoint
424 * @param integer $status
425 * @param array $additional_data
426 * @return WP_Error
427 */
428 public static function error($error_code, $error_message, $endpoint = '', $status = 500, $additional_data = []) {
429 $additional_data['status'] = $status;
430 if (! empty($endpoint)) {
431 $additional_data['endpoint'] = $endpoint;
432 }
433 // Add browser padding to avoid browsers not serving small JSON responses
434 $padding_length = 512;
435 $additional_data['browser_padding'] = str_repeat(' ', $padding_length);
436
437 return new WP_Error($error_code, $error_message, $additional_data);
438 }
439
440 /**
441 * API Response Formatter
442 *
443 * @param mixed $data
444 * @return WP_REST_Response
445 */
446 public static function success($data) {
447 return new WP_REST_Response($data, 200);
448 }
449
450 /**
451 * Normalize Favourites Data
452 *
453 * @param array $favourites
454 * @param array $_favourites
455 * @param boolean $undo
456 *
457 * @return array
458 */
459 public function normalizeFavourites($favourites, $_favourites = [], $undo = false) {
460 if ($undo) {
461 $_favourites[$favourites['type']] = array_values(array_filter($_favourites[$favourites['type']], function ($item) use ($favourites) {
462 return $item != $favourites['id'];
463 }));
464 return $_favourites;
465 }
466
467 array_map(function ($item) use (&$_favourites) {
468 if (! is_null($item)) {
469 $item = (array) $item;
470 if (isset($_favourites[$item['type']])) {
471 $_favourites[$item['type']][] = $item['id'];
472 } else {
473 $_favourites[$item['type']] = [$item['id']];
474 }
475 }
476 return $_favourites;
477 }, $favourites);
478
479 return $_favourites;
480 }
481
482 public function normalizeReviews($favourites, $_favourites = [], $undo = false) {
483 array_map(function ($item) use (&$_favourites) {
484 if (! is_null($item)) {
485 $item = (array) $item;
486 if (!isset($_favourites[$item['type']])) {
487 $_favourites[$item['type']] = [];
488 }
489 $_favourites[$item['type']][$item['type_id']] = $item['rating'];
490 }
491 return $_favourites;
492 }, $favourites);
493
494 return $_favourites;
495 }
496
497 /**
498 * Trigger Error
499 *
500 * @param object $triggered_by
501 * @return void
502 */
503 public static function trigger_error($triggered_by, $method = 'get_instance') {
504 $class = get_class($triggered_by);
505 $trace = debug_backtrace(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
506 $file = $trace[0]['file'];
507 $line = $trace[0]['line'];
508 trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
509 }
510
511 /**
512 * Printing Error Logs in debug.log file.
513 *
514 * @param mixed $log The data to log
515 * @param string $context Optional context for categorizing log entries
516 * @param string $level Optional log level (debug, info, warning, error)
517 * @return void
518 */
519 public static function log($log, $context = '', $level = 'info') {
520 // Allow complete override of logging behavior
521 $override_result = apply_filters('templately_log_override', null, $log, $context, $level);
522 if ($override_result !== null) {
523 return;
524 }
525
526 // Only log if WP_DEBUG_LOG is enabled
527 if (!defined('WP_DEBUG_LOG') || !WP_DEBUG_LOG) {
528 return;
529 }
530
531 // Format the log message
532 $formatted_message = self::format_log_message($log, $context, $level);
533
534 // Write to error log
535 error_log($formatted_message);
536 }
537
538 /**
539 * Format log message with context and level
540 *
541 * @param mixed $log The data to log
542 * @param string $context Context for categorizing log entries
543 * @param string $level Log level
544 * @return string Formatted log message
545 */
546 private static function format_log_message($log, $context = '', $level = 'info') {
547 // Convert arrays and objects to readable format
548 if (is_array($log) || is_object($log)) {
549 $log_content = print_r($log, true);
550 } else {
551 $log_content = (string) ($log ?: '');
552 }
553
554 // Build the formatted message
555 $timestamp = current_time('Y-m-d H:i:s');
556 $level_upper = strtoupper($level);
557
558 if (!empty($context)) {
559 return "[{$timestamp}] [{$level_upper}] [{$context}] {$log_content}";
560 } else {
561 return "[{$timestamp}] [{$level_upper}] {$log_content}";
562 }
563 }
564
565 public static function should_flush() {
566 if (isset($_REQUEST['is_lightspeed']) && $_REQUEST['is_lightspeed'] === 'true') {
567 return false;
568 }
569 return (!defined('TEMPLATELY_IGNORE_FLUSH_ALL') || !TEMPLATELY_IGNORE_FLUSH_ALL) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') === false;
570 }
571
572 public static function get_block_by_name($blocks, $search) {
573 $queue = $blocks;
574
575 while (!empty($queue)) {
576 $current_block = array_shift($queue);
577
578 if ($search === $current_block['blockName']) {
579 return $current_block;
580 }
581
582 if (isset($current_block['innerBlocks'])) {
583 // Add nested blocks to the end of the queue for processing
584 $queue = array_merge($queue, $current_block['innerBlocks']);
585 }
586 }
587
588 return false;
589 }
590
591 /**
592 * Only checks if user can install/activate plugins
593 *
594 * @param [type] $cap
595 * @param [type] ...$args
596 * @return void
597 */
598 public static function current_user_can($cap, ...$args) {
599 $user = wp_get_current_user();
600
601 // Multisite super admin has all caps by definition, Unless specifically denied.
602 if (is_multisite() && is_super_admin($user->ID)) {
603 return true;
604 }
605
606 $caps = map_meta_cap($cap, $user->ID, ...$args);
607
608 switch ($cap) {
609 case 'install_plugins':
610 case 'upload_plugins':
611 $caps = ['install_plugins'];
612 break;
613 case 'install_themes':
614 case 'upload_themes':
615 $caps = ['install_themes'];
616 break;
617 case 'activate_plugins':
618 case 'deactivate_plugins':
619 case 'activate_plugin':
620 case 'deactivate_plugin':
621 $caps = ['activate_plugins'];
622 break;
623 default:
624 break;
625 }
626
627 // Maintain BC for the argument passed to the "user_has_cap" filter.
628 $args = array_merge(array($cap, $user->ID), $args);
629
630 /**
631 * See WP_User::has_cap() for description.
632 */
633 $capabilities = apply_filters('user_has_cap', $user->allcaps, $caps, $args, $user);
634
635 // Everyone is allowed to exist.
636 $capabilities['exist'] = true;
637
638 // Nobody is allowed to do things they are not allowed to do.
639 unset($capabilities['do_not_allow']);
640
641 // Must have ALL requested caps.
642 foreach ((array) $caps as $cap) {
643 if (empty($capabilities[$cap])) {
644 return false;
645 }
646 }
647
648 return true;
649 }
650
651 /**
652 * Calculates the elapsed time and checks if it is close to the maximum execution time.
653 * Returns true if the script should exit to avoid exceeding the limit.
654 *
655 * @return bool True if the script should exit, false otherwise.
656 */
657 public static function fsi_should_exit() {
658 if (defined('TEMPLATELY_START_TIME') && ini_get('max_execution_time')) {
659 $max_time = ini_get('max_execution_time');
660 $elapsed = microtime(true) - TEMPLATELY_START_TIME;
661 $delay = max(5, $max_time * 20 / 100);
662
663 // Check if elapsed time is close to max execution time
664 if ($max_time - $elapsed <= $delay) {
665 return ['max_time' => $max_time, 'elapsed' => $elapsed, 'delay' => $delay];
666 }
667 }
668 return false;
669 }
670
671 /**
672 * Enable Elementor Container
673 * This function will enable the Elementor Container feature.
674 * Without this feature, some of the templates may not work properly.
675 *
676 * @return boolean
677 */
678 public static function enable_elementor_container() {
679 if (class_exists('Elementor\Plugin')) {
680 $control_name = Plugin::instance()->experiments->get_feature_option_key('container');
681 if (get_option($control_name) !== 'active') {
682 update_option($control_name, 'active');
683 return true;
684 }
685 }
686 return false;
687 }
688
689 /**
690 * Undocumented function
691 *
692 * @param [type] $args
693 * @param [type] $defaults
694 * @return array
695 */
696 public static function recursive_wp_parse_args($args, $defaults) {
697 $args = (array) $args;
698 $defaults = (array) $defaults;
699 $r = $defaults;
700 foreach ($args as $key => $value) {
701 if (is_array($value) && isset($r[$key])) {
702 // also handle numeric array. if both $value and $r[ $key ] are numeric array. wp_is_numeric_array()
703 if (wp_is_numeric_array($value) && wp_is_numeric_array($r[$key])) {
704 foreach ($value as $k => $v) {
705 if (!in_array($v, $r[$key])) {
706 if (!isset($r[$key][$k])) {
707 $r[$key][$k] = $v;
708 } else {
709 $r[$key][] = $v;
710 }
711 }
712 }
713 } else {
714 $r[$key] = self::recursive_wp_parse_args($value, $r[$key]);
715 }
716 } else {
717 $r[$key] = $value;
718 }
719 }
720 return $r;
721 }
722
723 }
724