HEX
Server: Apache
System: Linux WWW 6.1.0-40-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.153-1 (2025-09-20) x86_64
User: web11 (1011)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/intranet.kauko.lt/wp-content/plugins/wise-chat/src/services/WiseChatImagesService.php
<?php

/**
 * Wise Chat image services class.
 *
 * @author Kainex <contact@kainex.pl>
 */
class WiseChatImagesService {
	const UPLOAD_FILE_NAME = '_wise_chat_upload';
	
	/**
	* @var string
	*/
	private $tempFileName;
	
	/**
	* @var WiseChatOptions
	*/
	private $options;
	
	private $supportedExtensions = array(
		'image/gif' => 'gif',
		'image/jpeg' => 'jpg',
		'image/png' => 'png'
	);
	
	public function __construct() {
		$this->options = WiseChatOptions::getInstance();
	}
	
	/**
	* Prepares an image for the final upload.
	*
	* @param string $binaryImageData Binary image data
	*
	* @return string Prepared binary image data
	* @throws \Exception
	*/
	public function getPreparedImage($binaryImageData) {
		$this->checkImagesRequirements();
		
		if (strlen($binaryImageData) > $this->options->getIntegerOption('images_size_limit', 3145728)) {
			throw new Exception($this->options->getEncodedOption('message_error_8', __('The size of the file exceeds allowed limit.', 'wise-chat')));
		}
		
		try {
			$this->createTempFile();
			$this->saveTempFile($binaryImageData);

			/** @var WiseChatImageEditor $imageEditor */
			$imageEditor = WiseChatContainer::get('services/WiseChatImageEditor');
			$imageEditor->load($this->tempFileName);
			$imageEditor->resize(
				$this->options->getIntegerOption('images_width_limit', 1000),
				$this->options->getIntegerOption('images_height_limit', 1000)
			);
			$imageEditor->fixOrientation();
			$imageEditor->build();
			
			$preparedRawData = file_get_contents($this->tempFileName);
			
 			$this->deleteTempFile();
 			
 			return $preparedRawData;
		} catch (Exception $e) {
			$this->deleteTempFile();
			
			throw $e;
		}
	}
	
	/**
	* Parses "data:image/jpeg;base64,"-like prefix from the given string and Base64-decodes the rest.
	*
	* @param string $rawBase64Data Base64-encoded image data with prefix
	*
	* @return array
    * @throws Exception If image data is invalid
	*/
	public function decodePrefixedBase64ImageData($base64Data) {
		$prefixData = array();
		preg_match('/^data:(image\/\w+);base64/', $base64Data, $prefixData);
		
		if (!is_array($prefixData) || count($prefixData) < 2) {
			throw new Exception('Invalid image data');
		}
	
		$data = substr($base64Data, strpos($base64Data, ",") + 1);
		
		return array(
			'data' => base64_decode($data),
			'mimeType' => $prefixData[1]
		);
	}
	
	/**
	* Encodes image data to Base64 and adds the prefix like "data:image/jpeg;base64," at the beginning.
	*
	* @param string $binaryImageData
	* @param string $mimeType
	*
	* @return string
	*/
	public function encodeBase64WithPrefix($binaryImageData, $mimeType) {
		$data = base64_encode($binaryImageData);
		
		return sprintf('data:%s;base64,', $mimeType).$data;
	}
	
	/**
	* Downloads image from the given URL and saves it into the Media Library.
	* Returns image details as an array or throws an exception in case of an error. 
	* Custom thumbnail image is generated as well.
	*
	* @notice Redirections during retrieving the image are not supported.
	*
	* @param string $url URL to image file
	*
	* @return array|null Array containing keys: id, image, image-th
	* @throws \Exception
	*/
	public function downloadImage($url) {
		$this->checkImagesRequirements();
		$this->checkCurlRequirements();
	
		$this->createTempFile();
		
		$this->downloadImageFile($url);
		$result = $this->saveImageInMediaLibrary();
		
		$this->deleteTempFile();
		
		return $result;
	}
	
