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. ');
}
}
}