Skip to content

Commit 78061fd

Browse files
committed
using a task manager to process images
1 parent 832ce88 commit 78061fd

File tree

4 files changed

+329
-107
lines changed

4 files changed

+329
-107
lines changed

bootstrap.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use NewfoldLabs\WP\Module\Onboarding\Compatibility\Scan;
77
use NewfoldLabs\WP\Module\Onboarding\Compatibility\Safe_Mode;
88
use NewfoldLabs\WP\Module\Onboarding\Compatibility\Status;
9+
use NewfoldLabs\WP\Module\Onboarding\TaskManagers\ImageSideloadTaskManager;
910

1011
use function NewfoldLabs\WP\ModuleLoader\register;
1112

@@ -81,11 +82,6 @@ function () {
8182
}
8283
);
8384

84-
// Register AJAX handlers for image processing
85-
add_action(
86-
'init',
87-
function () {
88-
\NewfoldLabs\WP\Module\Onboarding\Services\ThemeGeneratorService::register_ajax_handlers();
89-
}
90-
);
85+
// Add action to process image sideload queue
86+
add_action( 'nfd_process_image_sideload_queue', [ ImageSideloadTaskManager::class, 'process_queue' ] );
9187
}

includes/Services/ThemeGeneratorService.php

Lines changed: 17 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace NewfoldLabs\WP\Module\Onboarding\Services;
44

5+
use NewfoldLabs\WP\Module\Onboarding\TaskManagers\ImageSideloadTaskManager;
6+
use NewfoldLabs\WP\Module\Onboarding\Tasks\ImageSideloadTask;
7+
58
/**
69
* ThemeGeneratorService for the onboarding module.
710
*
@@ -17,23 +20,21 @@ class ThemeGeneratorService {
1720
* @param string $content The content containing images.
1821
*/
1922
public static function process_homepage_images_immediate_async( $post_id, $content ) {
20-
// Use wp_remote_post to make an async request to the same site
21-
$admin_url = admin_url( 'admin-ajax.php' );
22-
$nonce = wp_create_nonce( 'sitegen_async_images' );
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+
}
2329

24-
wp_remote_post(
25-
$admin_url,
26-
array(
27-
'timeout' => 0.01, // Very short timeout to not block
28-
'blocking' => false, // Non-blocking request
29-
'body' => array(
30-
'action' => 'sitegen_process_images_async',
31-
'post_id' => $post_id,
32-
'content' => $content,
33-
'nonce' => $nonce,
34-
),
35-
)
36-
);
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+
}
3738
}
3839

