get_error_message() ); } } foreach ( $categories as $slug => $category ) { $term = get_term_by( 'slug', $slug, 'sg7g_category' ); if ( $term instanceof WP_Term ) { $category_ids[ $slug ] = (int) $term->term_id; continue; } $created = wp_insert_term( $category['name'], 'sg7g_category', array( 'slug' => $slug, 'description' => $category['description'], ) ); if ( is_wp_error( $created ) ) { self::rollback_demo_content( $created_posts, $created_terms ); self::redirect_result( 'error', $created->get_error_message() ); } $term_id = absint( $created['term_id'] ?? 0 ); $category_ids[ $slug ] = $term_id; $created_terms[] = $term_id; } foreach ( self::demo_items() as $index => $item ) { $existing_id = self::find_demo_post( $item['key'] ); if ( $existing_id ) { if ( 'trash' === get_post_status( $existing_id ) ) { $untrashed = wp_untrash_post( $existing_id ); if ( ! $untrashed ) { self::rollback_demo_content( $created_posts, $created_terms, $restored_posts, $featured_states ); self::redirect_result( 'error', __( 'A deleted demo item could not be restored.', '7sgaleri' ) ); } $restored_posts[] = $existing_id; $restored++; } else { $kept++; } if ( ! empty( $item['featured'] ) ) { $featured_states[ $existing_id ] = 1 === (int) get_post_meta( $existing_id, '_sg7g_featured', true ); update_post_meta( $existing_id, '_sg7g_featured', 1 ); $featured_ids[] = $existing_id; } continue; } $post_id = wp_insert_post( array( 'post_type' => 'sg7g_media', 'post_status' => 'publish', 'post_author' => get_current_user_id(), 'post_title' => $item['title'], 'post_excerpt' => $item['excerpt'], 'post_content' => $item['content'], 'menu_order' => $index, 'meta_input' => array( '_sg7g_demo_key' => $item['key'], '_sg7g_media_type' => $item['type'], '_sg7g_source' => $item['source'], '_sg7g_source_url' => $item['url'], '_sg7g_file_id' => 0, '_sg7g_source_valid' => 1, '_sg7g_validation_schema' => '4', '_sg7g_portable_id' => 'demo-' . $item['key'], ), ), true ); if ( is_wp_error( $post_id ) ) { self::rollback_demo_content( $created_posts, $created_terms, $restored_posts, $featured_states ); self::redirect_result( 'error', $post_id->get_error_message() ); } $post_id = (int) $post_id; $created_posts[] = $post_id; $term_id = absint( $category_ids[ $item['category'] ] ?? 0 ); $term_result = $term_id ? wp_set_object_terms( $post_id, array( $term_id ), 'sg7g_category', false ) : false; if ( is_wp_error( $term_result ) || ! $term_id ) { self::rollback_demo_content( $created_posts, $created_terms, $restored_posts, $featured_states ); self::redirect_result( 'error', __( 'A demo category could not be assigned.', '7sgaleri' ) ); } $tag_result = wp_set_object_terms( $post_id, $item['tags'], 'sg7g_tag', false ); if ( is_wp_error( $tag_result ) ) { self::rollback_demo_content( $created_posts, $created_terms, $restored_posts, $featured_states ); self::redirect_result( 'error', $tag_result->get_error_message() ); } if ( ! empty( $item['featured'] ) ) { update_post_meta( $post_id, '_sg7g_featured', 1 ); $featured_ids[] = $post_id; } $post = get_post( $post_id ); if ( $post instanceof WP_Post ) { SG7G_Default_Cover::ensure( $post_id, $post, true ); } $added++; } $featured_order = SG7G_Settings::featured_order(); foreach ( $featured_ids as $post_id ) { if ( ! in_array( $post_id, $featured_order, true ) ) { $featured_order[] = $post_id; } } SG7G_Settings::update_featured_order( $featured_order ); self::redirect_result( 'success', sprintf( /* translators: 1: Added count. 2: Restored count. 3: Existing count. */ __( 'Demo content is ready. %1$d item added, %2$d items restored, and %3$d existing items kept.', '7sgaleri' ), $added, $restored, $kept ) ); } private static function demo_content_count(): int { $expected_keys = array_column( self::demo_items(), 'key' ); $post_ids = get_posts( array( 'post_type' => 'sg7g_media', 'post_status' => array( 'publish', 'draft', 'pending', 'private', 'future' ), 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, 'update_post_term_cache' => false, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Small administrator-only lookup for plugin-created demo identifiers. 'meta_query' => array( array( 'key' => '_sg7g_demo_key', 'value' => $expected_keys, 'compare' => 'IN', ), ), ) ); $installed_keys = array(); foreach ( $post_ids as $post_id ) { $key = (string) get_post_meta( absint( $post_id ), '_sg7g_demo_key', true ); if ( in_array( $key, $expected_keys, true ) ) { $installed_keys[] = $key; } } return count( array_unique( $installed_keys ) ); } private static function find_demo_post( string $key ): int { $status_groups = array( array( 'publish', 'draft', 'pending', 'private', 'future' ), array( 'trash' ), ); foreach ( $status_groups as $statuses ) { $ids = get_posts( array( 'post_type' => 'sg7g_media', 'post_status' => $statuses, 'posts_per_page' => 1, 'fields' => 'ids', 'orderby' => 'ID', 'order' => 'ASC', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Exact plugin-owned demo identifier lookup. 'meta_key' => '_sg7g_demo_key', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Exact plugin-owned demo identifier lookup. 'meta_value' => $key, 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); if ( ! empty( $ids ) ) { return absint( $ids[0] ); } } return 0; } private static function rollback_demo_content( array $post_ids, array $term_ids, array $restored_ids = array(), array $featured_states = array() ): void { foreach ( array_reverse( $post_ids ) as $post_id ) { wp_delete_post( absint( $post_id ), true ); } foreach ( $featured_states as $post_id => $was_featured ) { if ( $was_featured ) { update_post_meta( absint( $post_id ), '_sg7g_featured', 1 ); } else { delete_post_meta( absint( $post_id ), '_sg7g_featured' ); } } foreach ( array_reverse( $restored_ids ) as $post_id ) { wp_trash_post( absint( $post_id ) ); } foreach ( array_reverse( $term_ids ) as $term_id ) { wp_delete_term( absint( $term_id ), 'sg7g_category' ); } } private static function demo_categories(): array { return array( 'short-films' => array( 'name' => __( 'Short Films', '7sgaleri' ), 'description' => __( 'Short-form films and visual stories.', '7sgaleri' ), ), 'trailers' => array( 'name' => __( 'Trailers', '7sgaleri' ), 'description' => __( 'Trailers, teasers, and preview clips.', '7sgaleri' ), ), 'soundtracks' => array( 'name' => __( 'Soundtracks', '7sgaleri' ), 'description' => __( 'Scores and soundtrack selections.', '7sgaleri' ), ), 'live-sessions' => array( 'name' => __( 'Live Sessions', '7sgaleri' ), 'description' => __( 'Live recordings and studio sessions.', '7sgaleri' ), ), ); } private static function demo_items(): array { return array( array( 'key' => 'sintel-trailer', 'title' => __( 'Sintel — Short Film Trailer', '7sgaleri' ), 'excerpt' => __( 'A cinematic trailer example for the Featured area.', '7sgaleri' ), 'content' => __( 'Use this entry to explore the video player, cover image, category navigation, sharing tools, and related content.', '7sgaleri' ), 'type' => 'video', 'source' => 'external_video', 'url' => 'https://media.w3.org/2010/05/sintel/trailer.mp4', 'category' => 'short-films', 'tags' => array( __( 'Cinema', '7sgaleri' ), __( 'Animation', '7sgaleri' ) ), 'featured' => true, ), array( 'key' => 'big-buck-bunny-trailer', 'title' => __( 'Big Buck Bunny — Trailer', '7sgaleri' ), 'excerpt' => __( 'A second video example for the editorial showcase.', '7sgaleri' ), 'content' => __( 'This demo shows how a published trailer appears throughout the gallery and on its individual content page.', '7sgaleri' ), 'type' => 'video', 'source' => 'external_video', 'url' => 'https://media.w3.org/2010/05/bunny/trailer.mp4', 'category' => 'trailers', 'tags' => array( __( 'Trailer', '7sgaleri' ), __( 'Animation', '7sgaleri' ) ), 'featured' => true, ), array( 'key' => 'open-movie-preview', 'title' => __( 'Open Movie — Preview', '7sgaleri' ), 'excerpt' => __( 'A compact video sample for gallery cards.', '7sgaleri' ), 'content' => __( 'Compare this item with the larger Featured card and see how the same content adapts to each layout.', '7sgaleri' ), 'type' => 'video', 'source' => 'external_video', 'url' => 'https://media.w3.org/2010/05/video/movie_300.mp4', 'category' => 'trailers', 'tags' => array( __( 'Preview', '7sgaleri' ), __( 'Video', '7sgaleri' ) ), 'featured' => true, ), array( 'key' => 'open-movie-webm-cut', 'title' => __( 'Open Movie — WebM Cut', '7sgaleri' ), 'excerpt' => __( 'A direct WebM source example.', '7sgaleri' ), 'content' => __( 'The gallery can play supported direct media links as well as platform embeds and Media Library files.', '7sgaleri' ), 'type' => 'video', 'source' => 'external_video', 'url' => 'https://media.w3.org/2010/05/video/movie_300.webm', 'category' => 'short-films', 'tags' => array( __( 'Short Film', '7sgaleri' ), __( 'Video', '7sgaleri' ) ), 'featured' => true, ), array( 'key' => 'soundhelix-demo-track-1', 'title' => __( 'SoundHelix — Demo Track 1', '7sgaleri' ), 'excerpt' => __( 'A music example for the Featured area and audio player.', '7sgaleri' ), 'content' => __( 'Use this entry to explore the audio player, music filtering, cover image, and related content.', '7sgaleri' ), 'type' => 'audio', 'source' => 'external_audio', 'url' => 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', 'category' => 'soundtracks', 'tags' => array( __( 'Music', '7sgaleri' ), __( 'Soundtrack', '7sgaleri' ) ), 'featured' => true, ), array( 'key' => 'soundhelix-demo-track-2', 'title' => __( 'SoundHelix — Demo Track 2', '7sgaleri' ), 'excerpt' => __( 'A soundtrack card and player example.', '7sgaleri' ), 'content' => __( 'This sample helps demonstrate how audio content sits beside video content in the same gallery.', '7sgaleri' ), 'type' => 'audio', 'source' => 'external_audio', 'url' => 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3', 'category' => 'soundtracks', 'tags' => array( __( 'Music', '7sgaleri' ), __( 'Score', '7sgaleri' ) ), 'featured' => false, ), array( 'key' => 'soundhelix-live-session-3', 'title' => __( 'SoundHelix — Live Session 3', '7sgaleri' ), 'excerpt' => __( 'An audio example for category navigation.', '7sgaleri' ), 'content' => __( 'Open the category menu to see how demo categories and their automatic icons work on desktop and mobile.', '7sgaleri' ), 'type' => 'audio', 'source' => 'external_audio', 'url' => 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3', 'category' => 'live-sessions', 'tags' => array( __( 'Live', '7sgaleri' ), __( 'Session', '7sgaleri' ) ), 'featured' => false, ), array( 'key' => 'soundhelix-live-session-4', 'title' => __( 'SoundHelix — Live Session 4', '7sgaleri' ), 'excerpt' => __( 'A final music card for the demo gallery.', '7sgaleri' ), 'content' => __( 'Try search, sorting, pagination, likes, and the single-content navigation with the complete demo set.', '7sgaleri' ), 'type' => 'audio', 'source' => 'external_audio', 'url' => 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3', 'category' => 'live-sessions', 'tags' => array( __( 'Live', '7sgaleri' ), __( 'Music', '7sgaleri' ) ), 'featured' => false, ), ); } public static function export_package(): void { self::require_manager(); check_admin_referer( 'sg7g_export_package' ); if ( ! class_exists( 'ZipArchive' ) ) { wp_die( esc_html__( 'ZIP support is unavailable on the server.', '7sgaleri' ) ); } $include_views = ! empty( $_POST['include_views'] ); $temp_file = wp_tempnam( '7sgaleri-aktarim.zip' ); if ( ! $temp_file ) { wp_die( esc_html__( 'The temporary export file could not be created.', '7sgaleri' ) ); } $zip = new ZipArchive(); if ( true !== $zip->open( $temp_file, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) { wp_delete_file( $temp_file ); wp_die( esc_html__( 'The ZIP package could not be opened.', '7sgaleri' ) ); } try { $post_ids = get_posts( array( 'post_type' => 'sg7g_media', 'post_status' => array( 'publish', 'draft', 'pending', 'private', 'future' ), 'posts_per_page' => -1, 'fields' => 'ids', 'orderby' => array( 'date' => 'ASC', 'ID' => 'ASC' ), 'no_found_rows' => true, 'ignore_sticky_posts' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); $files = array(); $attachment_paths = array(); $items = array(); $featured_order = SG7G_Settings::featured_order(); $featured_lookup = array_flip( $featured_order ); foreach ( $post_ids as $post_id ) { $post = get_post( (int) $post_id ); if ( ! $post instanceof WP_Post ) { continue; } $portable_id = trim( (string) get_post_meta( $post->ID, '_sg7g_portable_id', true ) ); if ( '' === $portable_id ) { $portable_id = wp_generate_uuid4(); update_post_meta( $post->ID, '_sg7g_portable_id', $portable_id ); } $source = (string) get_post_meta( $post->ID, '_sg7g_source', true ); $file_id = absint( get_post_meta( $post->ID, '_sg7g_file_id', true ) ); $thumbnail_id = absint( get_post_thumbnail_id( $post->ID ) ); $cover_path = $thumbnail_id ? self::add_attachment_to_zip( $zip, $thumbnail_id, 'cover', $files, $attachment_paths ) : ''; $media_path = in_array( $source, array( 'local_video', 'local_audio' ), true ) && $file_id ? self::add_attachment_to_zip( $zip, $file_id, 'media', $files, $attachment_paths ) : ''; if ( in_array( $source, array( 'local_video', 'local_audio' ), true ) && '' === $media_path ) { throw new RuntimeException( sprintf( /* translators: %s is the content title. */ /* translators: %s: Content title. */ esc_html__( 'Export was stopped because the local media file for “%s” could not be read.', '7sgaleri' ), sanitize_text_field( $post->post_title ?: __( '(Untitled)', '7sgaleri' ) ) ) ); } $item = array( 'portable_id' => $portable_id, 'title' => $post->post_title, 'content' => $post->post_content, 'excerpt' => $post->post_excerpt, 'status' => $post->post_status, 'slug' => $post->post_name, 'date' => $post->post_date, 'date_gmt' => $post->post_date_gmt, 'password' => $post->post_password, 'media_type' => (string) get_post_meta( $post->ID, '_sg7g_media_type', true ), 'source' => $source, 'source_url' => (string) get_post_meta( $post->ID, '_sg7g_source_url', true ), 'cover_file' => $cover_path, 'local_media' => $media_path, 'categories' => self::export_terms( $post->ID, 'sg7g_category' ), 'tags' => self::export_terms( $post->ID, 'sg7g_tag' ), 'featured' => 1 === (int) get_post_meta( $post->ID, '_sg7g_featured', true ), 'featured_order'=> isset( $featured_lookup[ $post->ID ] ) ? (int) $featured_lookup[ $post->ID ] : null, ); if ( $include_views ) { $item['views'] = SG7G_Views::get( $post->ID ); } $items[] = $item; } $manifest = array( 'schema' => self::SCHEMA, 'schema_version' => self::SCHEMA_VERSION, 'plugin_version' => SG7G_VERSION, 'package_id' => wp_generate_uuid4(), 'generated_at' => gmdate( 'c' ), 'source_site' => home_url( '/' ), 'include_views' => $include_views, 'content_count' => count( $items ), 'files' => $files, 'items' => $items, ); $json = wp_json_encode( $manifest, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ); if ( ! is_string( $json ) || ! $zip->addFromString( 'manifest.json', $json ) ) { throw new RuntimeException( esc_html__( 'The package manifest could not be created.', '7sgaleri' ) ); } if ( ! $zip->close() ) { throw new RuntimeException( esc_html__( 'The ZIP package could not be finalized.', '7sgaleri' ) ); } $filename = '7sgaleri-aktarim-' . current_time( 'Y-m-d-His' ) . '.zip'; nocache_headers(); header( 'Content-Type: application/zip' ); header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( $filename ) . '"' ); header( 'Content-Length: ' . (string) filesize( $temp_file ) ); header( 'X-Content-Type-Options: nosniff' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Streaming prevents large ZIP files from exhausting PHP memory. readfile( $temp_file ); wp_delete_file( $temp_file ); exit; } catch ( Throwable $error ) { $zip->close(); wp_delete_file( $temp_file ); wp_die( esc_html( $error->getMessage() ) ); } } public static function import_preview(): void { self::require_manager(); check_admin_referer( 'sg7g_import_preview' ); if ( ! class_exists( 'ZipArchive' ) ) { self::redirect_result( 'error', __( 'ZIP support is unavailable on the server.', '7sgaleri' ) ); } if ( empty( $_FILES['sg7g_import_package'] ) || ! is_array( $_FILES['sg7g_import_package'] ) ) { self::redirect_result( 'error', __( 'No ZIP package was selected for upload.', '7sgaleri' ) ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- The upload array is validated field by field below. $upload = $_FILES['sg7g_import_package']; if ( UPLOAD_ERR_OK !== (int) ( $upload['error'] ?? UPLOAD_ERR_NO_FILE ) ) { self::redirect_result( 'error', self::upload_error_message( (int) $upload['error'] ) ); } $filename = sanitize_file_name( (string) ( $upload['name'] ?? '' ) ); if ( 'zip' !== strtolower( (string) pathinfo( $filename, PATHINFO_EXTENSION ) ) ) { self::redirect_result( 'error', __( 'Only compatible gallery ZIP packages can be uploaded.', '7sgaleri' ) ); } $token = strtolower( wp_generate_password( 24, false, false ) ); $dir = self::staging_base() . '/' . $token; if ( ! wp_mkdir_p( $dir ) ) { self::redirect_result( 'error', __( 'The temporary import directory could not be created.', '7sgaleri' ) ); } self::protect_directory( self::staging_base() ); self::protect_directory( $dir ); require_once ABSPATH . 'wp-admin/includes/file.php'; $upload_dir_filter = static function ( array $uploads ) use ( $token ): array { $subdir = '/7sgaleri-imports/' . $token; $uploads['path'] = trailingslashit( $uploads['basedir'] ) . ltrim( $subdir, '/' ); $uploads['url'] = trailingslashit( $uploads['baseurl'] ) . ltrim( $subdir, '/' ); $uploads['subdir'] = $subdir; return $uploads; }; add_filter( 'upload_dir', $upload_dir_filter ); try { $handled_upload = wp_handle_upload( $upload, array( 'test_form' => false, 'mimes' => array( 'zip' => 'application/zip' ), ) ); } finally { remove_filter( 'upload_dir', $upload_dir_filter ); } if ( isset( $handled_upload['error'] ) || empty( $handled_upload['file'] ) ) { self::remove_directory( $dir ); $message = isset( $handled_upload['error'] ) ? sanitize_text_field( (string) $handled_upload['error'] ) : __( 'The uploaded package could not be moved to the secure temporary area.', '7sgaleri' ); self::redirect_result( 'error', $message ); } $package_path = (string) $handled_upload['file']; self::protect_directory( $dir ); $inspection = self::inspect_package( $package_path ); if ( is_wp_error( $inspection ) ) { self::remove_directory( $dir ); self::redirect_result( 'error', $inspection->get_error_message() ); } set_transient( self::staging_key( $token ), array( 'user_id' => get_current_user_id(), 'path' => $package_path, 'dir' => $dir, 'preview' => $inspection['preview'], ), self::TRANSIENT_TTL ); wp_safe_redirect( self::tools_url( array( 'sg7g_import_token' => $token ) ) ); exit; } public static function import_package(): void { self::require_manager(); $token = isset( $_POST['sg7g_import_token'] ) ? sanitize_key( wp_unslash( $_POST['sg7g_import_token'] ) ) : ''; if ( '' === $token ) { self::redirect_result( 'error', __( 'The import session could not be found.', '7sgaleri' ) ); } check_admin_referer( 'sg7g_import_package_' . $token ); $staging = self::get_staging( $token ); if ( ! is_array( $staging ) || empty( $staging['path'] ) || ! is_file( $staging['path'] ) ) { self::redirect_result( 'error', __( 'The import package has expired. Upload the package again.', '7sgaleri' ) ); } $strategy = isset( $_POST['duplicate_strategy'] ) ? sanitize_key( wp_unslash( $_POST['duplicate_strategy'] ) ) : 'skip'; if ( ! in_array( $strategy, array( 'skip', 'update', 'copy' ), true ) ) { $strategy = 'skip'; } $import_views = ! empty( $_POST['import_views'] ); $inspection = self::inspect_package( (string) $staging['path'] ); if ( is_wp_error( $inspection ) ) { self::delete_staging( $token, $staging ); self::redirect_result( 'error', $inspection->get_error_message() ); } $manifest = $inspection['manifest']; $zip = new ZipArchive(); if ( true !== $zip->open( (string) $staging['path'] ) ) { self::delete_staging( $token, $staging ); self::redirect_result( 'error', __( 'The import ZIP package could not be opened.', '7sgaleri' ) ); } $created_posts = array(); $created_attachments = array(); $updated_snapshots = array(); $attachment_cache = array(); $imported_posts = array(); $featured_imports = array(); $old_featured_order = SG7G_Settings::featured_order(); $counts = array( 'created' => 0, 'updated' => 0, 'skipped' => 0 ); self::$existing_index = null; self::$importing = true; try { foreach ( $manifest['items'] as $position => $item ) { $existing_id = self::find_existing( $item, $manifest['files'] ); if ( $existing_id && 'skip' === $strategy ) { ++$counts['skipped']; continue; } $is_update = $existing_id && 'update' === $strategy; if ( $is_update ) { $post_id = $existing_id; if ( ! isset( $updated_snapshots[ $post_id ] ) ) { $updated_snapshots[ $post_id ] = self::snapshot_post( $post_id ); } } else { $post_id = 0; } $post_data = self::post_data_from_item( $item, $post_id ); $post_id = wp_insert_post( wp_slash( $post_data ), true ); if ( is_wp_error( $post_id ) ) { throw new RuntimeException( esc_html( $post_id->get_error_message() ) ); } $post_id = (int) $post_id; if ( ! $is_update ) { $created_posts[] = $post_id; ++$counts['created']; } else { ++$counts['updated']; } $portable_id = 'copy' === $strategy ? wp_generate_uuid4() : sanitize_text_field( (string) ( $item['portable_id'] ?? '' ) ); if ( '' === $portable_id ) { $portable_id = wp_generate_uuid4(); } $type = 'audio' === ( $item['media_type'] ?? '' ) ? 'audio' : 'video'; $source = sanitize_key( (string) ( $item['source'] ?? '' ) ); $source_url = esc_url_raw( (string) ( $item['source_url'] ?? '' ) ); $file_id = 0; if ( in_array( $source, array( 'local_video', 'local_audio' ), true ) ) { $media_path = (string) ( $item['local_media'] ?? '' ); $file_id = self::import_attachment( $zip, $media_path, $post_id, $manifest['files'], $attachment_cache, $created_attachments ); $source_url = ''; } update_post_meta( $post_id, '_sg7g_portable_id', $portable_id ); update_post_meta( $post_id, '_sg7g_media_type', $type ); update_post_meta( $post_id, '_sg7g_source', $source ); update_post_meta( $post_id, '_sg7g_source_url', $source_url ); update_post_meta( $post_id, '_sg7g_file_id', $file_id ); update_post_meta( $post_id, '_sg7g_validation_schema', '4' ); if ( $file_id && ! empty( $item['local_media'] ) && isset( $manifest['files'][ $item['local_media'] ]['sha256'] ) ) { update_post_meta( $post_id, '_sg7g_file_sha256', sanitize_text_field( (string) $manifest['files'][ $item['local_media'] ]['sha256'] ) ); } else { delete_post_meta( $post_id, '_sg7g_file_sha256' ); } $validation = SG7G_Meta_Boxes::validate_source( $type, $source, $source_url, $file_id ); update_post_meta( $post_id, '_sg7g_source_valid', is_wp_error( $validation ) ? 0 : 1 ); if ( is_wp_error( $validation ) ) { update_post_meta( $post_id, '_sg7g_source_error', $validation->get_error_message() ); } else { delete_post_meta( $post_id, '_sg7g_source_error' ); } self::import_terms( $post_id, 'sg7g_category', (array) ( $item['categories'] ?? array() ) ); self::import_terms( $post_id, 'sg7g_tag', (array) ( $item['tags'] ?? array() ) ); $cover_path = (string) ( $item['cover_file'] ?? '' ); if ( '' !== $cover_path ) { $cover_id = self::import_attachment( $zip, $cover_path, $post_id, $manifest['files'], $attachment_cache, $created_attachments ); if ( $cover_id ) { set_post_thumbnail( $post_id, $cover_id ); } } $featured = ! empty( $item['featured'] ); if ( $featured ) { update_post_meta( $post_id, '_sg7g_featured', 1 ); $featured_imports[] = array( 'post_id' => $post_id, 'order' => is_numeric( $item['featured_order'] ?? null ) ? (int) $item['featured_order'] : ( 100000 + (int) $position ), ); } else { delete_post_meta( $post_id, '_sg7g_featured' ); } if ( $import_views && ! empty( $manifest['include_views'] ) && isset( $item['views'] ) ) { $incoming_views = max( 0, (int) $item['views'] ); $views = $is_update ? max( SG7G_Views::get( $post_id ), $incoming_views ) : $incoming_views; update_post_meta( $post_id, '_sg7g_views', $views ); } $imported_posts[] = $post_id; self::$existing_index = null; } usort( $featured_imports, static fn( array $a, array $b ): int => (int) $a['order'] <=> (int) $b['order'] ); $imported_featured_ids = array_column( $featured_imports, 'post_id' ); $merged_order = array_values( array_merge( array_filter( $old_featured_order, static fn( int $id ): bool => ! in_array( $id, $imported_posts, true ) ), $imported_featured_ids ) ); SG7G_Settings::update_featured_order( $merged_order ); self::$importing = false; foreach ( array_unique( $imported_posts ) as $post_id ) { $post = get_post( $post_id ); if ( $post instanceof WP_Post ) { try { SG7G_Default_Cover::ensure( $post_id, $post, true ); } catch ( Throwable $cover_error ) { unset( $cover_error ); } } } $zip->close(); self::delete_staging( $token, $staging ); $message = sprintf( /* translators: 1: created count, 2: updated count, 3: skipped count. */ /* translators: 1: Added item count, 2: Updated item count, 3: Skipped item count. */ __( 'Import completed. %1$d item added, %2$d items updated, and %3$d items skipped.', '7sgaleri' ), $counts['created'], $counts['updated'], $counts['skipped'] ); self::redirect_result( 'success', $message ); } catch ( Throwable $error ) { self::$importing = true; self::rollback_import( $created_posts, $created_attachments, $updated_snapshots, $old_featured_order ); self::$importing = false; $zip->close(); self::delete_staging( $token, $staging ); self::redirect_result( 'error', sprintf( /* translators: %s is the import error. */ /* translators: %s: Error message. */ __( 'Import rolled back: %s', '7sgaleri' ), $error->getMessage() ) ); } finally { self::$importing = false; } } public static function cancel_import(): void { self::require_manager(); $token = isset( $_POST['sg7g_import_token'] ) ? sanitize_key( wp_unslash( $_POST['sg7g_import_token'] ) ) : ''; if ( '' !== $token ) { check_admin_referer( 'sg7g_import_cancel_' . $token ); $staging = self::get_staging( $token ); self::delete_staging( $token, is_array( $staging ) ? $staging : array() ); } wp_safe_redirect( self::tools_url() ); exit; } public static function cleanup_staging(): void { if ( ! current_user_can( 'manage_options' ) || wp_rand( 1, 20 ) !== 1 ) { return; } $base = self::staging_base(); if ( ! is_dir( $base ) ) { return; } $entries = glob( $base . '/*', GLOB_ONLYDIR ); if ( ! is_array( $entries ) ) { return; } foreach ( $entries as $dir ) { $mtime = @filemtime( $dir ); if ( $mtime && ( time() - $mtime ) > DAY_IN_SECONDS ) { self::remove_directory( $dir ); } } } private static function render_preview( string $token, array $preview ): void { $summary = (array) ( $preview['summary'] ?? array() ); $items = (array) ( $preview['items'] ?? array() ); ?>

100 ) : ?>

open( $path ) ) { return new WP_Error( 'sg7g_package_open', __( 'The ZIP package could not be opened.', '7sgaleri' ) ); } try { if ( $zip->numFiles < 1 || $zip->numFiles > self::MAX_ARCHIVE_FILES ) { return new WP_Error( 'sg7g_package_count', __( 'The number of files in the ZIP package is outside the safe limits.', '7sgaleri' ) ); } $expanded = 0; $entries = array(); for ( $index = 0; $index < $zip->numFiles; ++$index ) { $name = (string) $zip->getNameIndex( $index ); if ( ! self::safe_archive_path( $name ) ) { return new WP_Error( 'sg7g_package_path', __( 'An unsafe file path was found in the ZIP package.', '7sgaleri' ) ); } $stat = $zip->statIndex( $index ); if ( ! is_array( $stat ) ) { return new WP_Error( 'sg7g_package_stat', __( 'A file in the ZIP package could not be read.', '7sgaleri' ) ); } $expanded += max( 0, (int) ( $stat['size'] ?? 0 ) ); if ( $expanded > self::MAX_EXPANDED_BYTES ) { return new WP_Error( 'sg7g_package_size', __( 'The extracted ZIP package exceeds the permitted safe size.', '7sgaleri' ) ); } $entries[ $name ] = $stat; } if ( ! isset( $entries['manifest.json'] ) || (int) $entries['manifest.json']['size'] > self::MAX_MANIFEST_BYTES ) { return new WP_Error( 'sg7g_manifest_missing', __( 'A valid manifest.json file could not be found.', '7sgaleri' ) ); } $json = $zip->getFromName( 'manifest.json' ); if ( ! is_string( $json ) ) { return new WP_Error( 'sg7g_manifest_read', __( 'The package manifest could not be read.', '7sgaleri' ) ); } $manifest = json_decode( $json, true ); if ( ! is_array( $manifest ) || JSON_ERROR_NONE !== json_last_error() ) { return new WP_Error( 'sg7g_manifest_json', __( 'The package manifest is not valid JSON.', '7sgaleri' ) ); } $schema_version = (int) ( $manifest['schema_version'] ?? 0 ); if ( self::SCHEMA !== ( $manifest['schema'] ?? '' ) || ! in_array( $schema_version, array( 1, self::SCHEMA_VERSION ), true ) ) { return new WP_Error( 'sg7g_manifest_schema', __( 'This ZIP package does not use a supported gallery transfer format.', '7sgaleri' ) ); } $items = isset( $manifest['items'] ) && is_array( $manifest['items'] ) ? array_values( $manifest['items'] ) : array(); $files = isset( $manifest['files'] ) && is_array( $manifest['files'] ) ? $manifest['files'] : array(); if ( count( $items ) > self::MAX_ITEMS ) { return new WP_Error( 'sg7g_manifest_items', __( 'The package contains more content than permitted.', '7sgaleri' ) ); } foreach ( $files as $file_path => $file_data ) { if ( ! self::safe_archive_path( (string) $file_path ) || ! str_starts_with( (string) $file_path, 'files/' ) ) { return new WP_Error( 'sg7g_file_path', __( 'An unsafe path was found in the package file list.', '7sgaleri' ) ); } if ( ! isset( $entries[ $file_path ] ) || ! is_array( $file_data ) ) { return new WP_Error( 'sg7g_file_missing', __( 'A media file defined in the package manifest could not be found in the ZIP archive.', '7sgaleri' ) ); } $expected_size = max( 0, (int) ( $file_data['size'] ?? 0 ) ); if ( $expected_size !== (int) ( $entries[ $file_path ]['size'] ?? -1 ) ) { return new WP_Error( 'sg7g_file_size', __( 'The size of a media file in the package does not match the manifest.', '7sgaleri' ) ); } $expected_hash = strtolower( (string) ( $file_data['sha256'] ?? '' ) ); if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_hash ) || $expected_hash !== self::zip_entry_hash( $zip, (string) $file_path ) ) { return new WP_Error( 'sg7g_file_hash', __( 'The integrity check for a media file in the package failed.', '7sgaleri' ) ); } } self::$existing_index = null; $summary = array( 'total' => count( $items ), 'new' => 0, 'existing' => 0, 'covers' => 0, 'media' => 0, 'categories' => 0, 'tags' => 0, 'invalid_sources' => 0, ); $category_keys = array(); $tag_keys = array(); $preview_items = array(); foreach ( $items as $item ) { $item_error = self::validate_item( $item, $files ); if ( is_wp_error( $item_error ) ) { return $item_error; } $existing_id = self::find_existing( $item, $files ); if ( $existing_id ) { ++$summary['existing']; } else { ++$summary['new']; } if ( ! empty( $item['cover_file'] ) ) { ++$summary['covers']; } if ( ! empty( $item['local_media'] ) ) { ++$summary['media']; } foreach ( (array) ( $item['categories'] ?? array() ) as $term ) { if ( is_array( $term ) ) { $category_keys[ sanitize_title( (string) ( $term['slug'] ?? $term['name'] ?? '' ) ) ] = true; } } foreach ( (array) ( $item['tags'] ?? array() ) as $term ) { if ( is_array( $term ) ) { $tag_keys[ sanitize_title( (string) ( $term['slug'] ?? $term['name'] ?? '' ) ) ] = true; } } $type = 'audio' === ( $item['media_type'] ?? '' ) ? 'audio' : 'video'; $source = sanitize_key( (string) ( $item['source'] ?? '' ) ); $source_url = esc_url_raw( (string) ( $item['source_url'] ?? '' ) ); if ( in_array( $source, array( 'local_video', 'local_audio' ), true ) ) { $media_path = (string) ( $item['local_media'] ?? '' ); $mime = isset( $files[ $media_path ]['mime'] ) ? (string) $files[ $media_path ]['mime'] : ''; $expected = 'local_audio' === $source ? 'audio/' : 'video/'; $invalid = '' === $media_path || ! str_starts_with( $mime, $expected ); } else { $validation = SG7G_Meta_Boxes::validate_source( $type, $source, $source_url, 0 ); $invalid = is_wp_error( $validation ); } if ( $invalid ) { ++$summary['invalid_sources']; } $preview_items[] = array( 'title' => (string) ( $item['title'] ?? '' ), 'type_label' => 'audio' === $type ? __( 'Audio / Music', '7sgaleri' ) : __( 'Video', '7sgaleri' ), 'source_label' => SG7G_Meta_Boxes::source_label( $source ), 'post_status' => self::status_label( (string) ( $item['status'] ?? 'draft' ) ), 'existing' => (bool) $existing_id, 'invalid_source' => $invalid, ); } $summary['categories'] = count( array_filter( array_keys( $category_keys ) ) ); $summary['tags'] = count( array_filter( array_keys( $tag_keys ) ) ); return array( 'manifest' => $manifest, 'preview' => array( 'plugin_version' => sanitize_text_field( (string) ( $manifest['plugin_version'] ?? '—' ) ), 'generated_at' => sanitize_text_field( (string) ( $manifest['generated_at'] ?? '—' ) ), 'include_views' => ! empty( $manifest['include_views'] ), 'summary' => $summary, 'items' => $preview_items, ), ); } finally { $zip->close(); } } private static function validate_item( mixed $item, array $files ): bool|WP_Error { if ( ! is_array( $item ) ) { return new WP_Error( 'sg7g_item_format', __( 'An invalid content record was found in the package.', '7sgaleri' ) ); } $type = (string) ( $item['media_type'] ?? '' ); if ( ! in_array( $type, array( 'video', 'audio' ), true ) ) { return new WP_Error( 'sg7g_item_type', __( 'An unsupported content type was found in the package.', '7sgaleri' ) ); } $source = sanitize_key( (string) ( $item['source'] ?? '' ) ); $sources = SG7G_Meta_Boxes::sources(); if ( ! isset( $sources[ $type ][ $source ] ) ) { return new WP_Error( 'sg7g_item_source', __( 'An unsupported media source was found in the package.', '7sgaleri' ) ); } foreach ( array( 'cover_file', 'local_media' ) as $file_key ) { $file_path = (string) ( $item[ $file_key ] ?? '' ); if ( '' !== $file_path && ( ! isset( $files[ $file_path ] ) || ! self::safe_archive_path( $file_path ) ) ) { return new WP_Error( 'sg7g_item_file', __( 'An item references a media file that is not included in the package.', '7sgaleri' ) ); } } if ( in_array( $source, array( 'local_video', 'local_audio' ), true ) && empty( $item['local_media'] ) ) { return new WP_Error( 'sg7g_item_local', __( 'The file for a local media source is missing from the package.', '7sgaleri' ) ); } return true; } private static function add_attachment_to_zip( ZipArchive $zip, int $attachment_id, string $kind, array &$files, array &$paths ): string { if ( isset( $paths[ $attachment_id ] ) ) { return $paths[ $attachment_id ]; } $file = get_attached_file( $attachment_id ); if ( ! $file || ! is_file( $file ) || ! is_readable( $file ) ) { return ''; } $hash = hash_file( 'sha256', $file ); if ( ! is_string( $hash ) || '' === $hash ) { return ''; } $filename = sanitize_file_name( wp_basename( $file ) ); $path = 'files/' . substr( $hash, 0, 16 ) . '-' . $attachment_id . '-' . $filename; if ( ! $zip->addFile( $file, $path ) ) { throw new RuntimeException( esc_html__( 'A media file could not be added to the ZIP package.', '7sgaleri' ) ); } $attachment = get_post( $attachment_id ); $files[ $path ] = array( 'kind' => $kind, 'original_name' => $filename, 'mime' => (string) get_post_mime_type( $attachment_id ), 'size' => (int) filesize( $file ), 'sha256' => $hash, 'title' => $attachment instanceof WP_Post ? $attachment->post_title : '', 'caption' => $attachment instanceof WP_Post ? $attachment->post_excerpt : '', 'description' => $attachment instanceof WP_Post ? $attachment->post_content : '', 'alt' => (string) get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), 'generated' => (bool) get_post_meta( $attachment_id, '_sg7g_generated_cover', true ), ); $paths[ $attachment_id ] = $path; return $path; } private static function export_terms( int $post_id, string $taxonomy ): array { $terms = wp_get_post_terms( $post_id, $taxonomy ); if ( is_wp_error( $terms ) ) { return array(); } $output = array(); foreach ( $terms as $term ) { $parents = array(); if ( 'sg7g_category' === $taxonomy && $term->parent ) { $ancestor_ids = array_reverse( get_ancestors( $term->term_id, $taxonomy, 'taxonomy' ) ); foreach ( $ancestor_ids as $ancestor_id ) { $ancestor = get_term( $ancestor_id, $taxonomy ); if ( $ancestor instanceof WP_Term ) { $parents[] = array( 'name' => $ancestor->name, 'slug' => $ancestor->slug, 'description' => $ancestor->description, ); } } } $output[] = array( 'name' => $term->name, 'slug' => $term->slug, 'description' => $term->description, 'parents' => $parents, ); } return $output; } private static function import_terms( int $post_id, string $taxonomy, array $terms ): void { $term_ids = array(); foreach ( $terms as $term_data ) { if ( ! is_array( $term_data ) ) { continue; } $parent_id = 0; if ( 'sg7g_category' === $taxonomy ) { foreach ( (array) ( $term_data['parents'] ?? array() ) as $parent_data ) { if ( is_array( $parent_data ) ) { $parent_id = self::ensure_term( $taxonomy, $parent_data, $parent_id ); } } } $term_id = self::ensure_term( $taxonomy, $term_data, $parent_id ); if ( $term_id ) { $term_ids[] = $term_id; } } $result = wp_set_object_terms( $post_id, array_values( array_unique( $term_ids ) ), $taxonomy, false ); if ( is_wp_error( $result ) ) { throw new RuntimeException( esc_html( $result->get_error_message() ) ); } } private static function ensure_term( string $taxonomy, array $data, int $parent_id = 0 ): int { $name = sanitize_text_field( (string) ( $data['name'] ?? '' ) ); $slug = sanitize_title( (string) ( $data['slug'] ?? $name ) ); if ( '' === $name && '' === $slug ) { return 0; } $existing = $slug ? get_term_by( 'slug', $slug, $taxonomy ) : false; if ( $existing instanceof WP_Term ) { return (int) $existing->term_id; } $created = wp_insert_term( $name ?: $slug, $taxonomy, array( 'slug' => $slug, 'description' => sanitize_textarea_field( (string) ( $data['description'] ?? '' ) ), 'parent' => 'sg7g_category' === $taxonomy ? $parent_id : 0, ) ); if ( is_wp_error( $created ) ) { if ( 'term_exists' === $created->get_error_code() ) { $existing_data = $created->get_error_data(); if ( is_array( $existing_data ) && isset( $existing_data['term_id'] ) ) { return (int) $existing_data['term_id']; } return (int) $existing_data; } throw new RuntimeException( esc_html( $created->get_error_message() ) ); } return (int) $created['term_id']; } private static function import_attachment( ZipArchive $zip, string $path, int $post_id, array $files, array &$cache, array &$created_attachments ): int { if ( '' === $path || ! isset( $files[ $path ] ) ) { return 0; } if ( isset( $cache[ $path ] ) ) { return (int) $cache[ $path ]; } $file_data = $files[ $path ]; $hash = sanitize_text_field( (string) ( $file_data['sha256'] ?? '' ) ); if ( $hash ) { $existing = get_posts( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Exact lookup for one attachment transfer hash. 'meta_key' => '_sg7g_transfer_sha256', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Exact lookup for one attachment transfer hash. 'meta_value' => $hash, 'no_found_rows' => true, ) ); if ( ! empty( $existing ) && get_attached_file( (int) $existing[0] ) && is_file( (string) get_attached_file( (int) $existing[0] ) ) ) { $cache[ $path ] = (int) $existing[0]; return (int) $existing[0]; } } // ZipArchive exposes a PHP stream; copy it incrementally to avoid holding media files in memory. // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fwrite, WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Required for ZipArchive stream handling. $stream = $zip->getStream( $path ); if ( ! is_resource( $stream ) ) { throw new RuntimeException( esc_html__( 'A media file in the package could not be opened.', '7sgaleri' ) ); } $original_name = sanitize_file_name( (string) ( $file_data['original_name'] ?? wp_basename( $path ) ) ); $temp_file = wp_tempnam( $original_name ); if ( ! $temp_file ) { fclose( $stream ); throw new RuntimeException( esc_html__( 'A temporary area could not be created for the media file.', '7sgaleri' ) ); } $output = fopen( $temp_file, 'wb' ); if ( ! is_resource( $output ) ) { fclose( $stream ); wp_delete_file( $temp_file ); throw new RuntimeException( esc_html__( 'The temporary media file could not be written.', '7sgaleri' ) ); } $hash_context = hash_init( 'sha256' ); while ( ! feof( $stream ) ) { $chunk = fread( $stream, 1048576 ); if ( false === $chunk ) { fclose( $stream ); fclose( $output ); wp_delete_file( $temp_file ); throw new RuntimeException( esc_html__( 'The media file in the package could not be read.', '7sgaleri' ) ); } if ( '' === $chunk ) { fclose( $stream ); fclose( $output ); wp_delete_file( $temp_file ); throw new RuntimeException( esc_html__( 'The media stream in the package stopped unexpectedly.', '7sgaleri' ) ); } hash_update( $hash_context, $chunk ); if ( strlen( $chunk ) !== fwrite( $output, $chunk ) ) { fclose( $stream ); fclose( $output ); wp_delete_file( $temp_file ); throw new RuntimeException( esc_html__( 'The temporary media file could not be written.', '7sgaleri' ) ); } } fclose( $stream ); fclose( $output ); $actual_hash = hash_final( $hash_context ); if ( ! hash_equals( $hash, $actual_hash ) ) { wp_delete_file( $temp_file ); throw new RuntimeException( esc_html__( 'The integrity check for the extracted media file failed.', '7sgaleri' ) ); } require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; $file_array = array( 'name' => $original_name, 'tmp_name' => $temp_file, 'error' => 0, 'size' => filesize( $temp_file ), ); $attachment_id = media_handle_sideload( $file_array, $post_id, sanitize_text_field( (string) ( $file_data['title'] ?? '' ) ) ); if ( is_wp_error( $attachment_id ) ) { wp_delete_file( $temp_file ); throw new RuntimeException( esc_html( $attachment_id->get_error_message() ) ); } $attachment_id = (int) $attachment_id; $created_attachments[] = $attachment_id; wp_update_post( wp_slash( array( 'ID' => $attachment_id, 'post_title' => sanitize_text_field( (string) ( $file_data['title'] ?? $original_name ) ), 'post_excerpt' => sanitize_textarea_field( (string) ( $file_data['caption'] ?? '' ) ), 'post_content' => wp_kses_post( (string) ( $file_data['description'] ?? '' ) ), ) ) ); update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( (string) ( $file_data['alt'] ?? '' ) ) ); update_post_meta( $attachment_id, '_sg7g_transfer_sha256', $hash ); $cache[ $path ] = $attachment_id; // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fwrite, WordPress.WP.AlternativeFunctions.file_system_operations_fclose return $attachment_id; } private static function post_data_from_item( array $item, int $post_id = 0 ): array { $allowed_statuses = array( 'publish', 'draft', 'pending', 'private', 'future' ); $status = sanitize_key( (string) ( $item['status'] ?? 'draft' ) ); if ( ! in_array( $status, $allowed_statuses, true ) ) { $status = 'draft'; } $date = self::sanitize_mysql_date( (string) ( $item['date'] ?? '' ) ); $date_gmt = self::sanitize_mysql_date( (string) ( $item['date_gmt'] ?? '' ) ); $data = array( 'post_type' => 'sg7g_media', 'post_title' => sanitize_text_field( (string) ( $item['title'] ?? '' ) ), 'post_content' => wp_kses_post( (string) ( $item['content'] ?? '' ) ), 'post_excerpt' => sanitize_textarea_field( (string) ( $item['excerpt'] ?? '' ) ), 'post_status' => $status, 'post_name' => sanitize_title( (string) ( $item['slug'] ?? '' ) ), 'post_password' => sanitize_text_field( (string) ( $item['password'] ?? '' ) ), 'post_author' => get_current_user_id(), ); if ( $post_id ) { $data['ID'] = $post_id; } if ( $date ) { $data['post_date'] = $date; } if ( $date_gmt ) { $data['post_date_gmt'] = $date_gmt; } return $data; } private static function find_existing( array $item, array $files ): int { $index = self::existing_index(); $portable_id = sanitize_text_field( (string) ( $item['portable_id'] ?? '' ) ); if ( $portable_id && isset( $index['portable'][ $portable_id ] ) ) { return (int) $index['portable'][ $portable_id ]; } $fingerprint = self::source_fingerprint( sanitize_key( (string) ( $item['source'] ?? '' ) ), esc_url_raw( (string) ( $item['source_url'] ?? '' ) ) ); if ( $fingerprint && isset( $index['source'][ $fingerprint ] ) ) { return (int) $index['source'][ $fingerprint ]; } $media_path = (string) ( $item['local_media'] ?? '' ); $hash = $media_path && isset( $files[ $media_path ]['sha256'] ) ? strtolower( (string) $files[ $media_path ]['sha256'] ) : ''; if ( $hash && isset( $index['file'][ $hash ] ) ) { return (int) $index['file'][ $hash ]; } return 0; } private static function existing_index(): array { if ( null !== self::$existing_index ) { return self::$existing_index; } $index = array( 'portable' => array(), 'source' => array(), 'file' => array() ); $ids = get_posts( array( 'post_type' => 'sg7g_media', 'post_status' => array( 'publish', 'draft', 'pending', 'private', 'future' ), 'posts_per_page' => -1, 'fields' => 'ids', 'no_found_rows' => true, ) ); foreach ( $ids as $post_id ) { $post_id = (int) $post_id; $portable = trim( (string) get_post_meta( $post_id, '_sg7g_portable_id', true ) ); if ( $portable ) { $index['portable'][ $portable ] = $post_id; } $source = (string) get_post_meta( $post_id, '_sg7g_source', true ); $url = (string) get_post_meta( $post_id, '_sg7g_source_url', true ); $fingerprint = self::source_fingerprint( $source, $url ); if ( $fingerprint ) { $index['source'][ $fingerprint ] = $post_id; } $hash = strtolower( trim( (string) get_post_meta( $post_id, '_sg7g_file_sha256', true ) ) ); if ( ! $hash ) { $file_id = absint( get_post_meta( $post_id, '_sg7g_file_id', true ) ); $file = $file_id ? get_attached_file( $file_id ) : ''; if ( $file && is_file( $file ) && is_readable( $file ) ) { $hash = (string) hash_file( 'sha256', $file ); if ( $hash ) { update_post_meta( $post_id, '_sg7g_file_sha256', $hash ); } } } if ( $hash ) { $index['file'][ $hash ] = $post_id; } } self::$existing_index = $index; return $index; } private static function source_fingerprint( string $source, string $url ): string { $url = trim( $url ); if ( '' === $url || in_array( $source, array( 'local_video', 'local_audio' ), true ) ) { return ''; } if ( in_array( $source, array( 'youtube', 'youtube_music' ), true ) ) { $id = SG7G_Meta_Boxes::youtube_id( $url ); return $id ? 'youtube:' . strtolower( $id ) : ''; } if ( 'vimeo' === $source && preg_match( '~(?:video/)?(\d+)~', (string) wp_parse_url( $url, PHP_URL_PATH ), $matches ) ) { return 'vimeo:' . $matches[1]; } if ( 'spotify' === $source ) { $path = trim( (string) wp_parse_url( $url, PHP_URL_PATH ), '/' ); $parts = array_values( array_filter( explode( '/', $path ) ) ); $parts = array_values( array_filter( $parts, static fn( string $part ): bool => ! in_array( strtolower( $part ), array( 'embed', 'intl-tr', 'intl-en' ), true ) ) ); if ( count( $parts ) >= 2 ) { return 'spotify:' . strtolower( $parts[0] . ':' . $parts[1] ); } } $host = strtolower( preg_replace( '/^www\./', '', (string) wp_parse_url( $url, PHP_URL_HOST ) ) ); $path = untrailingslashit( strtolower( rawurldecode( (string) wp_parse_url( $url, PHP_URL_PATH ) ) ) ); return $host && $path ? $source . ':' . $host . $path : ''; } private static function snapshot_post( int $post_id ): array { $post = get_post( $post_id, ARRAY_A ); if ( ! is_array( $post ) ) { throw new RuntimeException( esc_html__( 'The content to be updated could not be read.', '7sgaleri' ) ); } return array( 'post' => $post, 'meta' => get_post_meta( $post_id ), 'categories' => wp_get_object_terms( $post_id, 'sg7g_category', array( 'fields' => 'ids' ) ), 'tags' => wp_get_object_terms( $post_id, 'sg7g_tag', array( 'fields' => 'ids' ) ), ); } private static function rollback_import( array $created_posts, array $created_attachments, array $snapshots, array $featured_order ): void { foreach ( array_reverse( array_unique( array_map( 'absint', $created_posts ) ) ) as $post_id ) { wp_delete_post( $post_id, true ); } foreach ( array_reverse( array_unique( array_map( 'absint', $created_attachments ) ) ) as $attachment_id ) { if ( get_post( $attachment_id ) ) { wp_delete_attachment( $attachment_id, true ); } } foreach ( $snapshots as $post_id => $snapshot ) { if ( empty( $snapshot['post'] ) || ! is_array( $snapshot['post'] ) ) { continue; } $post_data = $snapshot['post']; $post_data['ID'] = (int) $post_id; wp_update_post( wp_slash( $post_data ) ); $current_meta = get_post_meta( (int) $post_id ); foreach ( array_keys( $current_meta ) as $key ) { delete_post_meta( (int) $post_id, (string) $key ); } foreach ( (array) ( $snapshot['meta'] ?? array() ) as $key => $values ) { foreach ( (array) $values as $value ) { add_post_meta( (int) $post_id, (string) $key, maybe_unserialize( $value ) ); } } wp_set_object_terms( (int) $post_id, array_map( 'absint', (array) ( $snapshot['categories'] ?? array() ) ), 'sg7g_category', false ); wp_set_object_terms( (int) $post_id, array_map( 'absint', (array) ( $snapshot['tags'] ?? array() ) ), 'sg7g_tag', false ); clean_post_cache( (int) $post_id ); } SG7G_Settings::update_featured_order( $featured_order ); } private static function zip_entry_hash( ZipArchive $zip, string $path ): string { $stream = $zip->getStream( $path ); if ( ! is_resource( $stream ) ) { return ''; } $context = hash_init( 'sha256' ); hash_update_stream( $context, $stream ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing a ZipArchive stream returned by getStream(). fclose( $stream ); return hash_final( $context ); } private static function safe_archive_path( string $path ): bool { if ( '' === $path || str_contains( $path, "\0" ) || str_contains( $path, '\\' ) ) { return false; } if ( str_starts_with( $path, '/' ) || preg_match( '/^[a-zA-Z]:/', $path ) ) { return false; } $parts = explode( '/', $path ); return ! in_array( '..', $parts, true ); } private static function staging_base(): string { $uploads = wp_upload_dir(); return trailingslashit( $uploads['basedir'] ) . '7sgaleri-imports'; } private static function protect_directory( string $dir ): void { if ( ! is_dir( $dir ) ) { return; } require_once ABSPATH . 'wp-admin/includes/file.php'; global $wp_filesystem; if ( ! WP_Filesystem() || ! $wp_filesystem instanceof WP_Filesystem_Base ) { return; } $index = trailingslashit( $dir ) . 'index.php'; if ( ! $wp_filesystem->exists( $index ) ) { $wp_filesystem->put_contents( $index, "exists( $htaccess ) ) { $wp_filesystem->put_contents( $htaccess, "Require all denied Deny from all ", FS_CHMOD_FILE ); } } private static function get_staging( string $token ): ?array { $staging = get_transient( self::staging_key( $token ) ); if ( ! is_array( $staging ) || (int) ( $staging['user_id'] ?? 0 ) !== get_current_user_id() ) { return null; } return $staging; } private static function delete_staging( string $token, array $staging ): void { delete_transient( self::staging_key( $token ) ); if ( ! empty( $staging['dir'] ) ) { self::remove_directory( (string) $staging['dir'] ); } elseif ( ! empty( $staging['path'] ) ) { wp_delete_file( (string) $staging['path'] ); } } private static function remove_directory( string $dir ): void { if ( ! is_dir( $dir ) ) { return; } require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; $filesystem = new WP_Filesystem_Direct( null ); $filesystem->delete( $dir, true, 'd' ); } private static function staging_key( string $token ): string { return 'sg7g_imp_' . get_current_user_id() . '_' . $token; } private static function result_key( string $token ): string { return 'sg7g_result_' . get_current_user_id() . '_' . $token; } private static function redirect_result( string $type, string $message ): void { $token = strtolower( wp_generate_password( 20, false, false ) ); set_transient( self::result_key( $token ), array( 'type' => $type, 'message' => sanitize_text_field( $message ) ), 300 ); wp_safe_redirect( self::tools_url( array( 'sg7g_result' => $token ) ) ); exit; } private static function render_result_notice( mixed $result ): void { if ( ! is_array( $result ) || empty( $result['message'] ) ) { return; } $type = 'success' === ( $result['type'] ?? '' ) ? 'success' : 'error'; printf( '

