Bug: auto-convert on upload discards the conversion result, leaving a broken attachment

Plugin 2.2.2. Settings: Auto-Convert on Upload = on, Preserve originals = off.

CM_Image_Converter_Hooks::handle_upload_convert_image() (class-image-converter-hooks.php:129-141) calls convert_image($upload, 0) and assigns the return value to $result, but then returns the original $upload. CM_Image_Converter_Core::convert_image() (class-image-converter-core.php:67) takes $upload by value — no & — so the in-place updates at lines 150-154 never reach the caller, despite the comment at lines 130-134 stating they do.

Result: WordPress registers the attachment with the original .jpg path and image/jpeg mime, while lines 221-231 have already deleted that file (preserve_originals off). The attachment ends up pointing at a non-existent file with empty metadata — broken thumbnail, blank file size in the media modal. The add_attachment fallback in handle_new_attachment() (line 169) also bails, because the original file is gone by then.

Fix: either return is_wp_error($result) ? $upload : $result; in the hook, or change the signature to convert_image(&$upload, ...).

Second, related issue: convert_single_file() (core.php:267) builds the output path from the source filename and writes it without checking whether that file already belongs to another attachment. Because the plugin deletes originals, wp_unique_filename() no longer detects a name clash on re-upload, so a new upload silently overwrote an existing attachment’s .webp and all of its size variants.

Batch conversion is unaffected — it passes a real attachment ID, so the update block at core.php:175 runs.

Addendum to the previous report — two further issues in the same module (2.2.2). These affect the manual Image Converter run, not the upload path.

3. auto_convert is a global kill switch, not just an upload-time setting — it silently disables manual conversion.

CM_Image_Converter_Core::should_skip_conversion() (class-image-converter-core.php:715-721) returns true for every image when the setting is off:

if (!$this->get_converter()->get_setting('auto_convert')) {
    return true;
}

The same setting is already checked at the three upload entry points (class-image-converter-hooks.php:108, 163, 201), which is where it belongs. Checking it again inside should_skip_conversion() means that with “Auto-Convert on Upload” unchecked, the “Start Image Converter” button converts nothing at all: convert_image() bails at core.php:74 and returns WP_Error('conversion_skipped') for every attachment. No admin notice, no log entry explaining why, and the settings UI gives no hint that the checkbox governs manual runs.

On our site this leaves the operator with no safe option: with the setting off, the batch run does nothing; with it on, every new media upload hits the broken hook from issue 1 above. Manual conversion should be gated on the user’s explicit action, not on the auto-convert preference.

4. process_batch() restarts from the beginning indefinitely when any original can never be converted.

CM_Image_Converter_Progress::process_batch() (class-image-converter-progress.php:114-136): when the ID cursor reaches the end of the range but actionable_original_count > 0, it returns 'complete' => false with 'cursor' => 0 (lines 127-131), so the next batch starts over from ID 0. The JS in assets/js/image-converter.js calls processNextBatch() recursively on every non-complete response (lines 572-575), with no cap in the plain conversion path — the sweep loops forever. In the guided workflow the poll is bounded by maxStatusAttempts = 120 (line 1028), but its exit condition is percentage >= 100 || originalRemaining === 0 (line 1083), which is likewise unreachable, so after ~2 minutes it reports “Status Check Timeout — the conversion may still be running in the background”, which is misleading: nothing is running.

There is no exit for the case where an image simply cannot be converted. On our library this reproduced exactly: 956 WebP, 74 remaining JPEG/PNG, 46 of them already flagged {"reason":"larger_output"}, leaving 28 actionable that never convert because of issue 3. The progress bar sat at “956 of 984 processed (97%)” with all action buttons disabled, cycling through the library indefinitely.

Two contributing details:

  • get_batch_attachment_ids() (class-image-converter-progress.php:231-255) filters only by MIME type, post_status, the ID cursor and the excluded list. Attachments already carrying _cm_conversion_skipped are re-fetched and re-attempted on every sweep, so each loop redundantly re-encodes the 46 known larger_output files before discarding the result.
  • ajax_get_conversion_status() reports 'original' => $actionable_original (class-image-converter-ajax.php:174), so the JS originalRemaining === 0 check can only ever be satisfied by full conversion — a permanently unconvertible original keeps both exit conditions false.

Suggested fix: abort the sweep when a full pass converts zero images (track conversions per pass and stop instead of resetting the cursor), report those attachments as “could not convert” rather than looping, and exclude _cm_conversion_skipped attachments from get_batch_attachment_ids() unless a force flag is set.

Hi @IgorPo
We have already fixed it, update coming on Monday.
Thanks again for detailed report!

Hi @IgorPo
Thanks for reporting this bug.

We have addressed this in v2.3.0 Changelog and update Classic Monks to the latest version.