]+src=["\']data:image\/([^;]+);base64,([^"\']+)["\']/i';
$content = preg_replace_callback(
$pattern,
function ($matches) use ($folder) {
// IGNORE $extension from
tag
$base64Data = $matches[2];
// Upload the image (extension is always determined by MIME)
$result = $this->uploadBase64Image($base64Data, $folder);
if ($result['success'] && isset($result['url'])) {
// Replace base64 with uploaded URL
// Replace only the data:image/...;base64,... part
$original = $matches[0];
$replace = preg_replace(
'/src=["\']data:image\/[^;]+;base64,[^"\']+["\']/',
'src="' . $result['url'] . '"',
$original
);
return $replace;
}
// If upload failed, keep original
return $matches[0];
},
$content
);
return $content;
}
/**
* Upload base64 image to WordPress uploads directory
*
* @param string $base64Data Base64 encoded image data (without data: prefix)
* @param string $folder Subfolder name (confirmation, notification, etc.)
* @param string $extension (IGNORED, always determined by MIME)
* @param string $fileName Optional custom filename without extension
* @return array Array with 'success' bool and 'url' or 'error' keys
*/
public function uploadBase64Image(
string $base64Data,
string $folder,
string $extension = 'png', // Ignored
string $fileName = ''
): array {
// Validate base64 data
$decodedData = base64_decode($base64Data, true);
if ($decodedData === false) {
return [
'success' => false,
'error' => BackendStrings::getExceptionStrings()['invalid_base64_data'],
];
}
// Verify and validate image
$validationResult = $this->validateImage($decodedData);
if (!$validationResult['success']) {
return $validationResult;
}
// Use extension derived from actual MIME type ONLY
$extension = $validationResult['extension'];
// Create upload directory if it doesn't exist
$uploadDir = $this->getUploadDirectory($folder);
if (!wp_mkdir_p($uploadDir)) {
return [
'success' => false,
'error' => BackendStrings::getExceptionStrings()['failed_create_upload_dir'],
];
}
// Process and prepare filename
$fileName = $this->prepareFileName($fileName, $extension);
// Full file path and handle conflicts
$filePath = $uploadDir . '/' . $fileName;
$fileName = $this->resolveFileConflict($filePath, $fileName);
$filePath = $uploadDir . '/' . $fileName;
// Write file
if (!$this->writeImageFile($filePath, $decodedData)) {
return [
'success' => false,
'error' => BackendStrings::getExceptionStrings()['failed_write_file'],
];
}
// Get full URL
$fullUrl = $this->getFullUrl($folder, $fileName);
return [
'success' => true,
'url' => $fullUrl,
'fileName' => $fileName,
'folder' => $folder,
];
}
/**
* Validate image data
*
* @param string $decodedData
* @return array
*/
private function validateImage(string $decodedData): array
{
$imageInfo = getimagesizefromstring($decodedData);
if ($imageInfo === false) {
return [
'success' => false,
'error' => BackendStrings::getExceptionStrings()['invalid_image_data'],
];
}
$allowedMimeTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp',
];
$detectedMimeType = $imageInfo['mime'];
if (!array_key_exists($detectedMimeType, $allowedMimeTypes)) {
return [
'success' => false,
'error' => BackendStrings::getExceptionStrings()['unsupported_image_type'] . $detectedMimeType,
];
}
return ['success' => true, 'extension' => $allowedMimeTypes[$detectedMimeType]];
}
/**
* Prepare and sanitize filename
*
* @param string $fileName
* @param string $extension
* @return string
*/
private function prepareFileName(string $fileName, string $extension): string
{
if (empty($fileName)) {
$fileName = 'image-' . time() . '-' . uniqid();
}
$fileName = sanitize_file_name($fileName);
if (empty($fileName)) {
$fileName = 'image-' . time() . '-' . uniqid();
}
if (strpos($fileName, '.') === false) {
$fileName .= '.' . $extension;
}
return $fileName;
}
/**
* Resolve file name conflicts by appending counter
*
* @param string $filePath
* @param string $fileName
* @return string
*/
private function resolveFileConflict(string $filePath, string $fileName): string
{
if (!file_exists($filePath)) {
return $fileName;
}
$counter = 1;
$baseName = pathinfo($fileName, PATHINFO_FILENAME);
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
$uploadDir = dirname($filePath);
while (file_exists($uploadDir . '/' . $fileName)) {
$fileName = $baseName . '-' . $counter . '.' . $ext;
$counter++;
}
return $fileName;
}
/**
* Write image file to disk with proper permissions
*
* @param string $filePath
* @param string $decodedData
* @return bool
*/
private function writeImageFile(string $filePath, string $decodedData): bool
{
$bytesWritten = file_put_contents($filePath, $decodedData);
return $bytesWritten !== false;
}
/**
* Get the full upload directory path for a specific folder
*
* @param string $folder Subfolder name
* @return string Full directory path
*/
private function getUploadDirectory(string $folder): string
{
$wpUploadDir = wp_upload_dir();
$baseDir = $wpUploadDir['basedir'];
// Sanitize folder name
$folder = sanitize_file_name($folder);
return $baseDir . '/' . self::BASE_UPLOAD_DIR . '/' . $folder;
}
/**
* Get the full URL to an uploaded image
*
* @param string $folder Subfolder name
* @param string $fileName Filename
* @return string Full URL
*/
public function getFullUrl(string $folder, string $fileName): string
{
$wpUploadDir = wp_upload_dir();
$baseUrl = $wpUploadDir['baseurl'];
return $baseUrl . '/' . self::BASE_UPLOAD_DIR . '/' . $folder . '/' . $fileName;
}
/**
* Delete uploaded image
*
* @param string $folder Subfolder name
* @param string $fileName Filename
* @return bool True if deleted, false otherwise
*/
public function deleteImage(string $folder, string $fileName): bool
{
$uploadDir = $this->getUploadDirectory($folder);
$filePath = $uploadDir . '/' . $fileName;
if (file_exists($filePath)) {
return unlink($filePath);
}
return false;
}
}