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() );
?>
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(
'',
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' ),
};
}
}