Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 105 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
SiteGenImageService | |
0.00% |
0 / 105 |
|
0.00% |
0 / 4 |
1056 | |
0.00% |
0 / 1 |
process_homepage_images_immediate_async | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
upload_images_to_wp_media_library | |
0.00% |
0 / 65 |
|
0.00% |
0 / 1 |
342 | |||
update_post_content_with_new_image_urls | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
56 | |||
connect_to_filesystem | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace NewfoldLabs\WP\Module\Onboarding\Services; |
4 | |
5 | use NewfoldLabs\WP\Module\Onboarding\TaskManagers\ImageSideloadTaskManager; |
6 | use NewfoldLabs\WP\Module\Onboarding\Tasks\ImageSideloadTask; |
7 | |
8 | /** |
9 | * SiteGenImageService for the onboarding module. |
10 | * |
11 | * Handles image processing and media library operations for the onboarding flow. |
12 | */ |
13 | class SiteGenImageService { |
14 | |
15 | /** |
16 | * Process homepage images immediately in background (non-blocking). |
17 | * This method dispatches an async request that doesn't block the main request. |
18 | * |
19 | * @param int $post_id The post ID to process images for. |
20 | * @param string $content The content containing images. |
21 | */ |
22 | public static function process_homepage_images_immediate_async( $post_id, $content ) { |
23 | // Extract image URLs from content |
24 | preg_match_all( '/<img[^>]+src=["\']([^"\']+)["\']/i', $content, $matches ); |
25 | $image_urls = isset( $matches[1] ) ? $matches[1] : array(); |
26 | if ( empty( $image_urls ) ) { |
27 | return; |
28 | } |
29 | |
30 | // Create and add task to queue |
31 | $task = new ImageSideloadTask( $post_id, $image_urls ); |
32 | ImageSideloadTaskManager::add_to_queue( $task ); |
33 | |
34 | // Schedule a single event to process the queue (if not already scheduled) |
35 | if ( ! wp_next_scheduled( 'nfd_process_image_sideload_queue' ) ) { |
36 | wp_schedule_single_event( time(), 'nfd_process_image_sideload_queue' ); |
37 | } |
38 | } |
39 | |
40 | /** |
41 | * Uploads images to the WordPress media library as attachments. |
42 | * |
43 | * This function takes an array of image URLs, downloads them, and |
44 | * uploads them to the WordPress media library, returning the URLs |
45 | * of the newly uploaded images. |
46 | * |
47 | * @param array $image_urls An array of image URLs to upload. |
48 | * @param int $post_id The post ID to attach the images to. |
49 | * @return array|false An array of WordPress attachment URLs on success, false on failure. |
50 | * @throws Exception If there is an error during the upload process. |
51 | */ |
52 | public static function upload_images_to_wp_media_library( $image_urls, $post_id ) { |
53 | require_once ABSPATH . 'wp-admin/includes/media.php'; |
54 | require_once ABSPATH . 'wp-admin/includes/image.php'; |
55 | |
56 | global $wp_filesystem; |
57 | self::connect_to_filesystem(); |
58 | |
59 | $uploaded_image_urls = array(); |
60 | $total_images = count( $image_urls ); |
61 | $successful_uploads = 0; |
62 | |
63 | try { |
64 | foreach ( $image_urls as $image_url ) { |
65 | // Check if the URL is valid. |
66 | if ( ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { |
67 | continue; |
68 | } |
69 | |
70 | // Fetch the image via remote get with timeout and a retry attempt. |
71 | $attempt = 0; |
72 | $max_attempts = 2; |
73 | while ( $attempt < $max_attempts ) { |
74 | $response = wp_remote_get( $image_url, array( 'timeout' => 15 ) ); |
75 | if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) { |
76 | break; |
77 | } |
78 | ++$attempt; |
79 | } |
80 | if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { |
81 | continue; |
82 | } |
83 | // Reading the headers from the image url to determine. |
84 | $headers = wp_remote_retrieve_headers( $response ); |
85 | $content_type = $headers['content-type'] ?? ''; |
86 | $image_data = wp_remote_retrieve_body( $response ); |
87 | if ( empty( $content_type ) || empty( $image_data ) ) { |
88 | continue; |
89 | } |
90 | // Determine the file extension based on MIME type. |
91 | $file_extension = ''; |
92 | switch ( $content_type ) { |
93 | case 'image/jpeg': |
94 | $file_extension = '.jpg'; |
95 | break; |
96 | case 'image/png': |
97 | $file_extension = '.png'; |
98 | break; |
99 | case 'image/gif': |
100 | $file_extension = '.gif'; |
101 | break; |
102 | case 'image/webp': |
103 | $file_extension = '.webp'; |
104 | break; |
105 | } |
106 | |
107 | if ( '' === $file_extension ) { |
108 | continue; |
109 | } |
110 | // create upload directory. |
111 | $upload_dir = wp_upload_dir(); |
112 | // xtract a filename from the URL. |
113 | $parsed_url = wp_parse_url( $image_url ); |
114 | $path_parts = pathinfo( $parsed_url['path'] ); |
115 | // filename to be added in directory. |
116 | $original_filename = $path_parts['filename'] . $file_extension; |
117 | |
118 | // to ensure the filename is unique within the upload directory. |
119 | $filename = wp_unique_filename( $upload_dir['path'], $original_filename ); |
120 | $filepath = $upload_dir['path'] . '/' . $filename; |
121 | |
122 | $wp_filesystem->put_contents( $filepath, $image_data ); |
123 | |
124 | // Create an attachment post for the image, metadata needed for WordPress media library. |
125 | // guid -for url, post_title for cleaned up name, post content is empty as this is an attachment. |
126 | // post_status inherit is for visibility. |
127 | $attachment = array( |
128 | 'guid' => $upload_dir['url'] . '/' . $filename, |
129 | 'post_mime_type' => $content_type, |
130 | 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ), |
131 | 'post_content' => '', |
132 | 'post_status' => 'inherit', |
133 | 'post_parent' => $post_id, // Attach to the specified post |
134 | ); |
135 | $attach_id = wp_insert_attachment( $attachment, $filepath ); |
136 | |
137 | // Generate and assign metadata for the attachment. |
138 | $attach_data = wp_generate_attachment_metadata( $attach_id, $filepath ); |
139 | wp_update_attachment_metadata( $attach_id, $attach_data ); |
140 | |
141 | // Add the WordPress attachment URL to the list. |
142 | if ( $attach_id ) { |
143 | $attachment_url = wp_get_attachment_url( $attach_id ); |
144 | if ( ! $attachment_url ) { |
145 | $attachment_url = null; |
146 | } |
147 | $uploaded_image_urls[ $image_url ] = $attachment_url; |
148 | $successful_uploads++; |
149 | } |
150 | } |
151 | } catch ( \Exception $e ) { |
152 | // Log error silently |
153 | } |
154 | return $uploaded_image_urls; |
155 | } |
156 | |
157 | /** |
158 | * Update post content by replacing original image URLs with WordPress media library URLs. |
159 | * |
160 | * @param int $post_id The post ID to update. |
161 | * @param array $url_mapping Array mapping original URLs to new WordPress URLs. |
162 | * @return bool True on success, false on failure. |
163 | */ |
164 | public static function update_post_content_with_new_image_urls( $post_id, $url_mapping ) { |
165 | // Get the current post content |
166 | $post = get_post( $post_id ); |
167 | if ( ! $post ) { |
168 | return false; |
169 | } |
170 | |
171 | $content = $post->post_content; |
172 | $updated = false; |
173 | $replaced_count = 0; |
174 | |
175 | // Replace each original URL with the new WordPress URL |
176 | foreach ( $url_mapping as $original_url => $new_url ) { |
177 | if ( ! empty( $new_url ) ) { |
178 | // Use str_replace for exact URL replacement |
179 | $new_content = str_replace( $original_url, $new_url, $content ); |
180 | if ( $new_content !== $content ) { |
181 | $content = $new_content; |
182 | $updated = true; |
183 | $replaced_count++; |
184 | } |
185 | } |
186 | } |
187 | |
188 | // Update the post if content changed |
189 | if ( $updated ) { |
190 | $update_result = wp_update_post( |
191 | array( |
192 | 'ID' => $post_id, |
193 | 'post_content' => $content, |
194 | ) |
195 | ); |
196 | |
197 | if ( is_wp_error( $update_result ) ) { |
198 | return false; |
199 | } |
200 | |
201 | return true; |
202 | } |
203 | |
204 | return true; // No changes needed |
205 | } |
206 | |
207 | /** |
208 | * Connect to the WordPress filesystem. |
209 | * |
210 | * @return boolean |
211 | */ |
212 | public static function connect_to_filesystem() { |
213 | require_once ABSPATH . 'wp-admin/includes/file.php'; |
214 | |
215 | // We want to ensure that the user has direct access to the filesystem. |
216 | $access_type = \get_filesystem_method(); |
217 | if ( 'direct' !== $access_type ) { |
218 | return false; |
219 | } |
220 | |
221 | $creds = \request_filesystem_credentials( site_url() . '/wp-admin', '', false, false, array() ); |
222 | |
223 | if ( ! \WP_Filesystem( $creds ) ) { |
224 | return false; |
225 | } |
226 | |
227 | return true; |
228 | } |
229 | } |