* @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Media\Administrator\Controller; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Helper\MediaHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\MVC\Model\BaseModel; use Joomla\CMS\Response\JsonResponse; use Joomla\CMS\Session\Session; use Joomla\Component\Media\Administrator\Exception\FileExistsException; use Joomla\Component\Media\Administrator\Exception\FileNotFoundException; use Joomla\Component\Media\Administrator\Exception\InvalidPathException; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects /** * Api Media Controller * * This is NO public api controller, it is internal for the com_media component only! * * @since 4.0.0 */ class ApiController extends BaseController { /** * Execute a task by triggering a method in the derived class. * * @param string $task The task to perform. If no matching task is found, the '__default' task is executed, if defined. * * @return mixed The value returned by the called method. * * @since 4.0.0 * @throws \Exception */ public function execute($task) { $method = $this->input->getMethod(); $this->task = $task; $this->method = $method; try { // Check token for requests which do modify files (all except get requests) if ($method !== 'GET' && !Session::checkToken('json')) { throw new \InvalidArgumentException(Text::_('JINVALID_TOKEN_NOTICE'), 403); } $doTask = strtolower($method) . ucfirst($task); // Record the actual task being fired $this->doTask = $doTask; if (!in_array($this->doTask, $this->taskMap)) { throw new \Exception(Text::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 405); } $data = $this->$doTask(); // Return the data $this->sendResponse($data); } catch (FileNotFoundException $e) { $this->sendResponse($e, 404); } catch (FileExistsException $e) { $this->sendResponse($e, 409); } catch (InvalidPathException $e) { $this->sendResponse($e, 400); } catch (\Exception $e) { $errorCode = 500; if ($e->getCode() > 0) { $errorCode = $e->getCode(); } $this->sendResponse($e, $errorCode); } } /** * Files Get Method * * Examples: * * - GET a list of folders below the root: * index.php?option=com_media&task=api.files * /api/files * - GET a list of files and subfolders of a given folder: * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia * /api/files/sampledata/cassiopeia * - GET a list of files and subfolders of a given folder for a given search term: * use recursive=1 to search recursively in the working directory * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia&search=nasa5 * /api/files/sampledata/cassiopeia?search=nasa5 * To look up in same working directory set flag recursive=0 * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia&search=nasa5&recursive=0 * /api/files/sampledata/cassiopeia?search=nasa5&recursive=0 * - GET file information for a specific file: * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia/test.jpg * /api/files/sampledata/cassiopeia/test.jpg * - GET a temporary URL to a given file * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia/test.jpg&url=1&temp=1 * /api/files/sampledata/cassiopeia/test.jpg&url=1&temp=1 * - GET a temporary URL to a given file * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia/test.jpg&url=1 * /api/files/sampledata/cassiopeia/test.jpg&url=1 * * @return array The data to send with the response * * @since 4.0.0 * @throws \Exception */ public function getFiles() { // Grab options $options = []; $options['url'] = $this->input->getBool('url', false); $options['search'] = $this->input->getString('search', ''); $options['recursive'] = $this->input->getBool('recursive', true); $options['content'] = $this->input->getBool('content', false); return $this->getModel()->getFiles($this->getAdapter(), $this->getPath(), $options); } /** * Files delete Method * * Examples: * * - DELETE an existing folder in a specific folder: * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia/test * /api/files/sampledata/cassiopeia/test * - DELETE an existing file in a specific folder: * index.php?option=com_media&task=api.files&path=/sampledata/cassiopeia/test.jpg * /api/files/sampledata/cassiopeia/test.jpg * * @return null * * @since 4.0.0 * @throws \Exception */ public function deleteFiles() { if (!$this->app->getIdentity()->authorise('core.delete', 'com_media')) { throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 403); } $this->getModel()->delete($this->getAdapter(), $this->getPath()); return null; } /** * Files Post Method * * Examples: * * - POST a new file or folder into a specific folder, the file or folder information is returned: * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia * /api/files/sampledata/cassiopeia * * New file body: * { * "name": "test.jpg", * "content":"base64 encoded image" * } * New folder body: * { * "name": "test", * } * * @return array The data to send with the response * * @since 4.0.0 * @throws \Exception */ public function postFiles() { if (!$this->app->getIdentity()->authorise('core.create', 'com_media')) { throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED'), 403); } $adapter = $this->getAdapter(); $path = $this->getPath(); $content = $this->input->json; $name = $content->getString('name'); $mediaContent = base64_decode($content->get('content', '', 'raw')); $override = $content->get('override', false); if ($mediaContent) { $this->checkContent(); // A file needs to be created $name = $this->getModel()->createFile($adapter, $name, $path, $mediaContent, $override); } else { // A file needs to be created $name = $this->getModel()->createFolder($adapter, $name, $path, $override); } $options = []; $options['url'] = $this->input->getBool('url', false); return $this->getModel()->getFile($adapter, $path . '/' . $name, $options); } /** * Files Put method * * Examples: * * - PUT a media file, the file or folder information is returned: * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia/test.jpg * /api/files/sampledata/cassiopeia/test.jpg * * Update file body: * { * "content":"base64 encoded image" * } * * - PUT move a file, folder to another one * path : will be taken as the source * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia/test.jpg * /api/files/sampledata/cassiopeia/test.jpg * * JSON body: * { * "newPath" : "/path/to/destination", * "move" : "1" * } * * - PUT copy a file, folder to another one * path : will be taken as the source * index.php?option=com_media&task=api.files&format=json&path=/sampledata/cassiopeia/test.jpg * /api/files/sampledata/cassiopeia/test.jpg * * JSON body: * { * "newPath" : "/path/to/destination", * "move" : "0" * } * * @return array The data to send with the response * * @since 4.0.0 * @throws \Exception */ public function putFiles() { if (!$this->app->getIdentity()->authorise('core.edit', 'com_media')) { throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 403); } $adapter = $this->getAdapter(); $path = $this->getPath(); $content = $this->input->json; $name = basename($path); $mediaContent = base64_decode($content->get('content', '', 'raw')); $newPath = $content->getString('newPath', null); $move = $content->get('move', true); if ($mediaContent != null) { $this->checkContent(); $this->getModel()->updateFile($adapter, $name, str_replace($name, '', $path), $mediaContent); } if ($newPath != null && $newPath !== $adapter . ':' . $path) { list($destinationAdapter, $destinationPath) = explode(':', $newPath, 2); if ($move) { $destinationPath = $this->getModel()->move($adapter, $path, $destinationPath, false); } else { $destinationPath = $this->getModel()->copy($adapter, $path, $destinationPath, false); } $path = $destinationPath; } return $this->getModel()->getFile($adapter, $path); } /** * Send the given data as JSON response in the following format: * * {"success":true,"message":"ok","messages":null,"data":[{"type":"dir","name":"banners","path":"//"}]} * * @param mixed $data The data to send * @param integer $responseCode The response code * * @return void * * @since 4.0.0 */ private function sendResponse($data = null, int $responseCode = 200) { // Set the correct content type $this->app->setHeader('Content-Type', 'application/json'); // Set the status code for the response http_response_code($responseCode); // Send the data echo new JsonResponse($data); $this->app->close(); } /** * Method to get a model object, loading it if required. * * @param string $name The model name. Optional. * @param string $prefix The class prefix. Optional. * @param array $config Configuration array for model. Optional. * * @return BaseModel|boolean Model object on success; otherwise false on failure. * * @since 4.0.0 */ public function getModel($name = 'Api', $prefix = 'Administrator', $config = []) { return parent::getModel($name, $prefix, $config); } /** * Performs various checks if it is allowed to save the content. * * @return void * * @since 4.0.0 * @throws \Exception */ private function checkContent() { $helper = new MediaHelper(); $contentLength = $this->input->server->getInt('CONTENT_LENGTH'); $params = ComponentHelper::getParams('com_media'); $paramsUploadMaxsize = $params->get('upload_maxsize', 0) * 1024 * 1024; $uploadMaxFilesize = $helper->toBytes(ini_get('upload_max_filesize')); $postMaxSize = $helper->toBytes(ini_get('post_max_size')); $memoryLimit = $helper->toBytes(ini_get('memory_limit')); if ( ($paramsUploadMaxsize > 0 && $contentLength > $paramsUploadMaxsize) || ($uploadMaxFilesize > 0 && $contentLength > $uploadMaxFilesize) || ($postMaxSize > 0 && $contentLength > $postMaxSize) || ($memoryLimit > -1 && $contentLength > $memoryLimit) ) { throw new \Exception(Text::_('COM_MEDIA_ERROR_WARNFILETOOLARGE'), 403); } } /** * Get the Adapter. * * @return string * * @since 4.0.0 */ private function getAdapter() { $parts = explode(':', $this->input->getString('path', ''), 2); if (count($parts) < 1) { return null; } return $parts[0]; } /** * Get the Path. * * @return string * * @since 4.0.0 */ private function getPath() { $parts = explode(':', $this->input->getString('path', ''), 2); if (count($parts) < 2) { return null; } return $parts[1]; } }