<?php

/**
 * @package     FF Explorer
 * @subpackage  com_ffexplorer
 *
 * @copyright   https://github.com/trananhmanh89/ffexplorer
 * @license     MIT
 */

use Joomla\Archive\Archive;
use Joomla\Archive\Zip;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Helper\MediaHelper;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\String\PunycodeHelper;

defined('_JEXEC') or die('Restricted access');

class FfexplorerControllerExplorer extends BaseController
{
    public function extract()
    {
        $this->checkToken();
        
        $target = $this->input->get('target', '', 'raw');
        $source = $this->input->get('source', '', 'raw');

        if (!$target || !$source) {
            $this->response('error', 'Path not found');
        }

        $target = JPATH_ROOT . $target;
        $source = JPATH_ROOT . $source;
        if (!is_file($source) || !is_dir($target)) {
            $this->response('error', 'Path not found');
        }

        try {
            $archive = new Archive();
            $archive->extract($source, $target);

            $this->response('success', true);
        } catch (Exception $e) {
            $this->response('error', $e->getMessage());
        }
    }
    public function download()
    {
        $this->checkToken();
        $relativePath = $this->input->getString('path');
        $path = realpath(JPATH_ROOT . $relativePath);

        if (!$relativePath || !file_exists($path) || !is_file($path)) {
            $this->response('error', 'File not existed');
        }

        ob_clean();
        $finfo = finfo_open(FILEINFO_MIME_TYPE);

        header('Content-Type: ' . finfo_file($finfo, $path));
  
        $finfo = finfo_open(FILEINFO_MIME_ENCODING);
        header('Content-Transfer-Encoding: ' . finfo_file($finfo, $path)); 
       
        header('Content-disposition: attachment; filename="' . basename($path) . '"'); 
       
        readfile($path);
        die();
    }

    public function compress()
    {
        $this->checkToken();
        $relativePath = $this->input->getString('path');
        $path = realpath(JPATH_ROOT . $relativePath);

        if (!$relativePath || !file_exists($path)) {
            $this->response('error', 'Path not existed');
        }

        $info = pathinfo($path);
        $items = array();

        if (is_dir($path)) {
            $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX');
            $excludefilter = array('.*~');
            $files = Folder::files($path, '.', true, true, $exclude, $excludefilter);
            
            foreach ($files as &$file) {
                $content = array();
                $content['data'] = file_get_contents($file);

                $name = realpath($file);
                $name = $info['filename'] . str_replace($path, '', $name);
                $content['name'] = $name;
                $items[] = $content;
            }

        } else {
            $items[] = array(
                'data' => file_get_contents($path),
                'name' => $info['basename']
            );
        }

        $target = $info['dirname'] . '/' . $info['filename'] . '.zip';
        if (File::exists($target)) {
            for ($i=1; $i < 100; $i++) { 
                $target = $info['dirname'] . '/' . $info['filename'] . '(' . $i . ')' . '.zip';
                if (!File::exists($target)) {
                    break;
                }
            }
        }

        $archive = new Zip();
        $result = $archive->create($target, $items);
        
        if ($result) {
            $this->response('success', '');
        } else {
            $this->response('error', 'Compress error!!');
        }
    }

    public function getPermission()
    {
        $this->checkToken();
        $relativePath = $this->input->getString('path');
        $path = realpath(JPATH_ROOT . $relativePath);

        if (!$relativePath || !file_exists($path)) {
            $this->response('error', 'Path not existed');
        }

        if (!Path::check($path)) {
            $this->response('error', 'Path error');
        }

        $permission = Path::getPermissions($path);
        $this->response('permission', $permission);
    }

    public function setPermission()
    {
        $this->checkToken();
        $relativePath = $this->input->getString('path');
        $path = realpath(JPATH_ROOT . $relativePath);

        if (!$relativePath || !file_exists($path)) {
            $this->response('error', 'Path not existed');
        }

        $mode = $this->input->get('mode');
        if (!$mode) {
            $this->response('error', 'Mode is missing!');
        }

        if (is_dir($path)) {
            $result = Path::setPermissions($path, null, $mode);
        } else {
            $result = Path::setPermissions($path, $mode, null);
        }

        if ($result) {
            $this->response('success', '');
        } else {
            $this->response('error', 'Set permission failed');
        }
    }

