Your IP : 216.73.216.182


Current Path : /var/www/html/components/com_osmembership/controller/
Upload File :
Current File : /var/www/html/components/com_osmembership/controller/controller.php

<?php
/**
 * @package        Joomla
 * @subpackage     Membership Pro
 * @author         Tuan Pham Ngoc
 * @copyright      Copyright (C) 2012 - 2026 Ossolution Team
 * @license        GNU/GPL, see LICENSE.php
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
use Joomla\Database\DatabaseDriver;
use Joomla\Filesystem\Exception\FilesystemException;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
use Joomla\Filesystem\Path;
use Joomla\Utilities\IpHelper;

class OSMembershipController extends MPFController
{
	use OSMembershipControllerData;
	use OSMembershipControllerDisplay;
	use MPFControllerDownload;

	/**
	 * Process downloading invoice for a subscription record based on given ID
	 */
	public function download_invoice()
	{
		$id = $this->input->getInt('id', 0);

		$db = Factory::getContainer()->get('db');

		$row = new OSMembershipTableSubscriber($db);

		if (!$row->load($id))
		{
			throw new Exception(Text::_('Invalid Invoice'), 404);
		}

		if (!$row->invoice_number)
		{
			throw new RuntimeException('Invalid Invoice');
		}

		// Check download invoice permission
		$canDownload = false;

		$user = $this->app->getIdentity();

		if ($user->authorise('core.admin', 'com_osmembership'))
		{
			$canDownload = true;
		}
		elseif ($user->authorise('membershippro.subscriptions', 'com_osmembership'))
		{
			$plan = OSMembershipHelperDatabase::getPlan($row->plan_id);

			if (in_array($plan->subscriptions_manage_user_id, [0, $user->id]))
			{
				$canDownload = true;
			}
		}
		elseif ($row->user_id > 0 && ($row->user_id == $user->id))
		{
			$canDownload = true;
		}

		if ($canDownload)
		{
			$invoicePath = OSMembershipHelper::generateAndReturnInvoicePath($row);

			$this->processDownloadFile($invoicePath, null, false, true);
		}
		else
		{
			throw new Exception(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
		}
	}

	/**
	 * Download selected document from membership profile
	 *
	 * @throws Exception
	 */
	public function download_document()
	{
		$planIds = OSMembershipHelperSubscription::getActivePlanIdsForUser();

		if (count($planIds) == 0)
		{
			throw new Exception(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
		}

		$id = $this->input->getInt('id', 0);

		/* @var DatabaseDriver $db */
		$db    = Factory::getContainer()->get('db');
		$query = $db->getQuery(true)
			->select('a.*')
			->from('#__osmembership_documents AS a')
			->where(
				'a.id IN (SELECT document_id FROM #__osmembership_plan_documents AS b WHERE b.plan_id  IN ('
				. implode(',', $planIds)
				. '))'
			)
			->where('a.id = ' . $id);
		$db->setQuery($query);
		$document = $db->loadObject();

		if (!$document)
		{
			throw new Exception(Text::_('Document not found or you are not allowed to download this document'), 404);
		}

		$path     = OSMembershipHelper::getDocumentsPath();
		$filePath = Path::clean($path . '/');
		$fileName = $document->attachment;

		if (file_exists($filePath . $fileName))
		{
			$this->processDownloadFile($filePath . $fileName, OSMembershipHelper::getOriginalFilename($fileName));
		}
		else
		{
			throw new Exception(Text::_('Document not found. Please contact administrator'), 404);
		}
	}

	/**
	 * Method to allow downloading schedule document
	 */
	public function download_schedule_document()
	{
		$id = $this->input->getInt('id', 0);

		/* @var DatabaseDriver $db */
		$db    = Factory::getContainer()->get('db');
		$query = $db->getQuery(true)
			->select('*')
			->from('#__osmembership_scheduledocuments')
			->where('id  = ' . $id);
		$db->setQuery($query);
		$document = $db->loadObject();

		if (!$document)
		{
			throw new Exception('Document not found', 404);
		}

		// Check to see if the user has access to this document
		$canAccess     = false;
		$subscriptions = OSMembershipHelper::callOverridableHelperMethod('Subscription', 'getUserSubscriptionsInfo');

		if (isset($subscriptions[$document->plan_id]))
		{
			$subscription = $subscriptions[$document->plan_id];

			if ($subscription->active_in_number_days >= $document->number_days)
			{
				$canAccess = true;
			}
		}

		if ($canAccess)
		{
			$this->processDownloadFile(JPATH_ROOT . '/media/com_osmembership/scheduledocuments/' . $document->document);
		}
		else
		{
			throw new Exception('You are not allowed to download this document');
		}
	}

	/**
	 * Download a file uploaded by users
	 *
	 * @throws Exception
	 */
	public function download_file()
	{
		$fileName = $this->input->get('file_name', '', 'string');
		$inline   = (bool) $this->input->getInt('inline', 0);

		$fileName = basename($fileName);

		$filePath = JPATH_ROOT . '/media/com_osmembership/upload/' . $fileName;

		if ($fileName && file_exists($filePath))
		{
			if (
				!OSMembershipHelper::isImageFilename($fileName)
				|| !(new OSMembershipHelperImage())->isValidImage($filePath)
			)
			{
				$inline = 0;
			}

			// Check permission
			$canDownload = false;
			$user        = $this->app->getIdentity();

			if ($user->authorise('core.admin', 'com_osmembership')
				|| $user->authorise('membershippro.subscriptions', 'com_osmembership'))
			{
				// Users with registrants management is allowed to download file
				$canDownload = true;
			}
			elseif ($user->id)
			{
				// User can only download the file uploaded by himself
				/* @var DatabaseDriver $db */
				$db = Factory::getContainer()->get('db');

				// Get list of published file upload custom fields
				$query = $db->getQuery(true)
					->select('id')
					->from('#__osmembership_fields')
					->where('fieldtype = "File"');
				$db->setQuery($query);
				$fieldIds = $db->loadColumn();

				if (count($fieldIds))
				{
					$query->clear()
						->select('COUNT(*)')
						->from('#__osmembership_subscribers AS a')
						->innerJoin('#__osmembership_field_value AS b ON a.id = b.subscriber_id')
						->where('a.user_id = ' . $user->id)
						->whereIn('b.field_id', $fieldIds)
						->where('b.field_value = ' . $db->quote($fileName));
					$db->setQuery($query);
					$total = (int) $db->loadResult();

					if ($total)
					{
						$canDownload = true;
					}
				}
			}

			if (!$canDownload)
			{
				$this->app->enqueueMessage(Text::_('You do not have permission to download this file'), 'error');
				$this->app->redirect(Uri::root(), 403);

				return;
			}

			$fileName = OSMembershipHelper::removePrefixFromUploadedFilename($fileName);

			$this->processDownloadFile(
				$filePath,
				OSMembershipHelper::getOriginalFilename($fileName),
				$inline
			);
		}
		else
		{
			$this->app->enqueueMessage(Text::_('OSM_FILE_NOT_EXIST'));
			$this->app->redirect('index.php?option=com_osmembership&Itemid=' . $this->input->getInt('Itemid'), 404);
		}
	}

	/**
	 * Process upload file
	 */
	public function upload_file()
	{
		Session::checkToken('get') or jexit(Text::_('JINVALID_TOKEN'));

		$fieldId = $this->input->getInt('field_id', 0);

		/* @var DatabaseDriver $db */
		$db    = Factory::getContainer()->get('db');
		$query = $db->getQuery(true)
			->select('*')
			->from('#__osmembership_fields')
			->where('id = ' . (int) $fieldId);
		$db->setQuery($query);
		$rowField = $db->loadObject();

		if (!$rowField)
		{
			echo json_encode(['error' => Text::_('OSM_FIELD_NOT_VALID')]);

			$this->app->close();
		}

		// Make sure this is valid File field type
		if ($rowField->fieldtype !== 'File')
		{
			echo json_encode(['error' => Text::_('OSM_NOT_FILE_FIELD_TYPE')]);

			$this->app->close();
		}

		// Make sure field is published
		if (!$rowField->published)
		{
			echo json_encode(['error' => Text::_('OSM_FIELD_NOT_VALID')]);

			$this->app->close();
		}

		// Validate access level
		if (!in_array($rowField->access, $this->app->getIdentity()->getAuthorisedViewLevels()))
		{
			echo json_encode(['error' => Text::_('OSM_NO_PERMISSION_UPLOAD_FILE')]);

			$this->app->close();
		}

		$config     = OSMembershipHelper::getConfig();
		$json       = [];
		$pathUpload = JPATH_ROOT . '/media/com_osmembership/upload';

		if (!is_dir($pathUpload))
		{
			Folder::create($pathUpload);
		}

		// Rate limiting: check per-session upload count
		$maxFilesPerSession = (int) $config->get('upload_max_files_per_session', 100);

		if ($maxFilesPerSession > 0)
		{
			$session          = $this->app->getSession();
			$sessionUploadKey = 'osm_upload_count';
			$uploadCount      = (int) $session->get($sessionUploadKey, 0);

			if ($uploadCount >= $maxFilesPerSession)
			{
				echo json_encode(
					['error' => Text::sprintf('OSM_FILE_UPLOAD_RATE_LIMIT_EXCEEDED', $maxFilesPerSession)]
				);

				$this->app->close();
			}
		}

		// Rate limiting: check per-session upload count per minute
		$maxFilesPerMinute = (int) $config->get('upload_max_files_per_minute', 20);

		if ($maxFilesPerMinute > 0)
		{
			$session               = $this->app->getSession();
			$sessionUploadTimesKey = 'osm_upload_times';
			$uploadTimes           = (array) $session->get($sessionUploadTimesKey, []);
			$now                   = time();

			// Keep only the upload timestamps within the last 60 seconds
			$uploadTimes = array_values(array_filter($uploadTimes, function ($timestamp) use ($now) {
				return $timestamp > ($now - 60);
			}));

			if (count($uploadTimes) >= $maxFilesPerMinute)
			{
				echo json_encode(
					['error' => Text::sprintf('OSM_FILE_UPLOAD_PER_MINUTE_RATE_LIMIT_EXCEEDED', $maxFilesPerMinute)]
				);

				$this->app->close();
			}

			$session->set($sessionUploadTimesKey, $uploadTimes);
		}

		// Folder size limit check
		$folderSizeLimit = (int) $config->get('upload_folder_size_limit', 0);

		if ($folderSizeLimit > 0)
		{
			$folderSizeLimitBytes = $folderSizeLimit * 1024 * 1024;
			$currentFolderSize    = 0;

			if (is_dir($pathUpload))
			{
				foreach (
					new \RecursiveIteratorIterator(
						new \RecursiveDirectoryIterator($pathUpload, \FilesystemIterator::SKIP_DOTS)
					) as $fileItem
				)
				{
					$currentFolderSize += $fileItem->getSize();
				}
			}

			if ($currentFolderSize >= $folderSizeLimitBytes)
			{
				echo json_encode(['error' => Text::_('OSM_FILE_UPLOAD_FOLDER_SIZE_LIMIT_EXCEEDED')]);

				$this->app->close();
			}
		}

		// Max files in folder check
		$maxFilesInFolder = (int) $config->get('upload_max_files_in_folder', 0);

		if ($maxFilesInFolder > 0)
		{
			$filesInFolder = 0;

			if (is_dir($pathUpload))
			{
				$iterator      = new \FilesystemIterator($pathUpload, \FilesystemIterator::SKIP_DOTS);
				$filesInFolder = iterator_count($iterator);
			}

			if ($filesInFolder >= $maxFilesInFolder)
			{
				echo json_encode(['error' => Text::_('OSM_FILE_UPLOAD_FOLDER_FILES_LIMIT_EXCEEDED')]);

				$this->app->close();
			}
		}

		$allowedExtensions = OSMembershipHelper::getAllowedFileTypes(trim($rowField->allowed_file_types));

		$file     = $this->input->files->get('file', [], 'raw');
		$fileName = $file['name'];
		$fileExt  = OSMembershipHelper::getFileExt($fileName);

		if (in_array(strtolower($fileExt), $allowedExtensions))
		{
			$canUpload = true;

			if ($config->upload_max_file_size > 0)
			{
				$maxFileSizeInByte = $config->upload_max_file_size * 1024 * 1024;

				if ($file['size'] > $maxFileSizeInByte)
				{
					$json['error'] = Text::sprintf('OSM_FILE_SIZE_TOO_LARGE', $config->upload_max_file_size);
					$canUpload     = false;
				}
			}

			if (version_compare(JVERSION, '5.4.8', '<')
				|| (version_compare(JVERSION, '6.0.0', '>=') && version_compare(JVERSION, '6.1.2', '<')))
			{
				/**
				 * We need to perform unsafe options check manually for Joomla earlier than 5.4.8 and
				 * Joomla 6.0.0 to Joomla 6.1.2 because the unsafe check is not supported by File::upload in
				 * these Joomla versions
				 */

				if (!InputFilter::isSafeFile($file))
				{
					$json['error'] = Text::_('OSM_FILE_IS_NOT_SAFE_FOR_UPLOAD');
					$canUpload     = false;
				}
			}

			$imageHelper = new OSMembershipHelperImage();

			if (in_array($fileExt, $imageHelper->getAllImageFileExtensions())
				&& !$imageHelper->isValidUploadedImage($file))
			{
				$json['error'] = Text::_('OSM_FILE_IS_NOT_VALID_IMAGE');
				$canUpload     = false;
			}

			if ($canUpload)
			{
				$fileName = File::makeSafe($fileName);

				// Randomize filename
				while (true)
				{
					$targetFileName = bin2hex(random_bytes(8)) . '_' . $fileName;

					if (!is_file($pathUpload . '/' . $targetFileName))
					{
						break;
					}
				}

				try
				{
					File::upload($file['tmp_name'], $pathUpload . '/' . $targetFileName);

					// Increment the per-session upload counter on successful upload
					if ($maxFilesPerSession > 0)
					{
						$session->set($sessionUploadKey, $uploadCount + 1);
					}

					// Record the upload timestamp for the per-minute rate limit check
					if ($maxFilesPerMinute > 0)
					{
						$uploadTimes[] = time();
						$session->set($sessionUploadTimesKey, $uploadTimes);
					}

					$json['success'] = Text::sprintf('OSM_FILE_UPLOADED', $fileName);
					$json['file']    = $targetFileName;
				}
				catch (FilesystemException $e)
				{
					$json['error'] = Text::sprintf('OSM_FILE_UPLOAD_FAILED', $fileName);
				}
			}
		}
		else
		{
			$json['error'] = Text::sprintf('OSM_FILE_NOT_ALLOWED', $fileExt, implode(', ', $allowedExtensions));
		}

		echo json_encode($json);

		$this->app->close();
	}

	/**
	 * Method to allow downloading update package for the given extension
	 *
	 * @throws Exception
	 */
	public function download_update_package()
	{
		// Check and make sure Joomla update is supported on this site before processing further
		$documentsPath        = OSMembershipHelper::getDocumentsPath();
		$updatePackagesFolder = Path::clean($documentsPath . '/update_packages');

		$domain         = $this->input->getString('domain');
		$downloadId     = trim($this->input->getString('download_id'));
		$validateDomain = true;

		// Try to get Download ID from Joomla core installer
		if (empty($downloadId))
		{
			$downloadId = $this->input->getString('dlid', '');

			if ($downloadId)
			{
				$validateDomain = false;
			}
		}

		$documentId = $this->input->getInt('document_id', 0);

		if (empty($domain) && $validateDomain)
		{
			throw new Exception('Invalid Domain', 403);
		}

		if (empty($downloadId))
		{
			throw new Exception('Invalid Download ID', 403);
		}

		if (empty($documentId))
		{
			throw new Exception('Invalid Extension ID', 403);
		}

		/* @var DatabaseDriver $db */
		$db    = Factory::getContainer()->get('db');
		$query = $db->getQuery(true)
			->select('*')
			->from('#__osmembership_downloadids')
			->where('download_id = ' . $db->quote($downloadId));
		$db->setQuery($query);
		$registeredId = $db->loadObject();

		if (!$registeredId)
		{
			throw new Exception('Invalid Download ID', 404);
		}

		$domain           = str_replace('www.', '', $domain);
		$registeredDomain = str_replace('www.', '', $registeredId->domain);

		if ($validateDomain && $registeredDomain && $registeredDomain != $domain)
		{
			throw new Exception(
				'This download ID as used for different domain already. You need to register a new download ID for this domain',
				403
			);
		}

		$userId = $registeredId->user_id;
		$user   = Factory::getUser($userId);

		if (!$user->id)
		{
			throw new Exception('User does not exist', 404);
		}

		// Check to see whether user has permission to download this documentl
		$planIds = OSMembershipHelperSubscription::getActivePlanIdsForUser($userId);

		if (count($planIds) == 0)
		{
			throw new Exception(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
		}

		$query->clear()
			->select('a.*')
			->from('#__osmembership_documents AS a')
			->where(
				'a.id IN (SELECT document_id FROM #__osmembership_plan_documents AS b WHERE b.plan_id IN ('
				. implode(',', $planIds)
				. ') )'
			)
			->where('a.id = ' . $documentId);
		$db->setQuery($query);
		$document = $db->loadObject();

		if (!$document)
		{
			throw new Exception(
				Text::_('Update package not found or you are not allowed to download this update package'), 404
			);
		}

		if ($document->update_package
			&& is_file(Path::clean($updatePackagesFolder . '/' . $document->update_package)))
		{
			$filePath = $updatePackagesFolder . '/' . $document->update_package;
		}
		else
		{
			$filePath = $documentsPath . '/' . $document->attachment;
		}

		if (!is_file(Path::clean($filePath)))
		{
			throw new Exception('Update package not found', 404);
		}

		// OK, valid
		if (empty($registeredId->domain))
		{
			$query->clear()
				->update('#__osmembership_downloadids')
				->set('domain = ' . $db->quote($domain))
				->where('id = ' . $registeredId->id);
			$db->setQuery($query);
			$db->execute();
		}

		//Log the download to database
		$columns = [
			'download_id',
			'document_id',
			'download_date',
			'domain',
			'server_ip',
		];

		$values = [
			$registeredId->id,
			$documentId,
			$db->quote(Factory::getDate('now')->toSql()),
			$db->quote($domain),
			$db->quote(IpHelper::getIp()),
		];

		$query->clear()
			->insert('#__osmembership_downloadlogs')
			->columns($db->quoteName($columns))
			->values(implode(',', $values));

		$db->setQuery($query);
		$db->execute();

		$this->processDownloadFile(
			$filePath,
			OSMembershipHelper::getOriginalFilename($document->update_package ?: $document->attachment)
		);
	}

	/**
	 * Render a signature image stored in images/com_osmembership/signatures/ inline.
	 *
	 * Only administrators and the subscriber who owns the signature may access it.
	 *
	 * @throws Exception
	 */
	public function render_signature()
	{
		$signaturesPath = JPATH_ROOT . '/images/com_osmembership/signatures';
		$fileName       = $this->input->get('signature_file', '', 'string');

		// Validate filename format: exactly 32 hex chars + .png
		if (!preg_match('/^[a-f0-9]{32}\.png$/i', $fileName))
		{
			throw new Exception(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
		}

		$filePath = $signaturesPath . '/' . $fileName;

		if (!file_exists($filePath))
		{
			throw new Exception(Text::_('OSM_FILE_NOT_EXIST'), 404);
		}

		$this->processDownloadFile($filePath, $fileName, true);
	}

	/**
	 * Download user file from user profile
	 *
	 * @throws Exception
	 */
	public function download_user_file()
	{
		$user = $this->app->getIdentity();

		if (!$user->id)
		{
			throw new RuntimeException('You need to login to download files assigned to your account');
		}

		$basePath = JPATH_ROOT . '/media/com_osmembership/userfiles/';

		if (is_dir($basePath . $user->id))
		{
			$path = $basePath . $user->id . '/';
		}
		elseif (is_dir($basePath . $user->username))
		{
			$path = $basePath . $user->username . '/';
		}
		else
		{
			$path = '';
		}

		if (empty($path))
		{
			throw new Exception(Text::_('No document available for your account. Please contact administrator'), 403);
		}

		$file = rawurldecode($this->input->getString('file'));
		$file = File::makeSafe($file);

		if (file_exists($path . $file))
		{
			$this->processDownloadFile($path . $file);
		}
		else
		{
			throw new Exception(Text::_('File not found. Please contact administrator'), 404);
		}
	}
}