	/**
	* Saves given image into the Media Library.
	* Returns image details as an array or exception if an error occurres. Custom thumbnail image is generated as well.
	*
	* @param string $imageData Raw image data
	*
	* @return array|null Array containing keys: id, image, image-th
	* @throws \Exception
	*/
	public function saveImage($imageData) {
		$this->checkImagesRequirements();
	
		$this->createTempFile();
		$this->saveTempFile($imageData);
		$result = null;
		if (is_array($this->getTempFileImageInfo(false))) {
			$result = $this->saveImageInMediaLibrary();
		}
		
		$this->deleteTempFile();
		
		return $result;
	}
	
	/**
	* Removes custom thumbnail image (generated by the plugin) for the given attachment.
	*
	* @param string $attachmentId
	*/
	public function removeRelatedImages($attachmentId) {
		$imagePath = get_attached_file($attachmentId);
		$imageThPath = preg_replace('/\.([a-zA-Z]+)$/', '-th.$1', $imagePath);
		if (file_exists($imageThPath) && is_writable($imageThPath)) {
			unlink($imageThPath);
		}
	}
	
	/**
	* Downloads image file from given URL and saves it into the temporary file.
	* Exception is thrown in case of errors.
	*
	* @param string $url
	*
	* @return array|null
	* @throws \Exception
	*/
	private function downloadImageFile($url) {
		$imageData = $this->getDataOverHttp($url);
		$this->saveTempFile($imageData);
		
		return $this->getTempFileImageInfo(true);
	}
	
	/**
	* Performs HTTP request and returns resource stored under the given URL. 
	* Exception is thrown in case of errors.
	*
	* @param string $url
	*
	* @return string
	* @throws \Exception
	*/
	private function getDataOverHttp($url) {
		$curl = curl_init($url);
		curl_setopt($curl, CURLOPT_HEADER, 0);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1);
		$data = curl_exec($curl);
		$httpStatus = intval(curl_getinfo($curl, CURLINFO_HTTP_CODE));
		
		if (curl_errno($curl)) {
			$this->logError('Resource downloading failure - cURL error: '.curl_errno($curl));
			curl_close($curl);
			throw new Exception('Resource could not be downloaded due to unknown error');
		}
		
		curl_close($curl);
		
		if ($httpStatus != 200) {
			$this->logError('Resource downloading failure - incorrect HTTP status: '.$httpStatus);
			throw new Exception('Resource could not be downloaded due to incorrect HTTP status code of the response');
		}
		
		if (strlen($data) > $this->options->getIntegerOption('images_size_limit', 3145728)) {
			$this->logError('Resource downloading failure - image is too big: '.strlen($data).' bytes');
			throw new Exception('Resource is too big to be downloaded');
		}
		
