538 lines
15 KiB
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;
|
|
}
|
|
}
|