PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.0.1
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.0.1
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 +78 -720 3.7.53.0.1 View file →
@@ -1,15 +1,11 @@
1 1 <?php
2 -
3 2 namespace Templately\Utils;
4 3
5 -use Elementor\Plugin;
6 -use Templately\Core\Importer\Utils\Utils;
7 4 use WP_Error;
8 5 use WP_REST_Response;
9 6 use function get_plugins;
10 7 use function is_plugin_active;
11 -
12 8 /**
13 9 * Utility Helper for Templately
14 10 *
15 11 * This class contains some helper functions for easy access.
@@ -16,251 +12,66 @@
16 12 *
17 13 * @since 1.0.0
18 14 */
19 15 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 - }
16 + /**
17 + * Get installed WordPress Plugin List
18 + * @return array
19 + */
20 + public static function get_plugins(){
21 + if( ! function_exists( 'get_plugins' ) ) {
22 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
23 + }
24 + return get_plugins();
25 + }
29 26
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 - }
27 + /**
28 + * Get installed WordPress Plugin List
29 + * @return boolean
30 + */
31 + public static function is_plugin_active( $plugin ){
32 + if( ! function_exists( 'is_plugin_active' ) ) {
33 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
34 + }
35 + return is_plugin_active( $plugin );
36 + }
45 37
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 - /**
38 + /**
58 39 * Collect IP from request.
59 40 *
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 41 * @return string
66 42 */
67 43 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 - }
73 -
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';
88 - }
89 -
90 - /**
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 - * Get views for front-end display
102 - *
103 - * @param string $name it will be file name only from the view's folder.
104 - * @param array $data
105 - * @return void
106 - */
107 - public static function views($name, $data = []) {
108 - extract($data);
109 - $helper = self::class;
110 - $file = TEMPLATELY_PATH . 'views/' . $name . '.php';
111 -
112 - if (is_readable($file)) {
113 - include_once $file;
114 - }
115 - }
116 -
117 - /**
118 - * A URL on the public Templately website, honouring the dev domain.
119 - *
120 - * The PHP counterpart of `react-src/utils/helper.js#webURL`. A hard-coded
121 - * `https://templately.com/...` sends a site running against the dev API to
122 - * the live site, where its account does not exist — so build every out-link
123 - * through this instead.
124 - *
125 - * Note this is the *website*, not the API host `get_api_url()` builds.
126 - *
127 - * @param string $path Path with or without a leading slash.
128 - * @param array $args Query args (utm_* etc).
129 - * @return string
130 - */
131 - public static function web_url( string $path = '', array $args = [] ): string {
132 - $base_url = self::is_dev_api() ? 'https://templately.dev' : 'https://templately.com';
133 - $url = $base_url . '/' . ltrim( $path, '/' );
134 -
135 - return empty( $args ) ? $url : add_query_arg( $args, $url );
136 - }
137 -
138 - /**
139 - * Get API URL for Templately endpoints
140 - *
141 - * @param string $endpoint API endpoint path (e.g., 'v2/import/pack/123')
142 - * @return string Complete API URL
143 - */
144 - public static function get_api_url($endpoint): string {
145 - $base_url = self::is_dev_api() ? 'https://app.templately.dev' : 'https://app.templately.com';
146 -
147 - /**
148 - * Filter the base URL for development API
149 - *
150 - * @since 3.5.0
151 - * @param string $base_url The default base URL
152 - */
153 - $base_url = apply_filters('templately_dev_api_base_url', $base_url);
154 -
155 - return "{$base_url}/api/{$endpoint}";
156 - }
157 -
158 - /**
159 - * Make a unified API request to Templately API
160 - *
161 - * @param string $method HTTP method (GET or POST)
162 - * @param string $api_url Complete API URL
163 - * @param array $body Request body data (for POST requests)
164 - * @param array $extra_headers Additional headers beyond the standard ones
165 - * @param int $timeout Request timeout in seconds (default: 30)
166 - * @return array|WP_Error Response array or WP_Error on failure
167 - */
168 - private static function make_api_request($method, $api_url, $body = [], $extra_headers = [], $timeout = 30) {
169 - $api_key = Options::get_instance()->get('api_key');
170 -
171 - $headers = [
172 - 'Authorization' => 'Bearer ' . $api_key,
173 - 'x-templately-ip' => self::get_ip(),
174 - 'x-templately-url' => home_url('/'),
175 - 'x-templately-version' => defined( 'TEMPLATELY_VERSION' ) ? constant( 'TEMPLATELY_VERSION' ) : '1.0.0',
176 - // Force JSON responses so the cloud returns JSON errors instead of an HTML
177 - // error page (which json_decode() cannot parse). Binary/XML downloads
178 - // (zip pack, attachment WXR) use their own wp_remote_* calls and bypass
179 - // this helper, so they are unaffected. Callers can override via $extra_headers.
180 - 'Accept' => 'application/json',
181 - ];
182 -
183 - // Add Content-Type for POST requests
184 - if (strtoupper($method) === 'POST') {
185 - $headers['Content-Type'] = 'application/json';
186 - }
187 -
188 - // Resolve requested platform: $_REQUEST wins (frontend-supplied), then caller's extra_headers, then default.
189 - if ( isset( $_REQUEST['requested_platform'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
190 - $extra_headers['x-templately-requested-platform'] = sanitize_text_field( wp_unslash( $_REQUEST['requested_platform'] ) );
191 - } elseif ( ! isset( $extra_headers['x-templately-requested-platform'] ) ) {
192 - $extra_headers['x-templately-requested-platform'] = 'templately';
193 - }
194 -
195 - // Merge additional headers
196 - $headers = array_merge($headers, $extra_headers);
197 -
198 - $args = [
199 - 'timeout' => $timeout,
200 - 'headers' => $headers,
201 - ];
202 -
203 - // Apply filter to allow network admin or other functionality to modify request args
204 - $args = apply_filters( 'templately_api_request_params', $args, $method, $api_url );
205 -
206 - // Add body for POST requests
207 - if (strtoupper($method) === 'POST') {
208 - $args['body'] = is_array($body) ? json_encode($body) : $body;
209 - }
210 -
211 - // Make the appropriate request
212 - if (strtoupper($method) === 'POST') {
213 - $response = wp_remote_post($api_url, $args);
44 + $ip = '127.0.0.1'; // Local IP
45 + if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
46 + $ip = $_SERVER['HTTP_CLIENT_IP'];
47 + } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
48 + $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
214 49 } else {
215 - $response = wp_remote_get($api_url, $args);
50 + $ip = ! empty( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : $ip;
216 51 }
217 52
218 - // Check for verification header in the response
219 - self::check_verification_header($response);
220 -
221 -
222 - // Check for site disconnection in response body
223 - self::check_site_disconnection($response);
224 -
225 - return $response;
53 + return sanitize_text_field( $ip );
226 54 }
227 55
228 - /**
229 - * Make a GET request to Templately API
230 - *
231 - * @param string $endpoint API endpoint path (e.g., 'v2/import/pack/123')
232 - * @param array $query_params Query parameters as key-value pairs
233 - * @param array $extra_headers Additional headers beyond the standard ones
234 - * @param int $timeout Request timeout in seconds (default: 30)
235 - * @return array|WP_Error Response array or WP_Error on failure
236 - */
237 - public static function make_api_get_request($endpoint, $query_params = [], $extra_headers = [], $timeout = 30) {
238 - $api_url = self::get_api_url($endpoint);
56 + /**
57 + * Get views for front-end display
58 + *
59 + * @param string $name it will be file name only from the view's folder.
60 + * @param array $data
61 + * @return void
62 + */
63 + public static function views( $name, $data = [] ){
64 + extract( $data );
65 + $helper = self::class;
66 + $file = TEMPLATELY_PATH . 'views/' . $name . '.php';
239 67
240 - // Add query parameters if provided
241 - if (!empty($query_params)) {
242 - $api_url = add_query_arg($query_params, $api_url);
243 - }
68 + if( is_readable( $file ) ) {
69 + include_once $file;
70 + }
71 + }
244 72
245 - return self::make_api_request('GET', $api_url, [], $extra_headers, $timeout);
246 - }
247 -
248 73 /**
249 - * Make a POST request to Templately API
250 - *
251 - * @param string $endpoint API endpoint path (e.g., 'v2/feedback/store')
252 - * @param array $body Request body data
253 - * @param array $extra_headers Additional headers beyond the standard ones
254 - * @param int $timeout Request timeout in seconds (default: 30)
255 - * @return array|WP_Error Response array or WP_Error on failure
256 - */
257 - public static function make_api_post_request($endpoint, $body = [], $extra_headers = [], $timeout = 30) {
258 - $api_url = self::get_api_url($endpoint);
259 - return self::make_api_request('POST', $api_url, $body, $extra_headers, $timeout);
260 - }
261 -
262 - /**
263 74 * Sanitize Helper
264 75 *
265 76 * @param mixed $value
266 77 * @param string $type
@@ -266,15 +77,15 @@
266 77 * @param string $type
267 78 *
268 79 * @return bool|string
269 80 */
270 - public static function sanitize($value, $type = 'text') {
271 - switch ($type) {
81 + public static function sanitize( $value, $type = 'text' ){
82 + switch ( $type ) {
272 83 case 'boolean':
273 - $sanitized_value = rest_sanitize_boolean($value);
84 + $sanitized_value = rest_sanitize_boolean( $value );
274 85 break;
275 86 default:
276 - $sanitized_value = sanitize_text_field($value);
87 + $sanitized_value = sanitize_text_field( $value );
277 88 break;
278 89 }
279 90
280 91 return $sanitized_value;
@@ -280,194 +91,8 @@
280 91 return $sanitized_value;
281 92 }
282 93
283 94 /**
284 - * Escape a string for safe embedding inside a GraphQL or JSON string literal.
285 - *
286 - * GraphQL string escaping rules are identical to JSON string escaping (per the
287 - * GraphQL spec), so wp_json_encode() is the authoritative escaper. We strip the
288 - * outer quotes it adds and return only the escaped inner content, ready to be
289 - * wrapped in your own quote pair.
290 - *
291 - * Handles pre-encoded JSON: when the caller has already run json_encode() +
292 - * wp_slash() on a value (e.g. categories, dependencies in Items.php), the
293 - * quotes are already escaped as \" and the string is ready to embed. Calling
294 - * wp_json_encode() again would double-escape those backslashes. We detect this
295 - * case by checking whether wp_unslash() produces valid JSON, and if so, return
296 - * the value directly without further encoding.
297 - *
298 - * @param string $value Raw string or wp_slash(json_encode()) output.
299 - * @return string Escaped string, safe to place between double quotes in GraphQL/JSON.
300 - */
301 - public static function esc_json_string( $value ) {
302 - $value = (string) $value;
303 -
304 - // If wp_slash() was applied to a JSON string upstream, the quotes are
305 - // already escaped (e.g. {\"key\":\"val\"}). Detect this by unslashing and
306 - // checking for valid JSON — if it matches, the value is already suitable
307 - // for embedding in a string literal; return it as-is to avoid doubling backslashes.
308 - $unslashed = wp_unslash( $value );
309 - if ( $unslashed !== $value ) {
310 - $decoded = json_decode( $unslashed, true );
311 - if ( json_last_error() === JSON_ERROR_NONE && null !== $decoded ) {
312 - return $value;
313 - }
314 - }
315 -
316 - $encoded = wp_json_encode( $value );
317 - // wp_json_encode wraps the value in "...", strip those outer quotes.
318 - return substr( $encoded, 1, -1 );
319 - }
320 -
321 - /**
322 - * Check for X-Templately-Verified header and update user verification status
323 - *
324 - * @param array|WP_Error $response The HTTP response array from wp_remote_get/wp_remote_post
325 - * @return void
326 - */
327 - public static function check_verification_header($response) {
328 - // Only process if response is not a WP_Error and contains headers
329 - if (is_wp_error($response)) {
330 - return;
331 - }
332 -
333 - // Retrieve the X-Templately-Verified header
334 - $verification_header = wp_remote_retrieve_header($response, 'X-Templately-Verified');
335 -
336 - // Check if header exists and has a truthy value
337 - if (!empty($verification_header) && filter_var($verification_header, FILTER_VALIDATE_BOOLEAN)) {
338 - try {
339 - // Get current user data
340 - $options = Options::get_instance();
341 - $user = $options->get('user');
342 -
343 - // Only update if user data exists and is not already verified
344 - if (!empty($user) && is_array($user) && empty($user['is_verified'])) {
345 - // Set verification flag
346 - $user['is_verified'] = true;
347 -
348 - // Save updated user data
349 - $options->set('user', $user);
350 -
351 - }
352 -
353 - if (!empty($user['is_verified'])){
354 - if(!headers_sent()){
355 - header( 'X-Templately-Verified: true' );
356 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
357 - self::log('User verification status already updated via X-Templately-Verified header');
358 - }
359 - }
360 -
361 - return true;
362 - }
363 - } catch (\Exception $e) {
364 - // Log error if debug logging is enabled
365 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
366 - self::log('Error updating user verification status: ' . $e->getMessage());
367 - }
368 - }
369 - }
370 -
371 - return false;
372 - }
373 -
374 - /**
375 - * Check for site disconnection status in API response body
376 - *
377 - * Detects SiteNotConnected errors and updates user disconnection status.
378 - * Sends X-Templately-Disconnected header for frontend detection.
379 - *
380 - *
381 - * @param array|WP_Error|mixed $response The response object or body array
382 - * @return bool True if site is disconnected, false otherwise
383 - */
384 - public static function check_site_disconnection($response) {
385 - if (is_wp_error($response)) {
386 - return false;
387 - }
388 -
389 - $response_body = $response;
390 -
391 - // If it's a raw WP response array with body, decode it
392 - if (is_array($response) && isset($response['body']) && is_string($response['body'])) {
393 - $response_body = json_decode(wp_remote_retrieve_body($response), true);
394 - }
395 -
396 - // Check if response body indicates site disconnection
397 - if (!is_array($response_body)) {
398 - return false;
399 - }
400 -
401 - $status = $response_body['status'] ?? null;
402 - $status_text = $response_body['statusText'] ?? null;
403 -
404 - // Check for SiteNotConnected error
405 - if ($status === 'error' && $status_text === 'SiteNotConnected') {
406 - try {
407 - // Get current user data
408 - $options = Options::get_instance();
409 - $user = $options->get('user');
410 -
411 - // Only update if user data exists
412 - if (!empty($user) && is_array($user)) {
413 - // Set disconnection flag
414 - $user['is_disconnected'] = true;
415 -
416 - // Save updated user data
417 - $options->set('user', $user);
418 -
419 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
420 - self::log('Site disconnection detected: SiteNotConnected status');
421 - }
422 - }
423 -
424 - // Send header for frontend detection
425 - if (!headers_sent()) {
426 - header('X-Templately-Disconnected: true');
427 - }
428 -
429 - return true;
430 - } catch (\Exception $e) {
431 - // Log error if debug logging is enabled
432 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
433 - self::log('Error updating site disconnection status: ' . $e->getMessage());
434 - }
435 - }
436 - }
437 -
438 - return false;
439 - }
440 -
441 - /**
442 - * Clear site disconnection status
443 - *
444 - * Called after successful site migration to reset the disconnection flag.
445 - *
446 - * @return void
447 - */
448 - public static function clear_site_disconnection() {
449 - try {
450 - $options = Options::get_instance();
451 - $user = $options->get('user');
452 -
453 - if (!empty($user) && is_array($user)) {
454 - $user['site_url'] = base64_encode( home_url('/') );
455 - $user['is_disconnected'] = false;
456 - $options->set('user', $user);
457 -
458 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
459 - self::log('Site disconnection status cleared and URL updated.');
460 - }
461 - }
462 - } catch (\Exception $e) {
463 - if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
464 - self::log('Error clearing site disconnection status: ' . $e->getMessage());
465 - }
466 - }
467 - }
468 -
469 - /**
470 95 * API Error Formatter
471 96 *
472 97 * @param int $error_code
473 98 * @param mixed $error_message
@@ -475,18 +100,15 @@
475 100 * @param integer $status
476 101 * @param array $additional_data
477 102 * @return WP_Error
478 103 */
479 - public static function error($error_code, $error_message, $endpoint = '', $status = 500, $additional_data = []) {
104 + public static function error( $error_code, $error_message, $endpoint = '', $status = 500, $additional_data = [] ) {
480 105 $additional_data['status'] = $status;
481 - if (! empty($endpoint)) {
106 + if ( ! empty( $endpoint ) ) {
482 107 $additional_data['endpoint'] = $endpoint;
483 108 }
484 - // Add browser padding to avoid browsers not serving small JSON responses
485 - $padding_length = 512;
486 - $additional_data['browser_padding'] = str_repeat(' ', $padding_length);
487 109
488 - return new WP_Error($error_code, $error_message, $additional_data);
110 + return new WP_Error( $error_code, $error_message, $additional_data );
489 111 }
490 112
491 113 /**
492 114 * API Response Formatter
@@ -493,10 +115,10 @@
493 115 *
494 116 * @param mixed $data
495 117 * @return WP_REST_Response
496 118 */
497 - public static function success($data) {
498 - return new WP_REST_Response($data, 200);
119 + public static function success( $data ) {
120 + return new WP_REST_Response( $data, 200 );
499 121 }
500 122
501 123 /**
502 124 * Normalize Favourites Data
@@ -506,42 +128,42 @@
506 128 * @param boolean $undo
507 129 *
508 130 * @return array
509 131 */
510 - public function normalizeFavourites($favourites, $_favourites = [], $undo = false) {
511 - if ($undo) {
512 - $_favourites[$favourites['type']] = array_values(array_filter($_favourites[$favourites['type']], function ($item) use ($favourites) {
132 + public function normalizeFavourites( $favourites, $_favourites = [], $undo = false ){
133 + if( $undo ) {
134 + $_favourites[ $favourites['type'] ] = array_values(array_filter( $_favourites[ $favourites['type'] ], function( $item ) use( $favourites ) {
513 135 return $item != $favourites['id'];
514 - }));
136 + } ));
515 137 return $_favourites;
516 138 }
517 139
518 - array_map(function ($item) use (&$_favourites) {
519 - if (! is_null($item)) {
140 + array_map( function( $item ) use ( &$_favourites) {
141 + if( ! is_null( $item ) ) {
520 142 $item = (array) $item;
521 - if (isset($_favourites[$item['type']])) {
522 - $_favourites[$item['type']][] = $item['id'];
143 + if ( isset( $_favourites[ $item['type'] ] ) ){
144 + $_favourites[ $item['type'] ][] = $item['id'];
523 145 } else {
524 - $_favourites[$item['type']] = [$item['id']];
146 + $_favourites[ $item['type'] ] = [ $item['id'] ];
525 147 }
526 148 }
527 149 return $_favourites;
528 - }, $favourites);
150 + }, $favourites );
529 151
530 152 return $_favourites;
531 153 }
532 154
533 - public function normalizeReviews($favourites, $_favourites = [], $undo = false) {
534 - array_map(function ($item) use (&$_favourites) {
535 - if (! is_null($item)) {
155 + public function normalizeReviews( $favourites, $_favourites = [], $undo = false ){
156 + array_map( function( $item ) use ( &$_favourites) {
157 + if( ! is_null( $item ) ) {
536 158 $item = (array) $item;
537 - if (!isset($_favourites[$item['type']])) {
538 - $_favourites[$item['type']] = [];
159 + if ( !isset( $_favourites[ $item['type'] ] ) ){
160 + $_favourites[ $item['type'] ] = [];
539 161 }
540 - $_favourites[$item['type']][$item['type_id']] = $item['rating'];
162 + $_favourites[ $item['type'] ][$item['type_id']] = $item['rating'];
541 163 }
542 164 return $_favourites;
543 - }, $favourites);
165 + }, $favourites );
544 166
545 167 return $_favourites;
546 168 }
547 169
@@ -550,10 +172,10 @@
550 172 *
551 173 * @param object $triggered_by
552 174 * @return void
553 175 */
554 - public static function trigger_error($triggered_by, $method = 'get_instance') {
555 - $class = get_class($triggered_by);
176 + public static function trigger_error( $triggered_by, $method = 'get_instance' ){
177 + $class = get_class( $triggered_by );
556 178 $trace = debug_backtrace(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
557 179 $file = $trace[0]['file'];
558 180 $line = $trace[0]['line'];
559 181 trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
@@ -561,281 +183,17 @@
561 183
562 184 /**
563 185 * Printing Error Logs in debug.log file.
564 186 *
565 - * @param mixed $log The data to log
566 - * @param string $context Optional context for categorizing log entries
567 - * @param string $level Optional log level (debug, info, warning, error)
187 + * @param mixed $log
568 188 * @return void
569 189 */
570 - public static function log($log, $context = '', $level = 'info') {
571 - // Allow complete override of logging behavior
572 - $override_result = apply_filters('templately_log_override', null, $log, $context, $level);
573 - if ($override_result !== null) {
574 - return;
575 - }
576 -
577 - // Only log if WP_DEBUG_LOG is enabled
578 - if (!defined('WP_DEBUG_LOG') || !WP_DEBUG_LOG) {
579 - return;
580 - }
581 -
582 - // Format the log message
583 - $formatted_message = self::format_log_message($log, $context, $level);
584 -
585 - // Write to error log
586 - error_log($formatted_message);
587 - }
588 -
589 - /**
590 - * Format log message with context and level
591 - *
592 - * @param mixed $log The data to log
593 - * @param string $context Context for categorizing log entries
594 - * @param string $level Log level
595 - * @return string Formatted log message
596 - */
597 - private static function format_log_message($log, $context = '', $level = 'info') {
598 - // Convert arrays and objects to readable format
599 - if (is_array($log) || is_object($log)) {
600 - $log_content = print_r($log, true);
601 - } else {
602 - $log_content = (string) ($log ?: '');
603 - }
604 -
605 - // Build the formatted message
606 - $timestamp = current_time('Y-m-d H:i:s');
607 - $level_upper = strtoupper($level);
608 -
609 - if (!empty($context)) {
610 - return "[{$timestamp}] [{$level_upper}] [{$context}] {$log_content}";
611 - } else {
612 - return "[{$timestamp}] [{$level_upper}] {$log_content}";
613 - }
614 - }
615 -
616 - public static function should_flush() {
617 - if (isset($_REQUEST['is_lightspeed']) && $_REQUEST['is_lightspeed'] === 'true') {
618 - return false;
619 - }
620 - return (!defined('TEMPLATELY_IGNORE_FLUSH_ALL') || !TEMPLATELY_IGNORE_FLUSH_ALL) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') === false;
621 - }
622 -
623 - public static function get_block_by_name($blocks, $search) {
624 - $queue = $blocks;
625 -
626 - while (!empty($queue)) {
627 - $current_block = array_shift($queue);
628 -
629 - if ($search === $current_block['blockName']) {
630 - return $current_block;
631 - }
632 -
633 - if (isset($current_block['innerBlocks'])) {
634 - // Add nested blocks to the end of the queue for processing
635 - $queue = array_merge($queue, $current_block['innerBlocks']);
636 - }
637 - }
638 -
639 - return false;
640 - }
641 -
642 - /**
643 - * Only checks if user can install/activate plugins
644 - *
645 - * @param [type] $cap
646 - * @param [type] ...$args
647 - * @return void
648 - */
649 - public static function current_user_can($cap, ...$args) {
650 - $user = wp_get_current_user();
651 -
652 - // Multisite super admin has all caps by definition, Unless specifically denied.
653 - if (is_multisite() && is_super_admin($user->ID)) {
654 - return true;
655 - }
656 -
657 - $caps = map_meta_cap($cap, $user->ID, ...$args);
658 -
659 - switch ($cap) {
660 - case 'install_plugins':
661 - case 'upload_plugins':
662 - $caps = ['install_plugins'];
663 - break;
664 - case 'install_themes':
665 - case 'upload_themes':
666 - $caps = ['install_themes'];
667 - break;
668 - case 'activate_plugins':
669 - case 'deactivate_plugins':
670 - case 'activate_plugin':
671 - case 'deactivate_plugin':
672 - $caps = ['activate_plugins'];
673 - break;
674 - default:
675 - break;
676 - }
677 -
678 - // Maintain BC for the argument passed to the "user_has_cap" filter.
679 - $args = array_merge(array($cap, $user->ID), $args);
680 -
681 - /**
682 - * See WP_User::has_cap() for description.
683 - */
684 - $capabilities = apply_filters('user_has_cap', $user->allcaps, $caps, $args, $user);
685 -
686 - // Everyone is allowed to exist.
687 - $capabilities['exist'] = true;
688 -
689 - // Nobody is allowed to do things they are not allowed to do.
690 - unset($capabilities['do_not_allow']);
691 -
692 - // Must have ALL requested caps.
693 - foreach ((array) $caps as $cap) {
694 - if (empty($capabilities[$cap])) {
695 - return false;
696 - }
697 - }
698 -
699 - return true;
700 - }
701 -
702 - /**
703 - * Calculates the elapsed time and checks if it is close to the maximum execution time.
704 - * Returns true if the script should exit to avoid exceeding the limit.
705 - *
706 - * @return bool True if the script should exit, false otherwise.
707 - */
708 - public static function fsi_should_exit() {
709 - if (defined('TEMPLATELY_START_TIME') && ini_get('max_execution_time')) {
710 - $max_time = ini_get('max_execution_time');
711 - $elapsed = microtime(true) - TEMPLATELY_START_TIME;
712 - $delay = max(5, $max_time * 20 / 100);
713 -
714 - // Check if elapsed time is close to max execution time
715 - if ($max_time - $elapsed <= $delay) {
716 - return ['max_time' => $max_time, 'elapsed' => $elapsed, 'delay' => $delay];
717 - }
718 - }
719 - return false;
720 - }
721 -
722 - /**
723 - * Enable Elementor Container
724 - * This function will enable the Elementor Container feature.
725 - * Without this feature, some of the templates may not work properly.
726 - *
727 - * @return boolean
728 - */
729 - public static function enable_elementor_container() {
730 - if (class_exists('Elementor\Plugin')) {
731 - $control_name = Plugin::instance()->experiments->get_feature_option_key('container');
732 - if (get_option($control_name) !== 'active') {
733 - update_option($control_name, 'active');
734 - return true;
735 - }
736 - }
737 - return false;
738 - }
739 -
740 - /**
741 - * Undocumented function
742 - *
743 - * @param [type] $args
744 - * @param [type] $defaults
745 - * @return array
746 - */
747 - public static function recursive_wp_parse_args($args, $defaults) {
748 - $args = (array) $args;
749 - $defaults = (array) $defaults;
750 - $r = $defaults;
751 - foreach ($args as $key => $value) {
752 - if (is_array($value) && isset($r[$key])) {
753 - // also handle numeric array. if both $value and $r[ $key ] are numeric array. wp_is_numeric_array()
754 - if (wp_is_numeric_array($value) && wp_is_numeric_array($r[$key])) {
755 - foreach ($value as $k => $v) {
756 - if (!in_array($v, $r[$key])) {
757 - if (!isset($r[$key][$k])) {
758 - $r[$key][$k] = $v;
759 - } else {
760 - $r[$key][] = $v;
761 - }
762 - }
763 - }
764 - } else {
765 - $r[$key] = self::recursive_wp_parse_args($value, $r[$key]);
766 - }
190 + public static function log( $log ){
191 + if ( defined('WP_DEBUG_LOG') && WP_DEBUG_LOG === true ) {
192 + if ( is_array( $log ) || is_object( $log ) ) {
193 + error_log( print_r( $log, true ) );
767 194 } else {
768 - $r[$key] = $value;
195 + error_log( $log );
769 196 }
770 197 }
771 - return $r;
772 198 }
773 -
774 - /**
775 - * Creates the plugin's working directory under wp-uploads and blocks direct
776 - * web access to it.
777 - *
778 - * Everything the importer needs on disk lands here: the extracted pack (its
779 - * WXR, its template JSON, its attachments), the AI-generated page JSON, and
780 - * the FSI logs. wp-uploads is web-served, so these paths are not private just
781 - * because their session id is a uuid — the guards are what makes them
782 - * unreadable, not the name.
783 - *
784 - * .htaccess covers Apache and is inherited by everything below this point;
785 - * web.config covers IIS; index.php stops a directory listing on any server.
786 - * nginx honours none of them, so an nginx site still needs a location rule —
787 - * this raises the floor, it does not replace server configuration.
788 - *
789 - * @param string $dir Absolute path to create and protect.
790 - *
791 - * @return bool Whether the directory exists and is usable.
792 - */
793 - public static function protect_directory( $dir ) {
794 - if ( empty( $dir ) ) {
795 - return false;
796 - }
797 -
798 - if ( ! is_dir( $dir ) && ! wp_mkdir_p( $dir ) ) {
799 - return false;
800 - }
801 -
802 - $guards = [
803 - 'index.php' => "<?php\n// Silence is golden.\n",
804 - '.htaccess' => "# Templately working files — not for direct access.\n<IfModule mod_authz_core.c>\n\tRequire all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\n\tOrder allow,deny\n\tDeny from all\n</IfModule>\n",
805 - 'web.config' => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n\t<system.webServer>\n\t\t<authorization>\n\t\t\t<deny users=\"*\" />\n\t\t</authorization>\n\t</system.webServer>\n</configuration>\n",
806 - ];
807 -
808 - foreach ( $guards as $file => $contents ) {
809 - $path = trailingslashit( $dir ) . $file;
810 - // Never overwrite: a site owner may have relaxed these deliberately.
811 - if ( ! file_exists( $path ) ) {
812 - @file_put_contents( $path, $contents ); // phpcs:ignore
813 - }
814 - }
815 -
816 - return true;
817 - }
818 -
819 - /**
820 - * Absolute path to the plugin's protected working directory in wp-uploads.
821 - *
822 - * @param string $sub Optional subdirectory ('tmp', 'log', 'preview', ...).
823 - *
824 - * @return string Trailing-slashed path, or '' when uploads is unusable.
825 - */
826 - public static function upload_dir( $sub = '' ) {
827 - $upload_dir = wp_upload_dir();
828 -
829 - if ( ! empty( $upload_dir['error'] ) || empty( $upload_dir['basedir'] ) ) {
830 - return '';
831 - }
832 -
833 - $base = trailingslashit( $upload_dir['basedir'] ) . 'templately' . DIRECTORY_SEPARATOR;
834 -
835 - // The guards go on the root so every subdirectory inherits them.
836 - self::protect_directory( $base );
837 -
838 - return '' === $sub ? $base : trailingslashit( $base . $sub );
839 - }
840 -
841 -}
199 +}