| Current Path : /var/www/html/components/com_jchat/Model/ |
| Current File : /var/www/html/components/com_jchat/Model/AttachmentsModel.php |
<?php
namespace JExtstore\Component\JChat\Site\Model;
/**
* @package JCHAT::ATTACHMENTS::components::com_jchat
* @subpackage models
* @author Joomla! Extensions Store
* @copyright (C) 2024 - Joomla! Extensions Store
* @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
*/
defined( '_JEXEC' ) or die( 'Restricted access' );
use Joomla\CMS\Language\Text;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Filter\InputFilter;
use Joomla\Filesystem\Folder;
use Joomla\Filesystem\File;
use JExtstore\Component\JChat\Administrator\Framework\Model as JChatModel;
use JExtstore\Component\JChat\Administrator\Framework\Helpers\Users as JChatHelpersUsers;
use JExtstore\Component\JChat\Administrator\Framework\Thumb\Factory as JChatThumbFactory;
use JExtstore\Component\JChat\Administrator\Framework\Exception\Exceptions;
/**
* Here the entity is the file attachment message on stream
*
* @package JCHAT::ATTACHMENTS::components::com_jchat
* @subpackage models
* @since 1.0
*/
class AttachmentsModel extends JChatModel {
use Exceptions;
/**
* @access private
* @var int
*/
private $cacheFolder;
/**
* User object
*
* @access private
* @var int
*/
private $myUser;
/**
* User object
*
* @access private
* @var int
*/
private $lastInsertId;
/**
* @access private
* @param string $originalFilename
* @param string $thumbFilename
* @param string $fileExtension
* @return boolean
*/
private function resizeImage($originalFilename, $thumbFilename, $fileExtension) {
$thumb = JChatThumbFactory::create($originalFilename);
$resizeWidth = $this->componentParams->get('resize_attachments_images_maxwidth', 800);
$defaultOptions = array (
'resizeUp' => false,
'jpegQuality' => $this->componentParams->get('resize_attachments_images_quality', 80),
'correctPermissions' => false,
'preserveAlpha' => true,
'alphaMaskColor' => array (255, 255, 255),
'preserveTransparency' => false,
'transparencyMaskColor' => array (0, 0, 0)
);
$thumb->setOptions($defaultOptions);
$thumb->resize($resizeWidth);
$thumb->save($thumbFilename, $fileExtension);
}
/**
* Generate file name hash
*
* @access private
* @param string $filename
* @param string $userid
* @return string
*/
private function generaHash($filename, $messageId) {
$filenameStripped = File::stripExt($filename);
$fileExtension = $this->getExt($filename);
$hash = md5($filenameStripped . $messageId);
return $hash . '.' . $fileExtension;
}
/**
* Store file attachment message on database as a special record
*
* @access private
* @param string $filename
* @return boolean
*/
private function storeDBMessage($filename) {
if ($this->getState('to', null) && !empty($filename)) {
// Get user reference
$to = $this->getState('to', null);
$tologged = $this->getState('tologged', null);
// Valid target user session id?
if($to == -1 && $tologged) {
$sessionSql = "SELECT" .
"\n " . $this->dbInstance->quoteName('session_id') .
"\n FROM #__session" .
"\n WHERE" .
"\n " . $this->dbInstance->quoteName('userid') . " = " . (int)$tologged .
"\n ORDER BY " . $this->dbInstance->quoteName('time') . " DESC" .
"\n LIMIT 1";
$this->dbInstance->setQuery($sessionSql);
$sessionIDReceiver = $this->dbInstance->loadResult();
$to = $sessionIDReceiver ? $sessionIDReceiver : -1;
}
// Get users actual names
$actualNames = JChatHelpersUsers::getActualNames ( $this->getState('from'), $this->getState('to'), $this->componentParams );
$unixTimeStamp = time();
$sql = "INSERT INTO #__jchat (" .
$this->dbInstance->quoteName('from') . ',' .
$this->dbInstance->quoteName('to') . ',' .
$this->dbInstance->quoteName('fromuser') . ',' .
$this->dbInstance->quoteName('touser') . ',' .
$this->dbInstance->quoteName('message') . ',' .
$this->dbInstance->quoteName('sent') . ',' .
$this->dbInstance->quoteName('read') . ',' .
$this->dbInstance->quoteName('type') . ',' .
$this->dbInstance->quoteName('status') . ',' .
$this->dbInstance->quoteName('actualfrom') . ',' .
$this->dbInstance->quoteName('actualto') . ',' .
$this->dbInstance->quoteName('ipaddress') . ') VALUES( ' .
$this->dbInstance->quote($this->getState('from')). ", ".
$this->dbInstance->quote($to). ",".
$this->dbInstance->quote($this->myUser->id). ", ".
$this->dbInstance->quote($tologged). ",".
$this->dbInstance->quote($filename) . ",".
$this->dbInstance->quote($unixTimeStamp) . ",".
"0" . "," .
$this->dbInstance->quote('file') . "," .
"0" . "," .
$this->dbInstance->quote($actualNames['fromActualName']) . ", ".
$this->dbInstance->quote($actualNames['toActualName']) . ", ".
$this->dbInstance->quote($_SERVER['REMOTE_ADDR']) .
")";
$this->dbInstance->setQuery($sql);
if(!$this->dbInstance->execute()){
return false;
}
if (empty($this->sessionName['jchat_user_'.$this->getState('to')])) {
$this->sessionName['jchat_user_'.$this->getState('to')] = array();
}
$lastInsertId = $this->dbInstance->insertid();
$this->lastInsertId = $lastInsertId;
$insertTime = HTMLHelper::_('date', $unixTimeStamp, Text::_('DATE_FORMAT_LC2'));
$this->sessionName['jchat_user_'.$this->getState('to')][$lastInsertId] = array(
"id" => $this->dbInstance->insertid(),
"from" => $this->getState('to'),
"message" => $filename,
"type" => 'file',
"status" => 0,
"time" => $insertTime,
"self" => 1,
"old" => 1) ;
}
return true;
}
/**
* Store file attachment message on database as a special record
* to all participants to the conference after retrieving the list of other peers involved
*
* @access private
* @param string $filename
* @return boolean
*/
private function storeDBConferenceMessage($filename) {
if (!empty($filename)) {
// Get this peer session ID identifier
$thisPeer = $this->getState('from');
if($this->getState('isLiveStreaming')) {
// Valid target user session id?
$otherConfPeersSql = "SELECT" .
"\n status.sessionid AS peer2, sess.userid" .
"\n FROM #__jchat_sessionstatus AS status" .
"\n LEFT JOIN #__session AS sess" .
"\n ON status.sessionid = sess.session_id" .
"\n WHERE" .
"\n status.livestreaming_hash = " . $this->dbInstance->quote($thisPeer) .
"\n AND status.sessionid != " . $this->dbInstance->quote($thisPeer);
} else {
// Valid target user session id?
$otherConfPeersSql = "SELECT" .
"\n conf.peer2, sess.userid" .
"\n FROM #__jchat_webrtc_conference AS conf" .
"\n LEFT JOIN #__session AS sess" .
"\n ON conf.peer2 = sess.session_id" .
"\n WHERE" .
"\n conf.peer1 = " . $this->dbInstance->quote($thisPeer);
}
$this->dbInstance->setQuery($otherConfPeersSql);
$otherConfPeers = $this->dbInstance->loadObjectList();
if(is_array($otherConfPeers) && count($otherConfPeers)) {
foreach ($otherConfPeers as $otherConfPeer) {
// Get users actual names
$actualNames = JChatHelpersUsers::getActualNames ( $thisPeer, $otherConfPeer->peer2, $this->componentParams );
$unixTimeStamp = time();
$sql = "INSERT INTO #__jchat (" .
$this->dbInstance->quoteName('from') . ',' .
$this->dbInstance->quoteName('to') . ',' .
$this->dbInstance->quoteName('fromuser') . ',' .
$this->dbInstance->quoteName('touser') . ',' .
$this->dbInstance->quoteName('message') . ',' .
$this->dbInstance->quoteName('sent') . ',' .
$this->dbInstance->quoteName('read') . ',' .
$this->dbInstance->quoteName('type') . ',' .
$this->dbInstance->quoteName('status') . ',' .
$this->dbInstance->quoteName('actualfrom') . ',' .
$this->dbInstance->quoteName('actualto') . ',' .
$this->dbInstance->quoteName('ipaddress') . ') VALUES( ' .
$this->dbInstance->quote($thisPeer). ", ".
$this->dbInstance->quote($otherConfPeer->peer2). ",".
$this->dbInstance->quote($this->myUser->id). ", ".
$this->dbInstance->quote($otherConfPeer->userid). ",".
$this->dbInstance->quote($filename) . ",".
$this->dbInstance->quote($unixTimeStamp) . ",".
"0" . "," .
$this->dbInstance->quote('file') . "," .
"0" . "," .
$this->dbInstance->quote($actualNames['fromActualName']) . ", ".
$this->dbInstance->quote($actualNames['toActualName']) . ", ".
$this->dbInstance->quote($_SERVER['REMOTE_ADDR']) .
")";
$this->dbInstance->setQuery($sql);
if(!$this->dbInstance->execute()){
return false;
}
if (empty($this->sessionName['jchat_user_'.$otherConfPeer->peer2])) {
$this->sessionName['jchat_user_'.$otherConfPeer->peer2] = array();
}
$lastInsertId = $this->dbInstance->insertid();
$this->lastInsertId = $lastInsertId;
$insertTime = HTMLHelper::_('date', $unixTimeStamp, Text::_('DATE_FORMAT_LC2'));
$this->sessionName['jchat_user_'.$otherConfPeer->peer2][$lastInsertId] = array(
"id" => $this->dbInstance->insertid(),
"from" => $otherConfPeer->peer2,
"message" => $filename,
"type" => 'file',
"status" => 0,
"time" => $insertTime,
"self" => 1,
"old" => 1) ;
}
}
}
return true;
}
/**
* Read file by chunks to send to output buffer
*
* @access private
* @param string $nomefile
* @return boolean
*/
private function readFileChunked($filePath) {
$chunksize = 1 * (1024 * 1024); // how many bytes per chunk
$buffer = '';
$cnt = 0;
$handle = fopen ( $filePath, 'rb' );
if ($handle === false) {
return false;
}
while ( ! feof ( $handle ) ) {
$buffer = fread ( $handle, $chunksize );
echo $buffer;
@ob_flush ();
flush ();
}
$status = fclose ( $handle );
return $status;
}
/**
* Detect mime type for streamed file
*
* @access private
* @param $filename
* @return string Il mime type trovato a fronte del lookup nella tabella
*/
private function detectMimeType($filename) {
global $mosConfig_absolute_path;
include_once JPATH_COMPONENT_ADMINISTRATOR . '/Framework/Helpers/mime.mapping.php';
$filename = strtolower ( $filename );
$exts = preg_split ( "#[/\\.]#i", $filename );
$n = count ( $exts ) - 1;
$fileExtension = $exts [$n];
foreach ( $mime_extension_map as $extension => $mime ) {
if ($extension === $fileExtension)
return $mime;
}
return 'application/octet-stream';
}
/**
* Gets the extension of a file name
*
* @param string $file
* The file name
*
* @return string The file extension
*/
private static function getExt($file) {
// String manipulation should be faster than pathinfo() on newer PHP versions.
$dot = strrpos ( $file, '.' );
if ($dot === false) {
return '';
}
$ext = substr ( $file, $dot + 1 );
// Extension cannot contain slashes.
if (strpos ( $ext, '/' ) !== false || (DIRECTORY_SEPARATOR === '\\' && strpos ( $ext, '\\' ) !== false)) {
return '';
}
return $ext;
}
/**
* Store uploaded file to cache folder,
* fully manage error messages and ask for database insert
*
* @access public
* @param bool $updateNulls
* @return mixed
*/
public function storeEntity($updateNulls = false) {
$tmpFile = $this->requestFilesName['newfile']['tmp_name'];
$tmpFileName = $this->requestFilesName['newfile']['name'];
if(!$tmpFile || !$tmpFileName) {
$msg = Text::_('COM_JCHAT_NOFILE_SELECTED');
$this->setException($msg);
return;
}
$tmpFileSize = $this->requestFilesName['newfile']['size'];
$allowedFileSize = $this->componentParams->get('maxfilesize', 2) * 1024 * 1024; // MB->Bytes
if($tmpFileSize > $allowedFileSize) {
$msg = Text::_('COM_JCHAT_SIZE_ERROR') .' Max ' . $this->componentParams->get('maxfilesize', 2) . 'MB.';
$this->setException($msg);
return;
}
$disallowedExtensions = explode(',', $this->componentParams->get('disallowed_extensions', 'exe,bat,pif'));
$tmpFileExtension = @array_pop(explode('.', $tmpFileName));
if(in_array(strtolower($tmpFileExtension), $disallowedExtensions)) {
$msg = Text::_('COM_JCHAT_EXT_ERROR') . $this->componentParams->get('disallowed_extensions', 'exe,bat,pif');
$this->setException($msg);
return;
}
if(!is_dir($this->cacheFolder)) {
Folder::create($this->cacheFolder);
}
if(!is_writable($this->cacheFolder)) {
try {
if(!chmod($this->cacheFolder, 0775)) {
throw new \Exception( Text::_('COM_JCHAT_DIR_WRITABLE'));
}
} catch(\Exception $e) {
$msg = $e->getMessage();
$this->setException($msg);
return;
}
}
if(!move_uploaded_file($tmpFile, $this->cacheFolder . $tmpFileName)) {
$msg = Text::_('COM_JCHAT_UPLOAD_ERROR');
$this->setException($msg);
return;
}
// Store the DB message file
$filter = InputFilter::getInstance();
if($this->getState('receiver', null) === 'conference') {
// Set the message to be received by conference joined users
if(!$this->storeDBConferenceMessage($filter->clean($tmpFileName))) {
$msg = Text::_('COM_JCHAT_SENDMSGFILE_ERROR');
$this->setException($msg);
return;
}
$this->lastInsertId = 'conference';
} else {
if(!$this->storeDBMessage($filter->clean($tmpFileName))) {
$msg = Text::_('COM_JCHAT_SENDMSGFILE_ERROR');
$this->setException($msg);
return;
}
}
// Hash and store the file on file system
$hashedFileName = $this->generaHash($tmpFileName, $this->lastInsertId);
if(file_exists($this->cacheFolder . $hashedFileName)) {
unlink($this->cacheFolder . $hashedFileName);
}
// Check if there is an image file uploaded and if the option to resize images is active
if($this->componentParams->get('resize_attachments_images', 0) && in_array(strtolower($tmpFileExtension), array('jpg', 'jpeg', 'gif'))) {
$this->resizeImage($this->cacheFolder . $tmpFileName, $this->cacheFolder . $hashedFileName, $tmpFileExtension);
unlink($this->cacheFolder . $tmpFileName);
} else {
if(!rename($this->cacheFolder . $tmpFileName, $this->cacheFolder . $hashedFileName)) {
$msg = Text::_('COM_JCHAT_RENAME_ERROR');
$this->setException($msg);
return;
}
}
$msg = Text::_('COM_JCHAT_SUCCESS_FILEUPLOAD');
$this->setState('result', $msg);
}
/**
* Download uploaded file message
*
* @access public
* @return void
*/
public function loadEntity($ids = null) {
$idMessage = $this->getState('idMessage');
$idUserConversation = $this->getState('from');
try {
$query = "SELECT #__jchat.from, #__jchat.message FROM #__jchat WHERE id = " . (int)$idMessage;
$this->dbInstance->setQuery($query);
$resultInfo = $this->dbInstance->loadObject();
if(!$resultInfo) {
$conversationArray = $this->sessionName['jchat_user_' . $idUserConversation];
foreach ($conversationArray as $message) {
if($message['id'] == $idMessage) {
$resultInfo = new \stdClass();
$resultInfo->from = $message['from'];
$resultInfo->message = $message['message'];
break;
}
}
if(!$resultInfo) {
throw new \Exception('COM_JCHAT_ERROR_NOTFOUND_FILE');
}
}
$fileName = $this->generaHash($resultInfo->message, $idMessage);
$filePath = $this->cacheFolder . $fileName;
if(!file_exists($filePath)) {
// Check if there is a conference shared file, fallback here before throwing an error
$fileName = $this->generaHash($resultInfo->message, 'conference');
$filePath = $this->cacheFolder . $fileName;
if(!file_exists($filePath)) {
throw new \Exception('COM_JCHAT_ERROR_DELETED_FILE');
}
}
} catch (\Exception $e) {
// JS inject
$appNonce = $this->app->get('csp_nonce', null);
$nonce = $appNonce ? ' nonce="' . $appNonce . '"' : '';
echo '<script' . $nonce . '>alert("' . Text::_($e->getMessage()) . '");window.history.go(-1);</script>';
exit();
}
$fsize = @filesize ( $filePath );
$mod_date = date ( 'r', filemtime ( $filePath ) );
$cont_dis = 'attachment';
$mimeType = $this->detectMimeType ( $fileName );
// required for IE, otherwise Content-disposition is ignored
if (ini_get ( 'zlib.output_compression' )) {
ini_set ( 'zlib.output_compression', 'Off' );
}
header ( "Pragma: public" );
header ( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
header ( "Expires: 0" );
header ( "Content-Transfer-Encoding: binary" );
header ( 'Content-Disposition:' . $cont_dis . ';' . ' filename="' . $resultInfo->message . '";' . ' modification-date="' . $mod_date . '";' . ' size=' . $fsize . ';' ); //RFC2183
header ( "Content-Type: " . $mimeType ); // MIME type
header ( "Content-Length: " . $fsize );
if (! ini_get ( 'safe_mode' )) { // set_time_limit doesn't work in safe mode
@set_time_limit ( 0 );
}
// No encoding - we aren't using compression... (RFC1945)
//header("Content-Encoding: none");
//header("Vary: none");
$downloadStatus = $this->readFileChunked ( $filePath );
// Al raggiungimento dell'effettivo download si aggiorna lo status update
if($downloadStatus) {
$query = "UPDATE #__jchat SET status=1 WHERE id = " . (int)$idMessage;
$this->dbInstance->setQuery($query);
$this->dbInstance->execute();
}
exit();
}
/**
* Class constructor
*
* @access public
* @return Object &
*/
public function __construct($config = array(), ?MVCFactoryInterface $factory = null) {
$this->getComponentParams();
$this->cacheFolder = JPATH_SITE . '/components/com_jchat/cache/';
$this->myUser = Factory::getApplication()->getIdentity();
parent::__construct( $config, $factory );
}
}
?>