TaoLer/extend/AngularFilemanager/LocalBridge/FileManagerApi.php
2021-05-10 13:59:58 +08:00

663 lines
20 KiB
PHP

<?php
namespace AngularFilemanager\LocalBridge;
use AngularFilemanager\LocalBridge\Translate;
/**
* File Manager API Class
*
* Made for PHP Local filesystem bridge for angular-filemanager to handle file manipulations
* @author Jakub Ďuraš <jakub@duras.me>
*/
class FileManagerApi
{
private $basePath = null;
private $translate;
public function __construct($basePath = null, $lang = 'en', $muteErrors = true)
{
if ($muteErrors) {
ini_set('display_errors', 0);
}
$this->basePath = $basePath ?: dirname(__DIR__);
$this->translate = new Translate($lang);
}
public function postHandler($query, $request, $files)
{
$t = $this->translate;
// Probably file upload
if (!isset($request['action'])
&& (isset($_SERVER["CONTENT_TYPE"])
&& strpos($_SERVER["CONTENT_TYPE"], 'multipart/form-data') !== false)
) {
$uploaded = $this->uploadAction($request['destination'], $files);
if ($uploaded === true) {
$response = $this->simpleSuccessResponse();
} else {
$response = $this->simpleErrorResponse($t->upload_failed);
}
return $response;
}
switch ($request['action']) {
case 'list':
$list = $this->listAction($request['path']);
if (!is_array($list)) {
$response = $this->simpleErrorResponse($t->listing_failed);
} else {
$response = new Response();
$response->setData([
'result' => $list
]);
}
break;
case 'rename':
$renamed = $this->renameAction($request['item'], $request['newItemPath']);
if ($renamed === true) {
$response = $this->simpleSuccessResponse();
} elseif ($renamed === 'notfound') {
$response = $this->simpleErrorResponse($t->file_not_found);
} else {
$response = $this->simpleErrorResponse($t->renaming_failed);
}
break;
case 'move':
$moved = $this->moveAction($request['items'], $request['newPath']);
if ($moved === true) {
$response = $this->simpleSuccessResponse();
} else {
$response = $this->simpleErrorResponse($t->moving_failed);
}
break;
case 'copy':
$copied = $this->copyAction($request['items'], $request['newPath']);
if ($copied === true) {
$response = $this->simpleSuccessResponse();
} else {
$response = $this->simpleErrorResponse($t->copying_failed);
}
break;
case 'remove':
$removed = $this->removeAction($request['items']);
if ($removed === true) {
$response = $this->simpleSuccessResponse();
} elseif ($removed === 'notempty') {
$response = $this->simpleErrorResponse($t->removing_failed_directory_not_empty);
} else {
$response = $this->simpleErrorResponse($t->removing_failed);
}
break;
case 'edit':
$edited = $this->editAction($request['item'], $request['content']);
if ($edited !== false) {
$response = $this->simpleSuccessResponse();
} else {
$response = $this->simpleErrorResponse($t->saving_failed);
}
break;
case 'getContent':
$content = $this->getContentAction($request['item']);
if ($content !== false) {
$response = new Response();
$response->setData([
'result' => $content
]);
} else {
$response = $this->simpleErrorResponse($t->file_not_found);
}
break;
case 'createFolder':
$created = $this->createFolderAction($request['newPath']);
if ($created === true) {
$response = $this->simpleSuccessResponse();
} elseif ($created === 'exists') {
$response = $this->simpleErrorResponse($t->folder_already_exists);
} else {
$response = $this->simpleErrorResponse($t->folder_creation_failed);
}
break;
case 'changePermissions':
$changed = $this->changePermissionsAction($request['items'], $request['perms'], $request['recursive']);
if ($changed === true) {
$response = $this->simpleSuccessResponse();
} elseif ($changed === 'missing') {
$response = $this->simpleErrorResponse($t->file_not_found);
} else {
$response = $this->simpleErrorResponse($t->permissions_change_failed);
}
break;
case 'compress':
$compressed = $this->compressAction(
$request['items'],
$request['destination'],
$request['compressedFilename']
);
if ($compressed === true) {
$response = $this->simpleSuccessResponse();
} else {
$response = $this->simpleErrorResponse($t->compression_failed);
}
break;
case 'extract':
$extracted = $this->extractAction($request['destination'], $request['item'], $request['folderName']);
if ($extracted === true) {
$response = $this->simpleSuccessResponse();
} elseif ($extracted === 'unsupported') {
$response = $this->simpleErrorResponse($t->archive_opening_failed);
} else {
$response = $this->simpleErrorResponse($t->extraction_failed);
}
break;
default:
$response = $this->simpleErrorResponse($t->function_not_implemented);
break;
}
return $response;
}
public function getHandler($queries)
{
$t = $this->translate;
switch ($queries['action']) {
case 'download':
$downloaded = $this->downloadAction($queries['path']);
if ($downloaded === true) {
exit;
} else {
$response = $this->simpleErrorResponse($t->file_not_found);
}
break;
case 'downloadMultiple':
$downloaded = $this->downloadMultipleAction($queries['items'], $queries['toFilename']);
if ($downloaded === true) {
exit;
} else {
$response = $this->simpleErrorResponse($t->file_not_found);
}
break;
default:
$response = $this->simpleErrorResponse($t->function_not_implemented);
break;
}
return $response;
}
private function downloadAction($path)
{
$file_name = basename($path);
$path = $this->canonicalizePath($this->basePath . $path);
if (!file_exists($path)) {
return false;
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $path);
finfo_close($finfo);
if (ob_get_level()) {
ob_end_clean();
}
header("Content-Disposition: attachment; filename=\"$file_name\"");
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header("Content-Type: $mime_type");
header('Pragma: public');
header('Content-Length: ' . filesize($path));
readfile($path);
return true;
}
private function downloadMultipleAction($items, $archiveName)
{
$archivePath = tempnam('../', 'archive');
$zip = new \ZipArchive();
if ($zip->open($archivePath, \ZipArchive::CREATE) !== true) {
unlink($archivePath);
return false;
}
foreach ($items as $path) {
$zip->addFile($this->basePath . $path, basename($path));
}
$zip->close();
header("Content-Disposition: attachment; filename=\"$archiveName\"");
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header("Content-Type: application/zip");
header('Pragma: public');
header('Content-Length: ' . filesize($archivePath));
readfile($archivePath);
unlink($archivePath);
return true;
}
private function uploadAction($path, $files)
{
$path = $this->canonicalizePath($this->basePath . $path);
foreach ($_FILES as $file) {
$fileInfo = pathinfo($file['name']);
$fileName = $this->normalizeName($fileInfo['filename']) . '.' . $fileInfo['extension'];
$uploaded = move_uploaded_file(
$file['tmp_name'],
$path . DIRECTORY_SEPARATOR . $fileName
);
if ($uploaded === false) {
return false;
}
}
return true;
}
private function listAction($path)
{
$files = array_values(array_filter(
scandir($this->basePath . $path),
function ($path) {
return !($path === '.' || $path === '..');
}
));
$files = array_map(function ($file) use ($path) {
$file = $this->canonicalizePath(
$this->basePath . $path . DIRECTORY_SEPARATOR . $file
);
$date = new \DateTime('@' . filemtime($file));
return [
'name' => basename($file),
'rights' => $this->parsePerms(fileperms($file)),
'size' => filesize($file),
'date' => $date->format('Y-m-d H:i:s'),
'type' => is_dir($file) ? 'dir' : 'file'
];
}, $files);
return $files;
}
public function listActionData($path)
{
$files = array_values(array_filter(
scandir($this->basePath . $path),
function ($path) {
return !($path === '.' || $path === '..');
}
));
$files = array_map(function ($file) use ($path) {
$file = $this->canonicalizePath(
$this->basePath . $path . DIRECTORY_SEPARATOR . $file
);
$date = new \DateTime('@' . filemtime($file));
return [
'name' => basename($file),
'rights' => $this->parsePerms(fileperms($file)),
'size' => filesize($file),
'date' => $date->format('Y-m-d H:i:s'),
'type' => is_dir($file) ? 'dir' : 'file'
];
}, $files);
return $files;
}
private function renameAction($oldPath, $newPath)
{
$oldPath = $this->basePath . $oldPath;
$newPath = $this->basePath . $newPath;
if (! file_exists($oldPath)) {
return 'notfound';
}
return rename($oldPath, $newPath);
}
private function moveAction($oldPaths, $newPath)
{
$newPath = $this->basePath . $this->canonicalizePath($newPath) . DIRECTORY_SEPARATOR;
foreach ($oldPaths as $oldPath) {
if (!file_exists($this->basePath . $oldPath)) {
return false;
}
$renamed = rename($this->basePath . $oldPath, $newPath . basename($oldPath));
if ($renamed === false) {
return false;
}
}
return true;
}
private function copyAction($oldPaths, $newPath)
{
$newPath = $this->basePath . $this->canonicalizePath($newPath) . DIRECTORY_SEPARATOR;
foreach ($oldPaths as $oldPath) {
if (!file_exists($this->basePath . $oldPath)) {
return false;
}
$copied = copy(
$this->basePath . $oldPath,
$newPath . basename($oldPath)
);
if ($copied === false) {
return false;
}
}
return true;
}
private function removeAction($paths)
{
foreach ($paths as $path) {
$path = $this->canonicalizePath($this->basePath . $path);
if (is_dir($path)) {
$dirEmpty = (new \FilesystemIterator($path))->valid();
if ($dirEmpty) {
return 'notempty';
} else {
$removed = rmdir($path);
}
} else {
$removed = unlink($path);
}
if ($removed === false) {
return false;
}
}
return true;
}
private function editAction($path, $content)
{
$path = $this->basePath . $path;
return file_put_contents($path, $content);
}
private function getContentAction($path)
{
$path = $this->basePath . $path;
if (! file_exists($path)) {
return false;
}
return file_get_contents($path);
}
private function createFolderAction($path)
{
$path = $this->basePath . $path;
if (file_exists($path) && is_dir($path)) {
return 'exists';
}
return mkdir($path);
}
private function changePermissionsAction($paths, $permissions, $recursive)
{
foreach ($paths as $path) {
if (!file_exists($this->basePath . $path)) {
return 'missing';
}
if (is_dir($path) && $recursive === true) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $item) {
$changed = chmod($this->basePath . $item, octdec($permissions));
if ($changed === false) {
return false;
}
}
}
return chmod($this->basePath . $path, octdec($permissions));
}
}
private function compressAction($paths, $destination, $archiveName)
{
$archivePath = $this->basePath . $destination . $archiveName;
$zip = new \ZipArchive();
if ($zip->open($archivePath, \ZipArchive::CREATE) !== true) {
return false;
}
foreach ($paths as $path) {
$fullPath = $this->basePath . $path;
if (is_dir($fullPath)) {
$dirs = [
[
'dir' => basename($path),
'path' => $this->canonicalizePath($this->basePath . $path),
]
];
while (count($dirs)) {
$dir = current($dirs);
$zip->addEmptyDir($dir['dir']);
$dh = opendir($dir['path']);
while ($file = readdir($dh)) {
if ($file != '.' && $file != '..') {
$filePath = $dir['path'] . DIRECTORY_SEPARATOR . $file;
if (is_file($filePath)) {
$zip->addFile(
$dir['path'] . DIRECTORY_SEPARATOR . $file,
$dir['dir'] . '/' . basename($file)
);
} elseif (is_dir($filePath)) {
$dirs[] = [
'dir' => $dir['dir'] . '/' . $file,
'path' => $dir['path'] . DIRECTORY_SEPARATOR . $file
];
}
}
}
closedir($dh);
array_shift($dirs);
}
} else {
$zip->addFile($path, basename($path));
}
}
return $zip->close();
}
private function extractAction($destination, $archivePath, $folderName)
{
$archivePath = $this->basePath . $archivePath;
$folderPath = $this->basePath . $this->canonicalizePath($destination) . DIRECTORY_SEPARATOR . $folderName;
$zip = new \ZipArchive;
if ($zip->open($archivePath) === false) {
return 'unsupported';
}
mkdir($folderPath);
$zip->extractTo($folderPath);
return $zip->close();
}
private function simpleSuccessResponse()
{
$response = new Response();
$response->setData([
'result' => [
'success' => true
]
]);
return $response;
}
private function simpleErrorResponse($message)
{
$response = new Response();
$response
->setStatus(500, 'Internal Server Error')
->setData([
'result' => [
'success' => false,
'error' => $message
]
]);
return $response;
}
private function parsePerms($perms)
{
if (($perms & 0xC000) == 0xC000) {
// Socket
$info = 's';
} elseif (($perms & 0xA000) == 0xA000) {
// Symbolic Link
$info = 'l';
} elseif (($perms & 0x8000) == 0x8000) {
// Regular
$info = '-';
} elseif (($perms & 0x6000) == 0x6000) {
// Block special
$info = 'b';
} elseif (($perms & 0x4000) == 0x4000) {
// Directory
$info = 'd';
} elseif (($perms & 0x2000) == 0x2000) {
// Character special
$info = 'c';
} elseif (($perms & 0x1000) == 0x1000) {
// FIFO pipe
$info = 'p';
} else {
// Unknown
$info = 'u';
}
// Owner
$info .= (($perms & 0x0100) ? 'r' : '-');
$info .= (($perms & 0x0080) ? 'w' : '-');
$info .= (($perms & 0x0040) ?
(($perms & 0x0800) ? 's' : 'x' ) :
(($perms & 0x0800) ? 'S' : '-'));
// Group
$info .= (($perms & 0x0020) ? 'r' : '-');
$info .= (($perms & 0x0010) ? 'w' : '-');
$info .= (($perms & 0x0008) ?
(($perms & 0x0400) ? 's' : 'x' ) :
(($perms & 0x0400) ? 'S' : '-'));
// World
$info .= (($perms & 0x0004) ? 'r' : '-');
$info .= (($perms & 0x0002) ? 'w' : '-');
$info .= (($perms & 0x0001) ?
(($perms & 0x0200) ? 't' : 'x' ) :
(($perms & 0x0200) ? 'T' : '-'));
return $info;
}
private function canonicalizePath($path)
{
$dirSep = DIRECTORY_SEPARATOR;
$wrongDirSep = DIRECTORY_SEPARATOR === '/' ? '\\' : '/';
// Replace incorrect dir separators
$path = str_replace($wrongDirSep, $dirSep, $path);
$path = explode($dirSep, $path);
$stack = array();
foreach ($path as $seg) {
if ($seg == '..') {
// Ignore this segment, remove last segment from stack
array_pop($stack);
continue;
}
if ($seg == '.') {
// Ignore this segment
continue;
}
$stack[] = $seg;
}
// Remove last /
if (empty($stack[count($stack) - 1])) {
array_pop($stack);
}
return implode($dirSep, $stack);
}
/**
* Creates ASCII name
*
* @param string name encoded in UTF-8
* @return string name containing only numbers, chars without diacritics, underscore and dash
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
private function normalizeName($name)
{
$name = preg_replace('~[^\\pL0-9_]+~u', '-', $name);
$name = trim($name, "-");
//$name = iconv("utf-8", "us-ascii//TRANSLIT", $name);
$name = preg_replace('~[^-a-z0-9_]+~', '', $name);
return $name;
}
}