    public function upload()
    {
        $this->checkToken();
        $path = $this->input->getString('path');
        $path = Path::clean($path, '/');
        if (!is_dir(JPATH_ROOT . $path)) {
            $this->response('error', 'empty path');
        }
        
        $path = $path ? $path : '/';

        $file = $this->input->files->get('file', array(), 'raw');
        $contentLength = (int) $file['size'];
        $mediaHelper = new MediaHelper;
        $postMaxSize = $mediaHelper->toBytes(ini_get('post_max_size'));
        $memoryLimit = $mediaHelper->toBytes(ini_get('memory_limit'));
        $uploadMaxFileSize = $mediaHelper->toBytes(ini_get('upload_max_filesize'));
        
        if (($file['error'] == 1)
            || ($postMaxSize > 0 && $contentLength > $postMaxSize)
            || ($memoryLimit != -1 && $contentLength > $memoryLimit)
            || ($uploadMaxFileSize > 0 && $contentLength > $uploadMaxFileSize))
        {
            $this->response('error', 'File too large');
        }

        $file['filepath'] = JPATH_ROOT . $path . '/' . $file['name'];

        if (File::exists($file['filepath']))
        {
            $this->response('error', 'File ' . $file['name'] . ' existed');
        }

        if (!isset($file['name']))
        {
            $this->response('error', 'File error');
        }

        if (File::upload($file['tmp_name'], $file['filepath'], false, true)) {
            $this->response('success', '');
        } else {
            $this->response('error', 'Upload error');
        }
    }

    public function saveContent()
    {
        $this->checkToken();
        $path = $this->input->getString('path');
        $path = Path::clean($path, '/');
        $content = $this->input->get('content', '', 'raw');

        if (!$path) {
            $this->response('error', 'empty path');
        }

        $file = JPATH_ROOT . $path;
        if (!File::exists($file)) {
            $this->response('error', 'file not existed');
        }

        if (@File::write($file, $content)) {
            $this->response('success', 'saved');
        } else {
            $this->response('error', 'could not write file');
        }
    }

    public function openFile()
    {
        $this->checkToken();

        $path = $this->input->getString('path');
        $path = Path::clean($path, '/');
        if (!$path) {
            $this->response('error', 'empty path');
        }

        $file = JPATH_ROOT . $path;
        if (!File::exists($file)) {
            $this->response('error', 'file not existed');
        }

        $mime = mime_content_type($file);
        if (strpos($mime, 'text') === 0 || $mime === 'inode/x-empty') {
            $content = file_get_contents($file);
            $this->response('content', $content);
        } else {
            $this->response('error', 'Could not open this file');
        }
    }

    public function explodeFolder()
    {
        $this->checkToken();

        $path = $this->input->getString('path');

        $path = $path ? JPATH_ROOT . $path : JPATH_ROOT;
        $folders = Folder::folders($path, '.', false, true);
        $folders = array_map(function($folder) {
            $folder = realpath($folder);
            $info = pathinfo($folder);

            $item = new stdClass;
            $item->path = str_replace(JPATH_ROOT, '', $folder);
            $item->name = $info['basename'];
            $item->type = 'folder';

            return $item;
        }, $folders);

        $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX');
        $excludefilter = array('.*~');
        $files = Folder::files($path, '.', false, true, $exclude, $excludefilter);
        $files = array_map(function($file) {
            $file = realpath($file);
            $info = pathinfo($file);

            $item = new stdClass;
            $item->path = str_replace(JPATH_ROOT, '', $file);
            $item->name = $info['basename'];
            $item->type = 'file';

            return $item;
        }, $files);

        $result = array_merge($folders, $files);

        die(json_encode($result));
    }

    public function newFile()
    {
        $this->checkToken();

        $name = $this->input->getString('name');
        $path = $this->input->getString('path');

        if (!$name || !$path) {
            die(json_encode(array('error' => 'empty')));
        }

        $file = JPATH_ROOT . $path . '/' . $name;
        if (File::exists($file)) {
            $this->response('error', 'File is already existed');
        }

        if (File::write($file, '')) {
            $this->response('success', 'File has been created');
        } else {
            $this->response('error', 'Create file failed');
        }
    }

