| @@ -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' |
| @@ -349,26 +201,8 @@ | ||
| 349 | 201 | ]; |
| 350 | 202 | } |
| 351 | 203 | |
| 352 | 204 | /** |
| 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 | 205 | * Upload base64 encoded image to media library |
| 372 | 206 | * |
| 373 | 207 | * @param string $base64 Base64 encoded image data (with or without data URI scheme) |
| 374 | 208 | * @param string $session_id Session ID for tracking (reserved for future use) |
| @@ -396,19 +230,11 @@ | ||
| 396 | 230 | * Upload base64 encoded image without dependency on prepare_post_data |
| 397 | 231 | * Handles MIME type detection and proper file extension assignment |
| 398 | 232 | * |
| 399 | 233 | * @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 | |
| 234 | + * @return int|WP_Error Attachment ID on success, WP_Error on failure | |
| 401 | 235 | */ |
| 402 | 236 | 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 | 237 | // Decode base64 string |
| 412 | 238 | $decoded_image = base64_decode($base64, true); |
| 413 | 239 | if ($decoded_image === false) { |
| 414 | 240 | return new \WP_Error('invalid_base64', __('Invalid base64 data.', 'templately')); |
| @@ -537,16 +363,12 @@ | ||
| 537 | 363 | * @param mixed $data |
| 538 | 364 | * @param int $postId |
| 539 | 365 | * @return array |
| 540 | 366 | */ |
| 541 | - public static function import_and_replace_attachments($content, $postId = 0, $attachment_options = []) { | |
| 367 | + public static function import_and_replace_attachments($content, $postId = 0) { | |
| 542 | 368 | // Instantiate GutenbergHelper |
| 543 | 369 | $helper = new GutenbergHelper(); |
| 544 | 370 | |
| 545 | - if ( ! empty( $attachment_options ) ) { | |
| 546 | - $helper->set_attachment_options( $attachment_options ); | |
| 547 | - } | |
| 548 | - | |
| 549 | 371 | $data = [ |
| 550 | 372 | 'content' => $content, |
| 551 | 373 | ]; |
| 552 | 374 | |
| @@ -606,50 +428,62 @@ | ||
| 606 | 428 | |
| 607 | 429 | return $post_data; |
| 608 | 430 | } |
| 609 | 431 | |
| 610 | - // ============================================================================ | |
| 611 | - // Session Data Functions - DEPRECATED: Use SessionData class instead | |
| 612 | - // ============================================================================ | |
| 432 | + // Static version of get_session_data | |
| 433 | + public static function get_all_session_data(): array { | |
| 434 | + $data = get_option(FullSiteImport::SESSION_OPTION_KEY, []); | |
| 435 | + return $data ?? []; | |
| 436 | + } | |
| 613 | 437 | |
| 614 | - /** | |
| 615 | - * @deprecated 3.4.7 Use SessionData::get_session_id() instead | |
| 616 | - */ | |
| 438 | + // Static version of get_session_data | |
| 439 | + public static function get_session_data($session_id): array { | |
| 440 | + $data = get_option(FullSiteImport::SESSION_OPTION_KEY, []); | |
| 441 | + return $data[$session_id] ?? []; | |
| 442 | + } | |
| 443 | + | |
| 444 | + // Static version of update_session_data | |
| 445 | + public static function update_session_data($session_id, $data): bool { | |
| 446 | + $old_data = get_option(FullSiteImport::SESSION_OPTION_KEY, []); | |
| 447 | + return update_option(FullSiteImport::SESSION_OPTION_KEY, Helper::recursive_wp_parse_args([$session_id => $data], $old_data)); | |
| 448 | + } | |
| 449 | + | |
| 450 | + // Delete specific session data by ID | |
| 451 | + public static function delete_session_data($session_id): bool { | |
| 452 | + $all_data = get_option(FullSiteImport::SESSION_OPTION_KEY, []); | |
| 453 | + | |
| 454 | + if (!isset($all_data[$session_id])) { | |
| 455 | + return false; // Session ID doesn't exist | |
| 456 | + } | |
| 457 | + | |
| 458 | + unset($all_data[$session_id]); | |
| 459 | + return update_option(FullSiteImport::SESSION_OPTION_KEY, $all_data); | |
| 460 | + } | |
| 461 | + | |
| 617 | 462 | public static function get_session_id(){ |
| 618 | - _deprecated_function(__METHOD__, '3.4.7', 'SessionData::get_session_id()'); | |
| 619 | - return SessionData::get_session_id(); | |
| 463 | + $session_id = null; | |
| 464 | + if(!empty($_REQUEST['session_id'])){ | |
| 465 | + $session_id = sanitize_text_field($_REQUEST['session_id']); | |
| 466 | + } | |
| 467 | + return $session_id; | |
| 620 | 468 | } |
| 621 | 469 | |
| 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)); | |
| 470 | + public static function get_session_data_by_id(): array { | |
| 471 | + if($session_id = self::get_session_id()){ | |
| 472 | + return self::get_session_data($session_id); | |
| 630 | 473 | } |
| 631 | - return false; | |
| 474 | + throw new Exception(__('Invalid Session ID.', 'templately')); | |
| 632 | 475 | } |
| 633 | 476 | |
| 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); | |
| 477 | + public static function update_session_data_by_id($data): bool { | |
| 478 | + if($session_id = self::get_session_id()){ | |
| 479 | + return self::update_session_data($session_id, $data); | |
| 480 | + } | |
| 481 | + throw new Exception(__('Invalid Session ID.', 'templately')); | |
| 640 | 482 | } |
| 641 | 483 | |
| 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 | 484 | |
| 650 | 485 | |
| 651 | - | |
| 652 | 486 | /** |
| 653 | 487 | * Clean up directory using RecursiveIteratorIterator approach |
| 654 | 488 | * This method handles directory cleanup with proper validation |
| 655 | 489 | * |
| @@ -677,6 +511,54 @@ | ||
| 677 | 511 | } catch (Exception $e) { |
| 678 | 512 | return false; |
| 679 | 513 | } |
| 680 | 514 | } |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + /** | |
| 525 | + * Clean session data by pack ID, keeping only the current session | |
| 526 | + * Removes all session entries with the same pack_id except the current session | |
| 527 | + * | |
| 528 | + * @param string $pack_id The pack ID to match for cleanup | |
| 529 | + * @param string $current_session_id The current session ID to preserve | |
| 530 | + * @return array Array of removed session IDs | |
| 531 | + */ | |
| 532 | + public static function clean_session_data_by_pack_id($pack_id, $current_session_id) { | |
| 533 | + if (empty($pack_id) || empty($current_session_id)) { | |
| 534 | + return []; | |
| 535 | + } | |
| 536 | + | |
| 537 | + $all_session_data = self::get_all_session_data(); | |
| 538 | + $removed_session_ids = []; | |
| 539 | + | |
| 540 | + | |
| 541 | + foreach ($all_session_data as $session_id => $session_data) { | |
| 542 | + // Skip the current session | |
| 543 | + if ($session_id === $current_session_id) { | |
| 544 | + continue; | |
| 545 | + } | |
| 546 | + | |
| 547 | + // Remove sessions that have the same pack_id | |
| 548 | + if (isset($session_data['id']) && $session_data['id'] === $pack_id) { | |
| 549 | + unset($all_session_data[$session_id]); | |
| 550 | + $removed_session_ids[] = $session_id; | |
| 551 | + } | |
| 552 | + } | |
| 553 | + | |
| 554 | + // Update the option with cleaned data if any sessions were removed | |
| 555 | + if (!empty($removed_session_ids)) { | |
| 556 | + update_option(FullSiteImport::SESSION_OPTION_KEY, $all_session_data); | |
| 557 | + } | |
| 558 | + | |
| 559 | + return $removed_session_ids; | |
| 560 | + } | |
| 561 | + | |
| 562 | + | |
| 681 | 563 | |
| 682 | 564 | } |