PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.2.7
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.2.7
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/Core/Importer/Utils/Utils.php +28 -404 3.7.53.2.7 View file →
@@ -2,10 +2,8 @@
2 2
3 3 namespace Templately\Core\Importer\Utils;
4 4
5 5 use Exception;
6 -use Templately\Builder\PageTemplates;
7 -use Templately\Builder\Factory\TemplateFactory;
8 6 use Templately\Core\Importer\FullSiteImport;
9 7 use Templately\Core\Importer\WPImport;
10 8 use Templately\Utils\Base;
11 9 use Templately\Utils\Helper;
@@ -11,26 +9,9 @@
11 9 use Templately\Utils\Helper;
12 10
13 11 class Utils extends Base {
14 12
15 - /**
16 - * Filter callback to prefer the GD image editor over Imagick during imports.
17 - * Imagick can fail on certain server configurations; GD is more reliable for
18 - * the resize/crop operations triggered by wp_generate_attachment_metadata().
19 - */
20 - public static function prefer_gd_editor( $editors ) {
21 - if ( is_callable( [ 'WP_Image_Editor_GD', 'test' ] ) && call_user_func( [ 'WP_Image_Editor_GD', 'test' ] ) ) {
22 - return [ 'WP_Image_Editor_GD' ];
23 - }
24 - return $editors;
25 - }
26 13
27 - public static function add_gd_editor_filter() {
28 - if ( ! has_filter( 'wp_image_editors', [ self::class, 'prefer_gd_editor' ] ) ) {
29 - add_filter( 'wp_image_editors', [ self::class, 'prefer_gd_editor' ] );
30 - }
31 - }
32 -
33 14 /**
34 15 * @throws Exception
35 16 */
36 17 public static function read_json_file( $path ) {
@@ -166,137 +147,8 @@
166 147 self::backup_option_value($key, $autoload);
167 148 return update_option($key, $value, $autoload);
168 149 }
169 150
170 - /**
171 - * Create the WordPress page an archive-type template attaches to, and wire up
172 - * the matching WP/WooCommerce/LearnDash setting.
173 - *
174 - * Shared by the FSI Templates runner and single-template Library imports so both
175 - * produce the same companion page + settings (page_for_posts,
176 - * woocommerce_shop_page_id, LearnDash courses slug).
177 - *
178 - * @param array $template_settings Needs 'type'; optional 'title' and 'page_settings.archive_page_id'.
179 - * @param string $platform 'elementor' | 'gutenberg'.
180 - * @param array $manifest FSI manifest (optional). Only used to skip creation when the
181 - * archive page is already part of the imported content.
182 - *
183 - * @return false|int New page ID, or false on skip/failure.
184 - */
185 - public static function create_archive_page( $template_settings, $platform, $manifest = [] ) {
186 - try {
187 - $archive_page_id = $template_settings['page_settings']['archive_page_id'] ?? null;
188 - if ( $archive_page_id && ! empty( $manifest['content']['page'][ $archive_page_id ] ) ) {
189 - return false;
190 - }
191 -
192 - $type = $template_settings['type'];
193 -
194 - $archive_page = wp_insert_post( [
195 - 'post_title' => $template_settings['title'] ?? ucfirst( $type ) . ' - (by Templately)',
196 - 'post_status' => 'publish',
197 - 'post_type' => 'page',
198 - 'post_content' => '',
199 - 'page_template' => $platform === 'elementor' ? 'elementor_header_footer' : PageTemplates::TEMPLATE_HEADER_FOOTER,
200 - ] );
201 -
202 - if ( is_wp_error( $archive_page ) ) {
203 - return false;
204 - }
205 -
206 - if ( $type === 'archive' ) {
207 - self::update_option( 'page_for_posts', $archive_page );
208 - }
209 -
210 - if ( $type === 'product_archive' ) {
211 - self::update_option( 'woocommerce_shop_page_id', $archive_page );
212 - }
213 -
214 - if ( $type === 'course_archive' ) {
215 - // get page slug from $archive_page id and update learndash_settings_permalinks option to courses.
216 - $post_name = get_post_field( 'post_name', $archive_page );
217 -
218 - if ( class_exists( '\LearnDash_Settings_Section' ) ) {
219 - $section = \LearnDash_Settings_Section::get_section_instance( 'LearnDash_Settings_Section_Permalinks' );
220 - if ( ! empty( $section ) ) {
221 - $section->set_setting( 'courses', $post_name );
222 - if ( function_exists( 'learndash_setup_rewrite_flush' ) ) {
223 - learndash_setup_rewrite_flush();
224 - }
225 - }
226 - }
227 - }
228 -
229 - return $archive_page;
230 - } catch ( \Exception $e ) {
231 - return false;
232 - }
233 - }
234 -
235 - /**
236 - * Create a single-page Library template carrying a post-content widget/block,
237 - * so a fluent_product_single import has a page template to render into.
238 - *
239 - * Shared by the FSI Templates runner and single-template Library imports.
240 - *
241 - * @param string $platform 'elementor' | 'gutenberg'.
242 - *
243 - * @return void
244 - */
245 - public static function create_page_template( $platform ) {
246 - try {
247 - $meta = [];
248 - $data = [];
249 -
250 - $post_data = [
251 - 'post_title' => 'Single Page - (by Templately)',
252 - 'post_status' => 'publish',
253 - 'post_type' => 'templately_library',
254 - ];
255 -
256 - if ( $platform === 'elementor' ) {
257 - $meta['_wp_page_template'] = 'elementor_header_footer';
258 - $data = json_decode( '{"content":[{"id":"4a86515d","settings":[],"elements":[{"id":"30a46db1","settings":{"content_width":"full"},"elements":[],"isInner":false,"widgetType":"tl-post-content","elType":"widget"}],"isInner":false,"elType":"container"}],"settings":{"template":"elementor_header_footer"},"metadata":[]}', true );
259 - } elseif ( $platform === 'gutenberg' ) {
260 - $meta['_wp_page_template'] = PageTemplates::TEMPLATE_HEADER_FOOTER;
261 - $data = [ 'content' => '<!-- wp:post-content /-->' ];
262 - }
263 -
264 - $factory = new TemplateFactory( $platform );
265 - $template = $factory->create( 'page_single', $post_data, $meta );
266 - $template->import( $data );
267 - } catch ( \Exception $e ) {
268 - return;
269 - }
270 - }
271 -
272 - /**
273 - * Run the companion-content side effects for a freshly imported Library template.
274 - *
275 - * Dispatches by resolved Builder type:
276 - * - archive / product_archive / course_archive → attached WP page + settings
277 - * - fluent_product_single → single-page template
278 - *
279 - * Convenience wrapper for single-template imports, which have no FSI manifest.
280 - *
281 - * @param string $type Resolved Builder type key.
282 - * @param string $title Template title (used for the created page).
283 - * @param string $platform 'elementor' | 'gutenberg'.
284 - *
285 - * @return false|int|null Archive page ID for archive types, null otherwise.
286 - */
287 - public static function create_companion_content( $type, $title, $platform ) {
288 - if ( in_array( $type, [ 'archive', 'product_archive', 'course_archive' ], true ) ) {
289 - return self::create_archive_page( [ 'title' => $title, 'type' => $type ], $platform );
290 - }
291 -
292 - if ( $type === 'fluent_product_single' ) {
293 - self::create_page_template( $platform );
294 - }
295 -
296 - return null;
297 - }
298 -
299 151 public static function import_page_settings( $id, $settings ) {
300 152 $extra_settings = [
301 153 'page_on_front' => [
302 154 'show_on_front' => 'page'
@@ -310,13 +162,9 @@
310 162 self::update_option( 'show_on_front', 'page' );
311 163 }
312 164 if ( ! empty( $settings['page_settings'] ) ) {
313 165 foreach ( $settings['page_settings'] as $option_name => $val ) {
314 - $__val = $id;
315 - if($option_name === 'fluent_cart_store_settings'){
316 - $__val = $val;
317 - }
318 - self::update_option( $option_name, $__val );
166 + self::update_option( $option_name, $id );
319 167 if ( array_key_exists( $option_name, $extra_settings ) ) {
320 168 foreach ( $extra_settings[ $option_name ] as $name => $value ) {
321 169 self::update_option( $name, $value );
322 170 }
@@ -348,190 +196,8 @@
348 196 'url' => esc_url_raw(wp_get_attachment_url($attachment_id)),
349 197 ];
350 198 }
351 199
352 - /**
353 - * Get a remote logo URL from the supported import payload shapes.
354 - *
355 - * @param mixed $logo A URL string or an array containing a URL.
356 - * @return string
357 - */
358 - public static function get_logo_url( $logo ) {
359 - if ( is_string( $logo ) ) {
360 - return $logo;
361 - }
362 -
363 - if ( is_array( $logo ) && isset( $logo['url'] ) && is_string( $logo['url'] ) ) {
364 - return $logo['url'];
365 - }
366 -
367 - return '';
368 - }
369 -
370 - /**
371 - * Upload base64 encoded image to media library
372 - *
373 - * @param string $base64 Base64 encoded image data (with or without data URI scheme)
374 - * @param string $session_id Session ID for tracking (reserved for future use)
375 - * @return array Array with 'id' and 'url' on success, or ['error' => message] on failure
376 - */
377 - public static function upload_logo_base64($base64, $session_id = null) {
378 - if(empty($base64)) {
379 - return ['error' => __('Base64 is empty', 'templately')];
380 - }
381 -
382 - // Upload the base64 image
383 - $attachment_id = self::upload_base64_image($base64);
384 -
385 - if(is_wp_error($attachment_id)){
386 - return ['error' => $attachment_id->get_error_message()];
387 - }
388 -
389 - return [
390 - 'id' => (int) $attachment_id,
391 - 'url' => esc_url_raw(wp_get_attachment_url($attachment_id)),
392 - ];
393 - }
394 -
395 - /**
396 - * Upload base64 encoded image without dependency on prepare_post_data
397 - * Handles MIME type detection and proper file extension assignment
398 - *
399 - * @param string $base64 Base64 encoded image data (with or without data URI scheme)
400 - * @return int|\WP_Error Attachment ID on success, WP_Error on failure
401 - */
402 - public static function upload_base64_image($base64) {
403 - // Strip data URL prefix if present (e.g., "data:image/png;base64,")
404 - if (strpos($base64, 'data:image/') === 0) {
405 - $base64_parts = explode(',', $base64, 2);
406 - if (isset($base64_parts[1])) {
407 - $base64 = $base64_parts[1];
408 - }
409 - }
410 -
411 - // Decode base64 string
412 - $decoded_image = base64_decode($base64, true);
413 - if ($decoded_image === false) {
414 - return new \WP_Error('invalid_base64', __('Invalid base64 data.', 'templately'));
415 - }
416 -
417 - // Detect MIME type from decoded image data
418 - $mime_type = self::detect_mime_type_from_data($decoded_image);
419 - if (empty($mime_type)) {
420 - return new \WP_Error('unknown_mime_type', __('Unable to determine image MIME type.', 'templately'));
421 - }
422 -
423 - // Get file extension from MIME type
424 - $extension = self::get_file_extension_by_mime_type($mime_type);
425 - if (empty($extension)) {
426 - return new \WP_Error('unsupported_mime_type', __('Unsupported image MIME type.', 'templately'));
427 - }
428 -
429 - // Generate unique filename
430 - $filename = 'templately-logo-' . \wp_generate_uuid4() . '.' . $extension;
431 -
432 - // Get upload directory
433 - $upload_dir = \wp_upload_dir();
434 - if (!$upload_dir['error']) {
435 - $upload_path = $upload_dir['path'] . '/' . $filename;
436 - } else {
437 - return new \WP_Error('upload_dir_error', __('Unable to access upload directory.', 'templately'));
438 - }
439 -
440 - // Write decoded image to file
441 - if (file_put_contents($upload_path, $decoded_image) === false) {
442 - return new \WP_Error('upload_error', __('Error uploading image.', 'templately'));
443 - }
444 -
445 - // Create attachment post
446 - $attachment_data = [
447 - 'post_mime_type' => $mime_type,
448 - 'post_title' => \sanitize_file_name(pathinfo($filename, PATHINFO_FILENAME)),
449 - 'post_content' => '',
450 - 'post_status' => 'inherit',
451 - ];
452 -
453 - $attachment_id = \wp_insert_attachment($attachment_data, $upload_path);
454 - if (is_wp_error($attachment_id)) {
455 - return $attachment_id;
456 - }
457 -
458 - // Ensure WordPress image functions are available
459 - // These functions are defined in wp-admin/includes/image.php which is not always loaded
460 - if (!function_exists('wp_generate_attachment_metadata')) {
461 - require_once(ABSPATH . 'wp-admin/includes/image.php');
462 - }
463 -
464 - // Generate and update attachment metadata
465 - $metadata = \wp_generate_attachment_metadata($attachment_id, $upload_path);
466 - \wp_update_attachment_metadata($attachment_id, $metadata);
467 -
468 - return $attachment_id;
469 - }
470 -
471 - /**
472 - * Detect MIME type from image data
473 - * Uses finfo_buffer if available, otherwise falls back to getimagesizefromstring
474 - *
475 - * @param string $image_data Raw image data
476 - * @return string|null MIME type or null if unable to detect
477 - */
478 - private static function detect_mime_type_from_data($image_data) {
479 - // Try using finfo_buffer first (most reliable)
480 - if (function_exists('finfo_buffer')) {
481 - $finfo = finfo_open(FILEINFO_MIME_TYPE);
482 - if ($finfo) {
483 - $mime_type = finfo_buffer($finfo, $image_data);
484 - finfo_close($finfo);
485 - if ($mime_type && strpos($mime_type, 'image/') === 0) {
486 - return $mime_type;
487 - }
488 - }
489 - }
490 -
491 - // Fallback: use getimagesizefromstring
492 - if (function_exists('getimagesizefromstring')) {
493 - $image_info = @getimagesizefromstring($image_data);
494 - if ($image_info && isset($image_info['mime'])) {
495 - return $image_info['mime'];
496 - }
497 - }
498 -
499 - return null;
500 - }
501 -
502 - /**
503 - * Get file extension by MIME type
504 - * Uses WordPress built-in functions for MIME type to extension conversion
505 - *
506 - * @since 3.4.5
507 - * @param string $mime_type MIME type (e.g., 'image/png')
508 - * @return string|null File extension without dot, or null if not found
509 - */
510 - private static function get_file_extension_by_mime_type($mime_type) {
511 - // Use WordPress core function if available (WordPress 5.8.1+)
512 - // wp_get_default_extension_for_mime_type() returns the default file extension for a given MIME type
513 - if (function_exists('wp_get_default_extension_for_mime_type')) {
514 - return \wp_get_default_extension_for_mime_type($mime_type);
515 - }
516 -
517 - // Fallback for WordPress < 5.8.1
518 - // Use wp_get_mime_types() which returns array with extensions as keys and MIME types as values
519 - // Example: ['jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', ...]
520 - $wp_mime_types = \wp_get_mime_types();
521 -
522 - // Flip the array to get MIME type as key and extensions as value
523 - $mime_map = array_flip($wp_mime_types);
524 -
525 - if (isset($mime_map[$mime_type])) {
526 - $extensions = $mime_map[$mime_type];
527 - // Get first extension if multiple are available (e.g., 'jpg|jpeg|jpe' -> 'jpg')
528 - return strtok($extensions, '|');
529 - }
530 -
531 - return null;
532 - }
533 -
534 200 /**
535 201 * Inserts a template into the Gutenberg editor.
536 202 *
537 203 * @param mixed $data
@@ -537,16 +203,12 @@
537 203 * @param mixed $data
538 204 * @param int $postId
539 205 * @return array
540 206 */
541 - public static function import_and_replace_attachments($content, $postId = 0, $attachment_options = []) {
207 + public static function import_and_replace_attachments($content, $postId = 0) {
542 208 // Instantiate GutenbergHelper
543 209 $helper = new GutenbergHelper();
544 210
545 - if ( ! empty( $attachment_options ) ) {
546 - $helper->set_attachment_options( $attachment_options );
547 - }
548 -
549 211 $data = [
550 212 'content' => $content,
551 213 ];
552 214
@@ -606,77 +268,39 @@
606 268
607 269 return $post_data;
608 270 }
609 271
610 - // ============================================================================
611 - // Session Data Functions - DEPRECATED: Use SessionData class instead
612 - // ============================================================================
272 + // Static version of get_session_data
273 + public static function get_session_data($session_id): array {
274 + $data = get_option(FullSiteImport::SESSION_OPTION_KEY, []);
275 + return $data[$session_id] ?? [];
276 + }
613 277
614 - /**
615 - * @deprecated 3.4.7 Use SessionData::get_session_id() instead
616 - */
278 + // Static version of update_session_data
279 + public static function update_session_data($session_id, $data): bool {
280 + $old_data = get_option(FullSiteImport::SESSION_OPTION_KEY, []);
281 + return update_option(FullSiteImport::SESSION_OPTION_KEY, Helper::recursive_wp_parse_args([$session_id => $data], $old_data));
282 + }
283 +
617 284 public static function get_session_id(){
618 - _deprecated_function(__METHOD__, '3.4.7', 'SessionData::get_session_id()');
619 - return SessionData::get_session_id();
285 + $session_id = null;
286 + if(!empty($_GET['session_id'])){
287 + $session_id = sanitize_text_field($_GET['session_id']);
288 + }
289 + return $session_id;
620 290 }
621 291
622 - /**
623 - * @deprecated 3.4.7 Use SessionData::save() or SessionData::set() instead
624 - */
625 - public static function update_session_data_by_id($data): bool {
626 - _deprecated_function(__METHOD__, '3.4.7', 'SessionData::save() or SessionData::set()');
627 - if($session_id = SessionData::get_session_id()){
628 - $existing = SessionData::get_data($session_id);
629 - return SessionData::save($session_id, array_merge($existing, $data));
292 + public static function get_session_data_by_id(): array {
293 + if($session_id = self::get_session_id()){
294 + return self::get_session_data($session_id);
630 295 }
631 - return false;
296 + throw new Exception(__('Invalid Session ID.', 'templately'));
632 297 }
633 298
634 - /**
635 - * @deprecated 3.4.7 Use SessionData::clean_by_pack_id() instead
636 - */
637 - public static function clean_session_data_by_pack_id($pack_id, $current_session_id) {
638 - _deprecated_function(__METHOD__, '3.4.7', 'SessionData::clean_by_pack_id()');
639 - return SessionData::clean_by_pack_id($pack_id, $current_session_id);
640 - }
641 -
642 - /**
643 - * @deprecated 3.4.7 Use SessionData::cleanup_expired() instead
644 - */
645 - public static function cleanup_expired_sessions($max_age_days = 7) {
646 - _deprecated_function(__METHOD__, '3.4.7', 'SessionData::cleanup_expired()');
647 - return SessionData::cleanup_expired($max_age_days);
648 - }
649 -
650 -
651 -
652 - /**
653 - * Clean up directory using RecursiveIteratorIterator approach
654 - * This method handles directory cleanup with proper validation
655 - *
656 - * @param string $dir_path The directory path to clean up
657 - * @return bool True on success, false on failure
658 - */
659 - public static function cleanup_directory($dir_path) {
660 - if (empty($dir_path) || !file_exists($dir_path) || !is_dir($dir_path)) {
661 - return false;
299 + public static function update_session_data_by_id($data): array {
300 + if($session_id = self::get_session_id()){
301 + $session_id = self::update_session_data($session_id, $data);
302 + return $data[$session_id] ?? [];
662 303 }
663 -
664 - try {
665 - $files = new \RecursiveIteratorIterator(
666 - new \RecursiveDirectoryIterator($dir_path, \RecursiveDirectoryIterator::SKIP_DOTS),
667 - \RecursiveIteratorIterator::CHILD_FIRST
668 - );
669 -
670 - foreach ($files as $fileinfo) {
671 - $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
672 - $todo($fileinfo->getRealPath());
673 - }
674 -
675 - rmdir($dir_path);
676 - return true;
677 - } catch (Exception $e) {
678 - return false;
679 - }
304 + throw new Exception(__('Invalid Session ID.', 'templately'));
680 305 }
681 -
682 -}
306 +}