Files
Instagram-Gallery-Sync-Pro/includes/class-image-handler.php

538 lines
15 KiB
PHP

<?php
/**
* Image Handler Class
*
* Handles downloading, saving, and processing of Instagram images.
*
* @package Instagram_Gallery_Sync_Pro
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Class IGSP_Image_Handler
*/
class IGSP_Image_Handler
{
/**
* Upload directory path
*
* @var string
*/
private $upload_path;
/**
* Upload directory URL
*
* @var string
*/
private $upload_url;
/**
* Thumbnail sizes
*
* @var array
*/
private $thumbnail_sizes = array(
'small' => 150,
'medium' => 320,
'large' => 640,
);
/**
* Logger instance
*
* @var IGSP_Logger
*/
private $logger;
/**
* Constructor
*/
public function __construct()
{
$this->setup_paths();
$this->logger = new IGSP_Logger();
}
/**
* Setup upload paths
*
* @return void
*/
private function setup_paths()
{
$upload_dir = wp_upload_dir();
$this->upload_path = $upload_dir['basedir'] . '/' . IGSP_UPLOAD_DIR;
$this->upload_url = $upload_dir['baseurl'] . '/' . IGSP_UPLOAD_DIR;
// Ensure directory exists
if (!file_exists($this->upload_path)) {
wp_mkdir_p($this->upload_path);
}
}
/**
* Download and save an image
*
* @param string $url Remote image URL
* @param string $instagram_id Instagram post ID for naming
* @return array|WP_Error Array with paths or WP_Error
*/
public function download_image($url, $instagram_id)
{
if (empty($url) || empty($instagram_id)) {
return new WP_Error('invalid_params', __('Invalid parameters for image download.', 'instagram-gallery-sync-pro'));
}
// Sanitize ID for filename
$safe_id = sanitize_file_name($instagram_id);
// Download the image
$temp_file = download_url($url, 30);
if (is_wp_error($temp_file)) {
$this->logger->error(
sprintf(__('Failed to download image: %s', 'instagram-gallery-sync-pro'), $temp_file->get_error_message()),
array('url' => $url)
);
return $temp_file;
}
// Validate the downloaded file
$validation = $this->validate_image($temp_file);
if (is_wp_error($validation)) {
@unlink($temp_file);
return $validation;
}
// Get file info
$file_info = $validation;
$extension = $file_info['extension'];
// Generate filenames
$filename = $safe_id . '.' . $extension;
$filepath = $this->upload_path . '/' . $filename;
// Move file to upload directory
$moved = rename($temp_file, $filepath);
if (!$moved) {
@unlink($temp_file);
return new WP_Error('move_failed', __('Failed to move downloaded file.', 'instagram-gallery-sync-pro'));
}
// Set correct permissions
chmod($filepath, 0644);
// Generate thumbnails
$thumbnail_path = $this->generate_thumbnail($filepath, $safe_id, $extension);
// Get image dimensions
$dimensions = getimagesize($filepath);
// Prepare result
$result = array(
'local_path' => IGSP_UPLOAD_DIR . '/' . $filename,
'thumbnail_path' => $thumbnail_path,
'full_url' => $this->upload_url . '/' . $filename,
'thumbnail_url' => !empty($thumbnail_path) ? $this->upload_url . '/' . str_replace(IGSP_UPLOAD_DIR . '/', '', $thumbnail_path) : '',
'file_size' => filesize($filepath),
'width' => $dimensions ? $dimensions[0] : 0,
'height' => $dimensions ? $dimensions[1] : 0,
'mime_type' => $file_info['mime_type'],
);
$this->logger->info(
sprintf(__('Image saved: %s', 'instagram-gallery-sync-pro'), $filename),
array('size' => $result['file_size'], 'dimensions' => $dimensions[0] . 'x' . $dimensions[1])
);
return $result;
}
/**
* Validate downloaded image
*
* @param string $filepath Path to temp file
* @return array|WP_Error File info or error
*/
private function validate_image($filepath)
{
if (!file_exists($filepath)) {
return new WP_Error('file_not_found', __('Downloaded file not found.', 'instagram-gallery-sync-pro'));
}
// Check file size (max 20MB)
$max_size = 20 * 1024 * 1024;
if (filesize($filepath) > $max_size) {
return new WP_Error('file_too_large', __('File exceeds maximum size limit.', 'instagram-gallery-sync-pro'));
}
// Verify it's actually an image
$file_info = wp_check_filetype_and_ext($filepath, basename($filepath));
$image_info = @getimagesize($filepath);
if (!$image_info) {
return new WP_Error('not_an_image', __('File is not a valid image.', 'instagram-gallery-sync-pro'));
}
// Check allowed types
$allowed_types = array('image/jpeg', 'image/png', 'image/gif', 'image/webp');
$mime_type = $image_info['mime'];
if (!in_array($mime_type, $allowed_types, true)) {
return new WP_Error('invalid_type', __('Image type not allowed.', 'instagram-gallery-sync-pro'));
}
// Map mime to extension
$ext_map = array(
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp',
);
return array(
'mime_type' => $mime_type,
'extension' => $ext_map[$mime_type] ?? 'jpg',
'width' => $image_info[0],
'height' => $image_info[1],
);
}
/**
* Generate thumbnail from image
*
* @param string $filepath Original image path
* @param string $safe_id Sanitized ID for naming
* @param string $extension File extension
* @return string|null Thumbnail path or null on failure
*/
public function generate_thumbnail($filepath, $safe_id, $extension)
{
// Use WordPress image editor
$editor = wp_get_image_editor($filepath);
if (is_wp_error($editor)) {
$this->logger->warning(
sprintf(__('Could not create image editor: %s', 'instagram-gallery-sync-pro'), $editor->get_error_message())
);
return null;
}
// Get current size
$size = $editor->get_size();
// Calculate thumbnail size (medium by default)
$thumb_size = $this->thumbnail_sizes['medium'];
// Resize while maintaining aspect ratio
$editor->resize($thumb_size, $thumb_size, true); // true = crop
// Optimize quality
$editor->set_quality(85);
// Save thumbnail
$thumb_dir = $this->upload_path . '/thumbnails';
if (!file_exists($thumb_dir)) {
wp_mkdir_p($thumb_dir);
}
$thumb_filename = $safe_id . '-thumb.' . $extension;
$thumb_path = $thumb_dir . '/' . $thumb_filename;
$saved = $editor->save($thumb_path);
if (is_wp_error($saved)) {
$this->logger->warning(
sprintf(__('Failed to save thumbnail: %s', 'instagram-gallery-sync-pro'), $saved->get_error_message())
);
return null;
}
return IGSP_UPLOAD_DIR . '/thumbnails/' . $thumb_filename;
}
/**
* Convert image to WebP (optional)
*
* @param string $filepath Original image path
* @return string|null WebP path or null
*/
public function convert_to_webp($filepath)
{
if (!function_exists('imagewebp')) {
return null;
}
$image_info = getimagesize($filepath);
if (!$image_info) {
return null;
}
$mime = $image_info['mime'];
// Create image resource based on type
switch ($mime) {
case 'image/jpeg':
$image = imagecreatefromjpeg($filepath);
break;
case 'image/png':
$image = imagecreatefrompng($filepath);
break;
case 'image/gif':
$image = imagecreatefromgif($filepath);
break;
default:
return null;
}
if (!$image) {
return null;
}
// Generate WebP path
$webp_path = preg_replace('/\.(jpe?g|png|gif)$/i', '.webp', $filepath);
// Save as WebP
$success = imagewebp($image, $webp_path, 85);
imagedestroy($image);
if (!$success) {
return null;
}
return str_replace($this->upload_path, IGSP_UPLOAD_DIR, $webp_path);
}
/**
* Strip EXIF data from image
*
* @param string $filepath Image path
* @return bool
*/
public function strip_exif($filepath)
{
$image_info = getimagesize($filepath);
if (!$image_info || $image_info['mime'] !== 'image/jpeg') {
return false;
}
$image = imagecreatefromjpeg($filepath);
if (!$image) {
return false;
}
// Re-save without EXIF
$result = imagejpeg($image, $filepath, 90);
imagedestroy($image);
return $result;
}
/**
* Delete image files for a post
*
* @param string $local_path Main image path
* @param string $thumbnail_path Thumbnail path
* @return bool
*/
public function delete_images($local_path, $thumbnail_path = '')
{
$upload_dir = wp_upload_dir();
$base_path = $upload_dir['basedir'];
$deleted = false;
if (!empty($local_path)) {
$full_path = $base_path . '/' . $local_path;
if (file_exists($full_path) && strpos($full_path, IGSP_UPLOAD_DIR) !== false) {
$deleted = unlink($full_path);
}
}
if (!empty($thumbnail_path)) {
$thumb_full_path = $base_path . '/' . $thumbnail_path;
if (file_exists($thumb_full_path) && strpos($thumb_full_path, IGSP_UPLOAD_DIR) !== false) {
unlink($thumb_full_path);
}
}
return $deleted;
}
/**
* Get image URL from local path
*
* @param string $local_path Local path
* @return string
*/
public function get_image_url($local_path)
{
if (empty($local_path)) {
return '';
}
$upload_dir = wp_upload_dir();
return $upload_dir['baseurl'] . '/' . $local_path;
}
/**
* Check if image exists locally
*
* @param string $local_path Local path
* @return bool
*/
public function image_exists($local_path)
{
if (empty($local_path)) {
return false;
}
$upload_dir = wp_upload_dir();
$full_path = $upload_dir['basedir'] . '/' . $local_path;
return file_exists($full_path);
}
/**
* Get total size of all stored images
*
* @return int Size in bytes
*/
public function get_total_storage_used()
{
$total = 0;
if (!is_dir($this->upload_path)) {
return $total;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->upload_path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
$total += $file->getSize();
}
return $total;
}
/**
* Get human-readable storage size
*
* @return string
*/
public function get_storage_used_formatted()
{
$bytes = $this->get_total_storage_used();
return size_format($bytes);
}
/**
* Save image from WordPress Media Library to plugin upload directory
*
* @param int $attachment_id WordPress attachment ID
* @return array|WP_Error Array with paths or WP_Error
*/
public function save_from_media_library($attachment_id)
{
$file_path = get_attached_file($attachment_id);
if (!$file_path || !file_exists($file_path)) {
return new WP_Error('file_not_found', __('Attachment file not found.', 'instagram-gallery-sync-pro'));
}
// Validate
$validation = $this->validate_image($file_path);
if (is_wp_error($validation)) {
return $validation;
}
$file_info = $validation;
$extension = $file_info['extension'];
// Generate unique filename
$safe_id = 'manual_' . time() . '_' . wp_rand(1000, 9999);
$filename = $safe_id . '.' . $extension;
$dest_path = $this->upload_path . '/' . $filename;
// Copy file (don't move - keep original in Media Library)
$copied = copy($file_path, $dest_path);
if (!$copied) {
return new WP_Error('copy_failed', __('Failed to copy image file.', 'instagram-gallery-sync-pro'));
}
chmod($dest_path, 0644);
// Generate thumbnail
$thumbnail_path = $this->generate_thumbnail($dest_path, $safe_id, $extension);
// Get dimensions
$dimensions = getimagesize($dest_path);
$result = array(
'local_path' => IGSP_UPLOAD_DIR . '/' . $filename,
'thumbnail_path' => $thumbnail_path ?: '',
'full_url' => $this->upload_url . '/' . $filename,
'file_size' => filesize($dest_path),
'width' => $dimensions ? $dimensions[0] : 0,
'height' => $dimensions ? $dimensions[1] : 0,
);
$this->logger->info(
sprintf(__('Manual image saved: %s', 'instagram-gallery-sync-pro'), $filename),
array('size' => $result['file_size'])
);
return $result;
}
/**
* Clear all stored images
*
* @return bool
*/
public function clear_all_images()
{
if (!is_dir($this->upload_path)) {
return true;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->upload_path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
rmdir($item->getRealPath());
} else {
// Don't delete index.php and .htaccess
$filename = $item->getFilename();
if ($filename !== 'index.php' && $filename !== '.htaccess') {
unlink($item->getRealPath());
}
}
}
// Recreate thumbnails directory
wp_mkdir_p($this->upload_path . '/thumbnails');
return true;
}
}