%2$s

', esc_attr( $type ), esc_html( (string) $result['message'] ) ); } private static function tools_url( array $args = array() ): string { $url = add_query_arg( array_merge( array( 'post_type' => 'sg7g_media', 'page' => 'sg7g-settings' ), $args ), admin_url( 'edit.php' ) ); return $url . '#sg7g-transfer'; } private static function require_manager(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', '7sgaleri' ) ); } } private static function summary_box( string $label, int|string $value ): void { ?>
label : $status; } private static function sanitize_mysql_date( string $date ): string { return preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $date ) ? $date : ''; } private static function upload_error_message( int $code ): string { return match ( $code ) { UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => __( 'The ZIP package exceeds the file size allowed by the server.', '7sgaleri' ), UPLOAD_ERR_PARTIAL => __( 'The ZIP package was only partially uploaded.', '7sgaleri' ), UPLOAD_ERR_NO_FILE => __( 'No ZIP package was selected for upload.', '7sgaleri' ), UPLOAD_ERR_NO_TMP_DIR => __( 'The server temporary upload directory is unavailable.', '7sgaleri' ), UPLOAD_ERR_CANT_WRITE => __( 'The ZIP package could not be written to the server disk.', '7sgaleri' ), UPLOAD_ERR_EXTENSION => __( 'A server extension stopped the ZIP upload.', '7sgaleri' ), default => __( 'An unknown error occurred while uploading the ZIP package.', '7sgaleri' ), }; } }