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