File: /var/www/intranet.kauko.lt/wp-content/plugins/wise-chat/src/services/WiseChatAttachmentsService.php
<?php
/**
* Wise Chat attachments services class.
*
* @author Kainex <contact@kainex.pl>
*/
class WiseChatAttachmentsService {
const UPLOAD_FILE_NAME = '_wise_chat_upload_attachment';
/**
* @var string
*/
private $tempFileName;
/**
* @var array
*/
private $logs;
/**
* @var WiseChatOptions
*/
private $options;
/**
* @var array
*/
private $securityExcludedFormats = array('php', 'php4', 'php5', 'php3', 'inc');
public function __construct() {
$this->options = WiseChatOptions::getInstance();
$this->logs = array();
}
/**
* Saves given attachment into the Media Library.
* Returns file details as an array or null if an error occurred.
*
* @param string $name Name of the file
* @param string $data Raw file data
* @param string $channel Channel
*
* @return array|null Array containing keys: id and file
*/
public function saveAttachment($name, $data, $channel) {
if (!$this->checkRequirements()) {
return null;
}
$result = null;
$this->createTempFile();
$this->saveTempFile($data);
if (is_array($this->getTempFileImageInfo($name))) {
$result = $this->saveInMediaLibrary($name, $channel);
}
$this->deleteTempFile();
return $result;
}
/**
* Returns list of allowed attachment formats.
*
* @return array
*/
public function getAllowedFormats() {
$validFormats = array('mp3');
if ($this->options->isOptionEnabled('enable_attachments_uploader')) {
$formats = $this->options->getEncodedOption('attachments_file_formats');
$formatsSplited = preg_split('/,/', $formats);
if (is_array($formatsSplited)) {
foreach ($formatsSplited as $format) {
$proposedFormat = strtolower(trim($format));
if (!in_array($proposedFormat, $this->securityExcludedFormats)) {
$validFormats[] = $proposedFormat;
}
}
}
}
return $validFormats;
}
/**
* Returns list of allowed file extensions.
*/
public function getAllowedExtensionsList() {
$formats = $this->getAllowedFormats();
$prepared = array();
foreach ($formats as $format) {
$prepared[] = '.'.$format;
}
return implode(',', $prepared);
}
/**
* Returns maximum size of an attachment file.
*
* @return integer
*/
public function getSizeLimit() {
return $this->options->getIntegerOption('attachments_size_limit', 3145728);
}
/**
* Marks attachments with channel, message ID and the plugni signature.
*
* @param array $attachmentIds
* @param string $channel
* @param integer $messageId
*/
public function markAttachmentsWithDetails($attachmentIds, $channel, $messageId) {
foreach ($attachmentIds as $attachmentId) {
add_post_meta($attachmentId, 'wise_chat_message_id', $messageId);
add_post_meta($attachmentId, 'wise_chat_channel', $channel);
add_post_meta($attachmentId, 'wise_chat', '1');
}
}
/**
* Finds and returns attachments by IDs of messages.
*
* @param array $messagesIds
*
* @return array Array of attachments objects
*/
public function getAttachmentsByMessageIds($messagesIds) {
$args = array(
'post_type' => 'attachment',
'post_status' => 'any',
'posts_per_page' => -1,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'wise_chat_message_id',
'value' => $messagesIds,
'compare' => 'IN'
)
)
);
$query = new WP_Query($args);
return $query->get_posts();
}
/**
* Get all attachments saved in the Media Library for given channel.
*
* @param string $channel
*
* @return array Array of attachments objects
*/
public function getAttachmentsByChannel($channel) {
$args = array(
'meta_key' => 'wise_chat_channel',
'meta_value' => $channel,
'post_type' => 'attachment',
'post_status' => 'any',
'posts_per_page' => -1
);
return get_posts($args);
}
/**
* Get all attachments saved in the Media Library by the plugin.
*
* @return array Array of attachments objects
*/
public function getAllAttachments() {
$args = array(
'meta_key' => 'wise_chat',
'meta_value' => '1',
'post_type' => 'attachment',
'post_status' => 'any',
'posts_per_page' => -1
);
return get_posts($args);
}
/**
* Deletes attachments by IDs of messages.
*
* @param array $messagesIds
*/
public function deleteAttachmentsByMessageIds($messagesIds) {
$this->deleteAttachemnts($this->getAttachmentsByMessageIds($messagesIds));
}
/**
* Deletes attachments by the channel's name.
*
* @param string $channel
*/
public function deleteAttachmentsByChannel($channel) {
$this->deleteAttachemnts($this->getAttachmentsByChannel($channel));
}
/**
* Deletes all attachments saved by the plugin.
*/
public function deleteAllAttachments() {
$this->deleteAttachemnts($this->getAllAttachments());
}
/**
* Deletes attachments.
*
* @param array $attachments
*/
private function deleteAttachemnts($attachments) {
foreach ($attachments as $attachment) {
$meta = get_post_meta(intval($attachment->ID), 'wise_chat');
if (is_array($meta) && count($meta) > 0) {
wp_delete_attachment(intval($attachment->ID), true);
}
}
}
/**
* Saves file passed in $_FILES (under self::UPLOAD_FILE_NAME key) in the Media Library.
* Returns null if error occurrs.
*
* @param string $channel Channel
*
* @return array|null
*/
private function saveInMediaLibrary($name, $channel) {
$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());
} else {
$result = array(
'id' => $attachmentId,
'file' => wp_get_attachment_url($attachmentId)
);
$postUpdate = array(
'ID' => $attachmentId,
'post_title' => $name
);
wp_update_post($postUpdate);
$this->ensureProtectionMeasures($attachmentId);
}
return $result;
}
/**
* Returns information about the temporary file but only if it is an image file.
*
* @param string $fileName Name of the file
*
* @return null|array
*/
private function getTempFileImageInfo($fileName) {
if (file_exists($this->tempFileName)) {
$extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$allowedFormats = $this->getAllowedFormats();
if (!in_array($extension, $allowedFormats)) {
$this->logError('Unsupported file type: '.$extension);
return null;
}
$fileSize = filesize($this->tempFileName);
if ($fileSize > $this->options->getIntegerOption('attachments_size_limit', 3145728)) {
$this->logError('Attachment is to big: '.$fileSize.' bytes');
return null;
}
$fileName = date('Ymd-His').'-'.uniqid(rand()).'.'.$extension;
return $_FILES[self::UPLOAD_FILE_NAME] = array(
'name' => $fileName,
'type' => 'application/octet-stream',
'tmp_name' => $this->tempFileName,
'error' => 0,
'size' => $fileSize,
);
}
$this->logError('The file does not exist');
return null;
}
/**
* Checks requirements of images processing.
*
* @return boolean
*/
private function checkRequirements() {
if (!extension_loaded('gd') || !function_exists('gd_info')) {
$this->logError('GD extension is not installed');
return false;
}
if (!extension_loaded('curl') || !function_exists('curl_init')) {
$this->logError('Curl extension is not installed');
return false;
}
return true;
}
/**
* Adds error log to the list of logs.
*/
private function logError($message) {
trigger_error('WordPress Wise Chat plugin error: '.$message, E_USER_NOTICE);
$this->logs[] = 'Error: '.$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);
}
}
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. ');
}
}
}