		return $data;
	}
	
	/**
	* Returns information about the image stored in the temporary file.
	* If the file is not an image an exception is thrown.
	*
	* @param boolean $safeResize
	*
	* @return array
	* @throws \Exception
	*/
	private function getTempFileImageInfo($safeResize) {
		$fileInfo = file_exists($this->tempFileName) ? getimagesize($this->tempFileName) : null;
		if (is_array($fileInfo)) {
			$mimeType = $fileInfo['mime'];
			if (!array_key_exists($mimeType, $this->supportedExtensions)) {
				$this->logError('Unsupported mime type: '.$mimeType);
				throw new Exception($this->options->getEncodedOption('message_error_7', __('Unsupported type of file', 'wise-chat')));
			}
			$fileName = date('Ymd-His').'-'.uniqid(rand()).'.'.$this->supportedExtensions[$mimeType];
			
			if ($safeResize) {
				$imageEditor = WiseChatContainer::get('services/WiseChatImageEditor');
				$imageEditor->load($this->tempFileName);
				$imageEditor->resize(
					$this->options->getIntegerOption('images_width_limit', 1000),
					$this->options->getIntegerOption('images_height_limit', 1000)
				);
				$imageEditor->build();
			}
			
			return $_FILES[self::UPLOAD_FILE_NAME] = array(
				'name' => $fileName,
				'type' => $mimeType,
				'tmp_name' => $this->tempFileName,
				'error' => 0,
				'size' => filesize($this->tempFileName),
			);
		}
		
		$this->logError('The file is not an image');
		throw new Exception($this->options->getEncodedOption('message_error_7', __('Unsupported type of file', 'wise-chat')));
	}
	
	/**
	* Saves image passed in $_FILES (under self::UPLOAD_FILE_NAME key) in the Media Library.
	* Returns null if an error occurs.
	*
	* @return array|null
	*/
	private function saveImageInMediaLibrary() {
		$result = null;
		
		require_once(ABSPATH.'wp-admin/includes/image.php');
		require_once(ABSPATH.'wp-admin/includes/file.php');
		require_once(ABSPATH.'wp-admin/includes/media.php');
		
		$attachmentId = media_handle_sideload($_FILES[self::UPLOAD_FILE_NAME], 0, null, array());
		
		if (is_wp_error($attachmentId)) {
			$this->logError('Error creating new entry in media library: '.$attachmentId->get_error_message());
			throw new Exception('The image could not be saved in the Media Library');
		} else {
			$result = array(
				'id' => $attachmentId,
				'image' => wp_get_attachment_url($attachmentId),
				'image-th' => $this->generateThumbnail($attachmentId)
			);
			
			$postUpdate = array(
				'ID' => $attachmentId,
				'post_title' => $_FILES[self::UPLOAD_FILE_NAME]['name']
			);
			wp_update_post($postUpdate);

			$this->ensureProtectionMeasures($attachmentId);
		}
		
		return $result;
	}
	
	/**
	* Generates thumbnail image for given attachment and returns URL of the thumbnail.
	*
	* @param string $attachmentId
	*
	* @return null|string
    * @throws Exception
	*/
	private function generateThumbnail($attachmentId) {
		$imagePath = get_attached_file($attachmentId);
		$imageThPath = preg_replace('/\.([a-zA-Z]+)$/', '-th.$1', $imagePath);
		
		$image = wp_get_image_editor($imagePath);
		if (!is_wp_error($image)) {
			$image->resize(
				$this->options->getIntegerOption('images_thumbnail_width_limit', 60),
				$this->options->getIntegerOption('images_thumbnail_height_limit', 60),
				true
			);
			$image->save($imageThPath);
		} else {
			$this->logError('Error creating thumbnail: '.$image->get_error_message());
			throw new Exception('The thumbnail of the image could not be generated');
		}
		
		$imageUrl = wp_get_attachment_url($attachmentId);
		$imageThUrl = preg_replace('/\.([a-zA-Z]+)$/', '-th.$1', $imageUrl);
		
		return $imageThUrl;
	}
	
	/**
	* Checks requirements of images processing.
	* @throws Exception
	*/
	private function checkImagesRequirements() {
		if (!extension_loaded('gd') || !function_exists('gd_info')) {
			$message = 'GD extension is not installed';
			$this->logError($message);
			throw new Exception($message);
		}
	}
	
	/**
	* Checks cURL requirement.
	* @throws Exception
	*/
	private function checkCurlRequirements() {
		if (!extension_loaded('curl') || !function_exists('curl_init')) {
			$message = 'cURL extension is not installed';
			$this->logError($message);
			throw new Exception($message);
		}
	}
	
	/**
	* Saves given data in the temporary file.
	*
	* @param string $data
	*/
	private function saveTempFile($data) {
		$fp = fopen($this->tempFileName,'w');
		fwrite($fp, $data);
		fclose($fp);
	}
	
	/**
	* Creates a temporary file in /tmp directory.
	*/
	private function createTempFile() {
		$this->deleteTempFile();
		$this->tempFileName = tempnam(sys_get_temp_dir(), 'php_files');
	}
	
	/**
	* Removes the temporary file which was created by the $this->createTempFile() method.
	*/
	private function deleteTempFile() {
		if ($this->tempFileName && file_exists($this->tempFileName) && is_writable($this->tempFileName)){
			unlink($this->tempFileName);
		}
	}
	
	/**
	* Triggers PHP notice with error message.
	*/
	private function logError($message) {
		@trigger_error('WordPress Wise Chat plugin error (ImagesService): '.$message, E_USER_NOTICE);
	}

	private function ensureProtectionMeasures(int $attachmentId) {
		$fullSizePath = get_attached_file($attachmentId);
		$directory = dirname($fullSizePath);
		if (!is_dir($directory) || !is_writable($directory)) {
			return;
		}
		$htaccessPath = rtrim($directory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'.htaccess';
		if (!file_exists($htaccessPath)) {
			file_put_contents($htaccessPath, 'Options All -Indexes');
		}
		$indexPath = rtrim($directory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'index.php';
		if (!file_exists($indexPath)) {
			file_put_contents($indexPath, '<'.'?'.'php // Silence is golden. ');
		}
	}
	
}