JezK
Edit File: ImageCopier.php
<?php namespace GeminiLabs\SiteReviews\Integrations\MultilingualPress; use GeminiLabs\SiteReviews\Database; use GeminiLabs\SiteReviews\Database\Query; use GeminiLabs\SiteReviews\Helpers\Cast; use GeminiLabs\SiteReviews\Helpers\Str; use Inpsyde\MultilingualPress\Editor\Notices\ExistingAttachmentsNotice; use Inpsyde\MultilingualPress\Framework\Filesystem; use Inpsyde\MultilingualPress\Framework\SwitchSiteTrait; use Inpsyde\MultilingualPress\TranslationUi\Post\RelationshipContext; class ImageCopier { use SwitchSiteTrait; protected RelationshipContext $context; protected ExistingAttachmentsNotice $existingAttachmentsNotice; protected Filesystem $filesystem; public function __construct( Filesystem $filesystem, ExistingAttachmentsNotice $existingAttachmentsNotice ) { $this->existingAttachmentsNotice = $existingAttachmentsNotice; $this->filesystem = $filesystem; } /** * Retrieve the attachment id of the existing attachment based on the file name. */ public function attachmentIdFromPath(string $attachmentPath): int { return $this->existingAttachmentId($attachmentPath); } /** * Copy attachments from source site to the give remote site using a list of attachment ids. */ public function copyById(int $sourceSiteId, int $remoteSiteId, array $sourceAttachmentIds): array { $this->context = new RelationshipContext([ RelationshipContext::REMOTE_SITE_ID => $remoteSiteId, RelationshipContext::SOURCE_SITE_ID => $sourceSiteId, ]); return $this->copyAttachmentsFromSource($sourceAttachmentIds); } /** * Copy attachments from source site to the give remote site using a list of attachment ids. */ public function copyByIdWithContext(RelationshipContext $context, array $sourceAttachmentIds): array { $this->context = $context; return $this->copyAttachmentsFromSource($sourceAttachmentIds); } /** * Retrieve the meta by the given attachment post. */ protected function attachmentMeta(\WP_Post $attachment): array { $altMeta = (string) get_post_meta($attachment->ID, '_wp_attachment_image_alt', true); $sourceUrl = (string) get_post_meta($attachment->ID, '_source_url', true); return array_filter([ '_source_url' => sanitize_url($sourceUrl), // this will exist if the image was imported from a CSV file '_wp_attachment_image_alt' => sanitize_text_field($altMeta), ]); } protected function copyAttachmentIds(array $sourceAttachmentIds): array { $sourceAttachmentIds = $this->ensureAttachmentIds($sourceAttachmentIds); $sourceAttachments = $this->sourceAttachments($sourceAttachmentIds); return $sourceAttachments ? $this->copyToRemoteSite(...$sourceAttachments) : []; } protected function copyAttachmentsFromSource(array $sourceAttachmentIds): array { $attachmentIds = []; $reviewAttachmentIds = []; if (class_exists('GeminiLabs\SiteReviews\Addon\Images\Uploader')) { $reviewAttachmentIds = array_filter($sourceAttachmentIds, fn ($attachmentId) => str_contains((string) get_attached_file($attachmentId, true), 'site-reviews/') ); if (!empty($reviewAttachmentIds)) { glsr('Addon\Images\Uploader')->setUploadPath(glsr()->id); glsr('Addon\Images\Uploader')->setIntermediateImageSizes(); $attachmentIds = $this->copyAttachmentIds($reviewAttachmentIds); glsr('Addon\Images\Uploader')->resetIntermediateImageSizes(); glsr('Addon\Images\Uploader')->resetUploadPath(); } } if ($otherAttachmentIds = array_diff($sourceAttachmentIds, $reviewAttachmentIds)) { $copiedAttachmentIds = $this->copyAttachmentIds($otherAttachmentIds); return array_merge($attachmentIds, $copiedAttachmentIds); } return $attachmentIds; } /** * Copy the attachment meta from the source give post to the remote attachment. */ protected function copyMetaFromSourceAttachment( AttachmentData $sourceAttachmentData, int $remoteAttachmentId ): void { foreach ($sourceAttachmentData->meta() as $attachmentKey => $sourceAttachmentMeta) { update_post_meta($remoteAttachmentId, $attachmentKey, $sourceAttachmentMeta); } } /** * Copy the attachment sizes from the given source post to the remote site. */ protected function copySizesFromSourceAttachment( AttachmentData $sourceAttachmentData, int $remoteAttachmentId ): void { $remoteUploadPath = wp_upload_dir()['path'] ?? ''; $sourceUploadPath = dirname($sourceAttachmentData->filePath()); $sourceAttachmentBasename = wp_basename($sourceAttachmentData->filePath()); $remoteAttachmentRealPath = "{$remoteUploadPath}/{$sourceAttachmentBasename}"; $metadata = $sourceAttachmentData->fileMeta(); $sizes = wp_get_registered_image_subsizes(); $sizes = apply_filters('intermediate_image_sizes_advanced', $sizes, $metadata, $remoteAttachmentId); $copiedSizes = []; foreach (($metadata['sizes'] ?? []) as $size => $meta) { if (!array_key_exists($size, $sizes)) { continue; } $sourceFile = "{$sourceUploadPath}/{$meta['file']}"; $remoteFile = "{$remoteUploadPath}/{$meta['file']}"; if ($this->filesystem->pathExists($remoteFile) && $this->filesystem->isReadable($remoteFile)) { $copiedSizes[$size] = $meta; continue; } if ($this->filesystem->copy($sourceFile, $remoteFile)) { $copiedSizes[$size] = $meta; continue; } $meta = image_make_intermediate_size( $remoteAttachmentRealPath, $sizes[$size]['width'] ?? null, $sizes[$size]['height'] ?? null, $sizes[$size]['crop'] ?? false ); if ($meta) { $copiedSizes[$size] = $meta; } } $metadata['sizes'] = $copiedSizes; if (empty($metadata['sizes'])) { $metadata = wp_generate_attachment_metadata($remoteAttachmentId, $remoteAttachmentRealPath); } wp_update_attachment_metadata($remoteAttachmentId, $metadata); } /** * Copy attachment file to the remote upload dir and create a new attachment post. */ protected function copyToRemoteSite(AttachmentData ...$sourceAttachmentsData): array { $originalSite = $this->maybeSwitchSite($this->context->remoteSiteId()); $remoteAttachments = []; $uploadDir = wp_upload_dir(); $uploadPath = $uploadDir['path'] ?? ''; $uploadUrl = $uploadDir['url'] ?? ''; if (!$uploadPath || !$uploadUrl || !$this->filesystem->mkDirP($uploadDir['path'])) { $this->maybeRestoreSite($originalSite); return []; } foreach ($sourceAttachmentsData as $sourceAttachmentData) { $sourceAttachmentPath = $sourceAttachmentData->filePath(); $sourceAttachmentBasename = wp_basename($sourceAttachmentPath); if ($existingAttachmentId = $this->existingAttachmentId($sourceAttachmentData->relativePath())) { $updatedId = $this->updateAttachment($sourceAttachmentData, $existingAttachmentId); if ($updatedId) { $remoteAttachments[] = $updatedId; } continue; } $remoteAttachmentRealPath = "{$uploadPath}/{$sourceAttachmentBasename}"; $remoteAttachmentUrl = "{$uploadUrl}/{$sourceAttachmentBasename}"; if (!$this->filesystem->pathExists($sourceAttachmentPath) || !$this->filesystem->isReadable($sourceAttachmentPath)) { continue; } if (!$this->filesystem->copy($sourceAttachmentPath, $remoteAttachmentRealPath)) { continue; } $remoteAttachments[] = $this->createAttachmentPostByPath( $sourceAttachmentData, $remoteAttachmentRealPath, $remoteAttachmentUrl ); } $this->maybeRestoreSite($originalSite); return array_filter($remoteAttachments); } /** * Create an attachment post by the attachment path. */ protected function createAttachmentPostByPath( AttachmentData $sourceAttachmentData, string $remoteAttachmentRealPath, string $remoteAttachmentUrl ): int { $filetype = wp_check_filetype($remoteAttachmentRealPath); $sourceAttachment = $sourceAttachmentData->post(); $remoteAttachmentData = [ 'guid' => $remoteAttachmentUrl, 'menu_order' => $sourceAttachment->menu_order, 'post_author' => get_current_user_id(), 'post_content' => $sourceAttachment->post_content, 'post_excerpt' => $sourceAttachment->post_excerpt, 'post_mime_type' => $filetype['type'] ?? '', 'post_title' => $sourceAttachment->post_title, ]; $this->requireAttachmentFunctions(); $remoteAttachmentId = wp_insert_attachment( $remoteAttachmentData, $remoteAttachmentRealPath, $this->context->remotePostId(), // attach to the remote post ); if ($remoteAttachmentId instanceof \WP_Error) { return 0; } $this->copyMetaFromSourceAttachment( $sourceAttachmentData, $remoteAttachmentId ); $this->copySizesFromSourceAttachment( $sourceAttachmentData, $remoteAttachmentId ); return $remoteAttachmentId; } /** * Ensure attachment ids are valid integer values. */ protected function ensureAttachmentIds(array $attachmentIds): array { return array_filter(wp_parse_id_list($attachmentIds)); } /** * Retrieve the attachment id of the existing attachment based on the file name. */ protected function existingAttachmentId(string $attachmentPath): int { $relativeAttachmentPath = Str::removePrefix( $attachmentPath, trailingslashit(wp_upload_dir()['basedir'] ?? '') ); $sql = glsr(Query::class)->sql(" SELECT post_id FROM table|postmeta WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s ", "%{$relativeAttachmentPath}"); return Cast::toInt(glsr(Database::class)->dbGetVar($sql)); } /** * Check if an attachment post is a valid attachment. */ protected function isLocalAttachment(\WP_Post $attachment): bool { return is_local_attachment((string) get_permalink($attachment)); } /** * Require functions to work with attachments. */ protected function requireAttachmentFunctions(): void { if (!\function_exists('wp_generate_attachment_metadata') && !\function_exists('MultilingualPress\Vendor\wp_generate_attachment_metadata')) { require_once \ABSPATH.'wp-admin/includes/image.php'; } if (!\function_exists('get_intermediate_image_sizes')) { require_once \ABSPATH.'wp-admin/includes/media.php'; } } /** * Retrieve the attachments post and files path * The items contains the post and the real path of the attachment. * * @param int[] $attachmentIds * * @return AttachmentData[] */ protected function sourceAttachments(array $attachmentIds): array { $sourceAttachments = []; foreach ($attachmentIds as $attachmentId) { $attachment = get_post($attachmentId); if (!$attachment instanceof \WP_Post || !$this->isLocalAttachment($attachment)) { continue; } $attachmentPath = get_attached_file($attachmentId, true); // unfiltered if (!$attachmentPath) { continue; } $relativeAttachmentPath = Str::removePrefix( $attachmentPath, trailingslashit(wp_upload_dir()['basedir'] ?? '') ); $sourceAttachments[$attachmentId] = new AttachmentData( $attachment, $this->attachmentMeta($attachment), $attachmentPath, $relativeAttachmentPath ); } return $sourceAttachments; } /** * Update the remote attachment post meta data with data provided by the given source attachment. */ protected function updateAttachment( AttachmentData $sourceAttachmentData, int $remoteAttachmentId ): int { $data = $sourceAttachmentData->post()->to_array(); $data['ID'] = $remoteAttachmentId; $data['post_parent'] = $this->context->remotePostId(); // attach to the remote post $attachmentId = wp_update_post($data, true); if (is_wp_error($attachmentId)) { $this->existingAttachmentsNotice->addAttachment( $sourceAttachmentData->post()->ID, $this->context->remoteSiteId() ); return 0; } $this->copyMetaFromSourceAttachment( $sourceAttachmentData, $remoteAttachmentId ); $this->copySizesFromSourceAttachment( $sourceAttachmentData, $remoteAttachmentId ); return $attachmentId; } }