PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.3.3
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.3.3
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 +112 -362 3.6.53.3.3 View file →
@@ -17,18 +17,8 @@
17 17 * @since 1.0.0
18 18 */
19 19 class Helper extends Base {
20 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 21 * Get installed WordPress Plugin List
32 22 * @return array
33 23 */
34 24 public static function get_plugins() {
@@ -89,127 +79,8 @@
89 79 }
90 80 }
91 81
92 82 /**
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 83 * Sanitize Helper
213 84 *
214 85 * @param mixed $value
215 86 * @param string $type
@@ -229,194 +100,8 @@
229 100 return $sanitized_value;
230 101 }
231 102
232 103 /**
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 104 * API Error Formatter
420 105 *
421 106 * @param int $error_code
422 107 * @param mixed $error_message
@@ -510,59 +195,21 @@
510 195
511 196 /**
512 197 * Printing Error Logs in debug.log file.
513 198 *
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)
199 + * @param mixed $log
517 200 * @return void
518 201 */
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;
202 + public static function log($log) {
203 + if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
204 + if (is_array($log) || is_object($log)) {
205 + error_log(print_r($log, true));
206 + } else {
207 + error_log($log ?: '');
208 + }
524 209 }
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 210 }
537 211
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 212 public static function should_flush() {
566 213 if (isset($_REQUEST['is_lightspeed']) && $_REQUEST['is_lightspeed'] === 'true') {
567 214 return false;
568 215 }
@@ -677,9 +324,9 @@
677 324 */
678 325 public static function enable_elementor_container() {
679 326 if (class_exists('Elementor\Plugin')) {
680 327 $control_name = Plugin::instance()->experiments->get_feature_option_key('container');
681 - if (get_option($control_name) !== 'active') {
328 + if (get_option($control_name) === 'inactive') {
682 329 update_option($control_name, 'active');
683 330 return true;
684 331 }
685 332 }
@@ -719,5 +366,108 @@
719 366 }
720 367 return $r;
721 368 }
722 369
370 + /**
371 + * Save template data to file
372 + *
373 + * Common function for saving templates to files, used by AI content operations
374 + *
375 + * @param string $process_id The process ID
376 + * @param string $content_id The content ID
377 + * @param string $template The template data (base64 encoded or raw)
378 + * @param array $ai_page_ids Array of AI page IDs
379 + * @param bool $is_preview Whether this is a preview operation
380 + * @param bool $is_skipped Whether the template was skipped
381 + * @return array|WP_Error Result array with status and data
382 + */
383 + public static function save_template_to_file($process_id, $content_id, $template, $ai_page_ids, $is_preview = false, $is_skipped = false) {
384 + $upload_dir = wp_upload_dir();
385 +
386 + // Always save to preview directory for AI content workflow
387 + $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'preview' . DIRECTORY_SEPARATOR . $process_id . DIRECTORY_SEPARATOR;
388 +
389 + // Decode template if it's base64 encoded
390 + if (! empty($template) && base64_decode($template, true) !== false) {
391 + $template = base64_decode($template);
392 + }
393 +
394 + // Handle empty template (skipped)
395 + if (empty($template)) {
396 + $template = json_encode([
397 + "isSkipped" => true,
398 + ]);
399 + }
400 +
401 + // Find the correct directory for the content ID
402 + $found_key = null;
403 + foreach ($ai_page_ids as $key => $ids) {
404 + if (in_array($content_id, $ids)) {
405 + $found_key = $key;
406 + break;
407 + }
408 + }
409 +
410 + if ($found_key === null) {
411 + return self::error('invalid_content_id', __('Content ID not found in AI page IDs.', 'templately'), 'save_template_to_file', 400);
412 + }
413 +
414 + // Create directory and file path
415 + $page_dir = $tmp_dir . $found_key . DIRECTORY_SEPARATOR;
416 + $file_path = $page_dir . $content_id . '.ai.json';
417 + wp_mkdir_p($page_dir);
418 +
419 + // Save the file
420 + $is_success = file_put_contents($file_path, $template);
421 +
422 + if ($is_success) {
423 + // Update processed pages option
424 + $processed_pages = get_option("templately_ai_processed_pages", []);
425 + $processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
426 + $processed_pages[$process_id]['pages'][$content_id] = $is_skipped;
427 + update_option("templately_ai_processed_pages", $processed_pages, false);
428 +
429 + return [
430 + 'status' => 'success',
431 + 'data' => [
432 + 'process_id' => $process_id,
433 + // 'file_path' => $file_path,
434 + 'content_id' => $content_id,
435 + ],
436 + ];
437 + }
438 +
439 + return self::error('file_save_failed', __('Failed to save template file.', 'templately'), 'save_template_to_file', 500);
440 + }
441 +
442 + /**
443 + * Get matched session data by process ID
444 + *
445 + * Helper function to retrieve session data for a given process ID
446 + *
447 + * @param string $process_id The process ID to match
448 + * @return array|false Returns matched data array or false if not found
449 + */
450 + public static function get_matched_session_data($process_id) {
451 + if (empty($process_id)) {
452 + return false;
453 + }
454 +
455 + $all_data = Utils::get_all_session_data();
456 +
457 + if (empty($all_data) || ! is_array($all_data)) {
458 + return false;
459 + }
460 +
461 + // INSERT_YOUR_CODE
462 + if (is_array($all_data)) {
463 + $all_data = array_reverse($all_data);
464 + }
465 + foreach ($all_data as $data) {
466 + if (isset($data['process_id']) && ($data['process_id'] === $process_id)) {
467 + return $data;
468 + }
469 + }
470 +
471 + return false;
472 + }
723 473 }