    public function newFolder()
    {
        $this->checkToken();

        $name = $this->input->getString('name');
        $path = $this->input->getString('path');
        
        $name = Folder::makeSafe($name);

        if (!$name || !$path) {
            die(json_encode(array('error' => 'empty')));
        }

        $folder = JPATH_ROOT . $path . '/' . $name;
        if (Folder::exists($folder)) {
            $this->response('error', 'Folder is already existed');
        }

        if (Folder::create($folder)) {
            $this->response('success', 'Folder has been created');
        } else {
            $this->response('error', 'Create folder failed');
        }
    }

    public function renameFolder()
    {
        $this->checkToken();

        $newName = $this->input->getString('newName');
        $oldPath = $this->input->getString('oldPath');

        $newName = Folder::makeSafe($newName);

        if (!$newName || !$oldPath) {
            $this->response('error', 'empty');
        }

        if (!Folder::exists(JPATH_ROOT . $oldPath)) {
            $this->response('error', 'Folder not found');
        }

        $info = pathinfo($oldPath);
        $folder = JPATH_ROOT . $info['dirname'] . '/' . $newName;
        if (Folder::exists($folder)) {
            $this->response('error', 'Folder is already existed');
        }

        $result = rename( JPATH_ROOT . $oldPath, $folder);
        if ($result) {
            $folderPath = realpath($folder);
            $folderPath = str_replace(JPATH_ROOT, '', $folderPath);

            $this->response('data', array(
                'path' => $folderPath,
                'name' => $newName,
            ));
        } else {
            $this->response('error', 'rename error');
        }
    }

    public function deleteFolder()
    {
        $this->checkToken();

        $path = $this->input->getString('path');
        $path = Path::clean($path, '/');
        if (!$path) {
            $this->response('error', 'empty path');
        }

        if (Folder::exists(JPATH_ROOT . $path)) {
            try {
                if(Folder::delete(JPATH_ROOT . $path)) {
                    $this->response('success', 'deleted');
                } else {
                    $this->response('error', 'Delete failed');
                }
            } catch (Exception $e) {
                $this->response('error', $e->getMessage());
            }
            
        } else {
            $this->response('error', 'Folder is not existed');
        }
    }

    public function renameFile()
    {
        $this->checkToken();

        $newName = $this->input->getString('newName');
        $oldPath = $this->input->getString('oldPath');

        if (!$newName || !$oldPath) {
            $this->response('error', 'empty');
        }

        if (!File::exists(JPATH_ROOT . $oldPath)) {
            $this->response('error', 'File not found');
        }

        $info = pathinfo($oldPath);
        $file = JPATH_ROOT . $info['dirname'] . '/' . $newName;
        if (File::exists($file)) {
            $this->response('error', 'File is already existed');
        }

        $result = rename( JPATH_ROOT . $oldPath, $file);
        if ($result) {
            $filePath = realpath($file);
            $filePath = str_replace(JPATH_ROOT, '', $filePath);

            $this->response('data', array(
                'path' => $filePath,
                'name' => $newName,
            ));
        } else {
            $this->response('error', 'rename error');
        }
    }

    public function deleteFile()
    {
        $this->checkToken();

        $path = $this->input->getString('path');
        $path = Path::clean($path, '/');
        if (!$path) {
            $this->response('error', 'empty path');
        }

        if (File::exists(JPATH_ROOT . $path)) {
            try {
                if(File::delete(JPATH_ROOT . $path)) {
                    $this->response('success', 'deleted');
                } else {
                    $this->response('error', 'Delete failed');
                }
            } catch (Exception $e) {
                $this->response('error', $e->getMessage());
            }
        } else {
            $this->response('error', 'File is not existed');
        }
    }

    public function checkToken($method = 'post', $redirect = false)
    {
        // sleep(3);
        if (!parent::checkToken($method, $redirect)) {
            $this->response('error', 'csrf token error');
        }
    }

    protected function response($type = 'success', $data = array())
    {
        die(@json_encode(array($type => $data)));
    }
}