3940
/**
@@ -150,90 +151,6 @@ public static function upload_images_to_wp_media_library( $image_urls, $post_id
150151
return $uploaded_image_urls;
151152
}
152153

153-
/**
154-
* Extract image URLs from content and upload them to WordPress media library.
155-
*
156-
* This function extracts image URLs from img tags in the content, uploads the images
157-
* to the WordPress media library, and then replaces the old image URLs in the content
158-
* with the new ones.
159-
*
160-
* @param string $content The content containing img tags with image URLs.
161-
* @param int $post_id The post ID to attach the images to.
162-
* @return string The updated content with new image URLs.
163-
*/
164-
public static function sideload_images_and_replace_grammar( $content, $post_id ) {
165-
// Extract image URLs from img tags in the content
166-
$image_urls = array();
167-
preg_match_all( '/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $content, $matches );
168-
169-
if ( ! empty( $matches[1] ) ) {
170-
$image_urls = array_unique( $matches[1] );
171-
}
172-
173-
if ( empty( $image_urls ) ) {
174-
return $content;
175-
}
176-
177-
// Upload the images to WordPress media library.
178-
$url_mapping = self::upload_images_to_wp_media_library( $image_urls, $post_id );
179-
180-
foreach ( $url_mapping as $old_url => $new_url ) {
181-
if ( null === $new_url ) {
182-
continue;
183-
}
184-
// escaping any special characters in the old URL to avoid breaking the regex.
185-
$escaped_old_url = preg_quote( $old_url, '/' );
186-
187-
$escaped_old_url_regex_double_quote = '/"' . $escaped_old_url . '.*?"/m';
188-
$content = preg_replace( $escaped_old_url_regex_double_quote, '"' . $new_url . '"', $content );
189-
190-
$escaped_old_url_regex_parenthesis = '/\(' . $escaped_old_url . '.*?\)/m';
191-
$content = preg_replace( $escaped_old_url_regex_parenthesis, '(' . $new_url . ')', $content );
192-
}
193-
194-
// Update the content with new image URLs.
195-
return $content;
196-
}
197-
198-
/**
199-
* Register AJAX handlers for image processing.
200-
* This should be called during plugin initialization.
201-
*/
202-
public static function register_ajax_handlers() {
203-
add_action( 'wp_ajax_sitegen_process_images_async', array( __CLASS__, 'handle_ajax_image_processing' ) );
204-
add_action( 'wp_ajax_nopriv_sitegen_process_images_async', array( __CLASS__, 'handle_ajax_image_processing' ) );
205-
}
206-
207-
/**
208-
* Handle AJAX request for immediate async image processing.
209-
*/
210-
public static function handle_ajax_image_processing() {
211-
// Verify nonce for security
212-
if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'sitegen_async_images' ) ) {
213-
wp_die( 'Security check failed' );
214-
}
215-
216-
$post_id = intval( $_POST['post_id'] ?? 0 );
217-
$content = sanitize_textarea_field( $_POST['content'] ?? '' );
218-
219-
if ( ! $post_id || ! $content ) {
220-
wp_die( 'Invalid parameters' );
221-
}
222-
223-
// Process images
224-
$updated_content = self::sideload_images_and_replace_grammar( $content, $post_id );
225-
if ( $updated_content !== $content ) {
226-
wp_update_post(
227-
array(
228-
'ID' => $post_id,
229-
'post_content' => $updated_content,
230-
)
231-
);
232-
}
233-
234-
wp_die( 'Success' );
235-
}
236-
237154
/**
238155
* Connect to the WordPress filesystem.
239156
*
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
<?php
2+
3+
namespace NewfoldLabs\WP\Module\Onboarding\TaskManagers;
4+
5+
use NewfoldLabs\WP\Module\Onboarding\Tasks\ImageSideloadTask;
6+
use NewfoldLabs\WP\Module\Onboarding\Data\Options;
7+
8+
/**
9+
* Task Manager for Image Sideloading
10+
*
11+
* Manages a queue of ImageSideloadTask objects and processes them in FIFO order.
12+
*/
13+
class ImageSideloadTaskManager {
14+
15+
/**
16+
* Option name for storing the queue
17+
*
18+
* @var string
19+
*/
20+
const QUEUE_OPTION = 'nfd_image_sideload_queue';
21+
22+
/**
23+
* Option name for storing the processing status
24+
*
25+
* @var string
26+
*/
27+
const STATUS_OPTION = 'nfd_image_sideload_status';
28+
29+
/**
30+
* Add a task to the queue
31+
*
32+
* @param ImageSideloadTask $task The task to add
33+
* @return bool True on success, false on failure
34+
*/
35+
public static function add_to_queue( ImageSideloadTask $task ) {
36+
$queue = self::get_queue();
37+
38+
// Check if task already exists
39+
$task_id = $task->get_id();
40+
foreach ( $queue as $existing_task ) {
41+
if ( $existing_task['id'] === $task_id ) {
42+
return false; // Task already exists
43+
}
44+
}
45+
46+
// Add task to queue (FIFO - add to end)
47+
$queue[] = array(
48+
'id' => $task_id,
49+
'post_id' => $task->get_post_id(),
50+
'urls' => $task->get_image_urls(),
51+
'status' => 'pending',
52+
'created' => time(),
53+
);
54+
55+
self::set_queue( $queue );
56+
return true;
57+
}
58+
59+
/**
60+
* Get the current queue
61+
*
62+
* @return array
63+
*/
64+
public static function get_queue() {
65+
$queue = get_option( Options::get_option_name( self::QUEUE_OPTION ), array() );
66+
if ( ! is_array( $queue ) ) {
67+
$queue = array();
68+
}
69+
return $queue;
70+
}
71+
72+
/**
73+
* Set the queue
74+
*
75+
* @param array $queue The queue to set
76+
* @return void
77+
*/
78+
public static function set_queue( $queue ) {
79+
update_option( Options::get_option_name( self::QUEUE_OPTION ), $queue );
80+
}
81+
82+
/**
83+
* Get the processing status
84+
*
85+
* @return string
86+
*/
87+
public static function get_status() {
88+
return get_option( Options::get_option_name( self::STATUS_OPTION ), 'idle' );
89+
}
90+
91+
/**
92+
* Set the processing status
93+
*
94+
* @param string $status The status to set
95+
* @return void
96+
*/
97+
public static function set_status( $status ) {
98+
update_option( Options::get_option_name( self::STATUS_OPTION ), $status );
99+
}
100+
101+
/**
102+
* Process the next task in the queue
103+
*
104+
* @return bool|\WP_Error True if task was processed, false if queue is empty, WP_Error on failure
105+
*/
106+
public static function process_next_task() {
107+
$queue = self::get_queue();
108+
109+
if ( empty( $queue ) ) {
110+
self::set_status( 'idle' );
111+
return false;
112+
}
113+
114+
// Get the next task
115+
$task_data = array_shift( $queue );
116+
117+
// Mark as processing
118+
$task_data['status'] = 'processing';
119+
$task_data['started'] = time();
120+
121+
// Create task object and execute
122+
$task = new ImageSideloadTask( $task_data['post_id'], $task_data['urls'] );
123+
$result = $task->execute();
124+
125+
if ( is_wp_error( $result ) ) {
126+
// Mark as failed
127+
$task_data['status'] = 'failed';
128+
$task_data['error'] = $result->get_error_message();
129+
$task_data['completed'] = time();
130+
131+
// Add back to queue for retry
132+
$queue[] = $task_data;
133+
134+
self::set_queue( $queue );
135+
self::set_status( 'processing' );
136+
137+
return $result;
138+
}
139+
140+
// Mark as completed
141+
$task_data['status'] = 'completed';
142+
$task_data['completed'] = time();
143+
144+
self::set_queue( $queue );
145+
self::set_status( 'processing' );
146+
147+
return true;
148+
}
149+
150+
/**
151+
* Process the entire queue
152+
*
153+
* @param int $max_tasks Maximum number of tasks to process in one run (default: 5)
154+
* @return array Array with 'processed' and 'remaining' counts
155+
*/
156+
public static function process_queue( $max_tasks = 5 ) {
157+
$processed = 0;
158+
$queue = self::get_queue();
159+
$initial_count = count( $queue );
160+
161+
self::set_status( 'processing' );
162+
163+
while ( $processed < $max_tasks && ! empty( $queue ) ) {
164+
$result = self::process_next_task();
165+
166+
if ( $result === false ) {
167+
break; // Queue is empty
168+
}
169+
170+
if ( is_wp_error( $result ) ) {
171+
// Continue processing other tasks even if one fails
172+
$processed++;
173+
continue;
174+
}
175+
176+
$processed++;
177+
}
178+
179+
$remaining = count( self::get_queue() );
180+
181+
if ( $remaining === 0 ) {
182+
self::set_status( 'idle' );
183+
}
184+
185+
return array(
186+
'processed' => $processed,
187+
'remaining' => $remaining,
188+
'total' => $initial_count,
189+
);
190+
}
191+
192+
/**
193+
* Clear the queue
194+
*
195+
* @return void
196+
*/
197+
public static function clear_queue() {
198+
self::set_queue( array() );
199+
self::set_status( 'idle' );
200+
}
201+
202+
/**
203+
* Get queue statistics
204+
*
205+
* @return array
206+
*/
207+
public static function get_stats() {
208+
$queue = self::get_queue();
209+
$stats = array(
210+
'total' => count( $queue ),
211+
'pending' => 0,
212+
'processing' => 0,
213+
'completed' => 0,
214+
'failed' => 0,
215+
);
216+
217+
foreach ( $queue as $task ) {
218+
$status = $task['status'] ?? 'pending';
219+
if ( isset( $stats[ $status ] ) ) {
220+
$stats[ $status ]++;
221+
}
222+
}
223+
224+
return $stats;
225+
}
226+
}

0 commit comments

Comments
 (0)