. * */ namespace Vvveb\Controller\Editor; use function Vvveb\__; use Vvveb\Controller\Base; use function Vvveb\sanitizeFileName; class Code extends Base { protected $saveDenyExtensions = ['php', 'tpl']; function dirForType($type) { switch ($type) { case 'public': $scandir = DIR_MEDIA; break; case 'plugins': $scandir = DIR_PLUGINS; break; case 'themes': $scandir = DIR_THEMES; break; default: return false; } return $scandir; } function index() { $type = $this->request->get['type'] ?? false; $admin_path = \Vvveb\adminPath(); $controllerPath = $admin_path . 'index.php?module=editor/code'; if (! $type) { return; } $this->view->scanUrl = "$controllerPath&action=scan&type=$type"; $this->view->uploadUrl = "$controllerPath&action=upload&type=$type"; $this->view->loadFileUrl = "$controllerPath&action=loadFile&type=$type"; $this->view->saveUrl = "$controllerPath&action=save&type=$type"; $this->view->type = $type; if ($type) { $this->view->mediaPath = str_replace('/media', "/$type", $this->view->mediaPath); } } function sanitizeFileName($file, $type) { if (V_SUBDIR_INSTALL && strpos($file, V_SUBDIR_INSTALL) === 0) { //$path = str_replace(V_SUBDIR_INSTALL, '', $path); $file = substr_replace($file, '', 0, strlen(V_SUBDIR_INSTALL)); } $file = preg_replace("@^[\/]public@", '', $file); if ($type == 'plugins') { $file = DIR_PLUGINS . preg_replace("@^[\/]plugins[\/]@", '', $file); } else { if ($type == 'themes') { $file = DIR_THEMES . preg_replace("@^[\/]themes[\/]@", '', $file); } else { $file = DIR_PUBLIC . $file; } } $file = sanitizeFileName($file); return $file; } function save() { $type = $this->request->get['type']; $file = $this->request->get['file']; $content = $this->request->post['content']; $file = $this->sanitizeFileName($file, $type); $message = ['success' => false, 'message' => sprintf(__('Error saving: %s!'), $file)]; $extension = strtolower(substr($file, strrpos($file, '.') + 1)); if (in_array($extension, $this->saveDenyExtensions)) { $message = ['success' => false, 'message' => sprintf(__('Saving not allowed for file type %s!'), trim($extension, '.'))]; $success = false; } else { if (! is_writable($file)) { $message = ['success' => false, 'message' => sprintf(__('File not writable: %s Check if file has write permission.'), $file)]; } else { if (file_put_contents($file, $content)) { $message = ['success' => true, 'message' => __('File saved!')]; } } } $this->response->setType('json'); $this->response->output($message); } function loadFile() { $type = $this->request->get['type']; $file = $this->request->get['file']; $file = $this->sanitizeFileName($file, $type); if (! is_readable($file)) { die("File not readable: $file"); } if ($content = file_get_contents($file)) { die($content); } die("Error loading: $file"); } function scan() { $type = $this->request->get['type'] ?? 'public'; $scandir = $this->dirForType($type); if (! $scandir) { return []; } // This function scans the files folder recursively, and builds a large array $scan = function ($dir) use ($scandir, &$scan) { $files = []; // Is there actually such a folder/file? if (file_exists($dir)) { $listdir = @scandir($dir); if ($listdir) { foreach ($listdir as $f) { if (! $f || $f[0] == '.' || $f == 'node_modules' || $f == 'vendor') { continue; // Ignore hidden files } if (is_dir($dir . DS . $f)) { // The path is a folder $files[] = [ 'name' => $f, 'type' => 'folder', 'path' => str_replace([$scandir, '\\'], ['', '/'], $dir) . '/' . $f, 'items' => $scan($dir . DS . $f), // Recursively get the contents of the folder ]; } else { // It is a file $files[] = [ 'name' => $f, 'type' => 'file', 'path' => str_replace([$scandir, '\\'], ['', '/'], $dir) . '/' . $f, 'size' => @filesize($dir . DS . $f), // Gets the size of this file ]; } } } } return $files; }; $response = $scan($scandir); // Output the directory listing as JSON $this->response->setType('json'); $this->response->output([ 'name' => '', 'type' => 'folder', 'path' => '', 'items' => $response, ]); } }