alias = $alias; $this->theme = Theme::getEditTheme(); $this->selectionInputName = 'file'; $this->assetExtensions = FileDefinitions::get('assetExtensions'); parent::__construct($controller, []); $this->bindToController(); } /** * @inheritDoc */ protected function loadAssets() { $this->addCss('css/assetlist.css', 'core'); $this->addJs('js/assetlist.js', 'core'); } /** * Renders the widget. * @return string */ public function render() { return $this->makePartial('body', [ 'data' => $this->getData() ]); } // // Event handlers // public function onOpenDirectory() { $path = Input::get('path'); if (!$this->validatePath($path)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path')); } $delay = Input::get('delay'); if ($delay) { usleep(1000000*$delay); } $this->putSession('currentPath', $path); return [ '#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()]) ]; } public function onRefresh() { return [ '#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()]) ]; } public function onUpdate() { $this->extendSelection(); return $this->onRefresh(); } public function onDeleteFiles() { $this->validateRequestTheme(); $fileList = Request::input('file'); $error = null; $deleted = []; try { $assetsPath = $this->getAssetsPath(); foreach ($fileList as $path => $selected) { if ($selected) { if (!$this->validatePath($path)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path')); } $fullPath = $assetsPath.'/'.$path; if (File::exists($fullPath)) { if (!File::isDirectory($fullPath)) { if (!@File::delete($fullPath)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_deleting_file', ['name' => $path] )); } } else { $empty = File::isDirectoryEmpty($fullPath); if ($empty === false) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_deleting_dir_not_empty', ['name' => $path] )); } if (!@rmdir($fullPath)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_deleting_dir', ['name' => $path] )); } } $deleted[] = $path; $this->removeSelection($path); } } } } catch (Exception $ex) { $error = $ex->getMessage(); } return [ 'deleted' => $deleted, 'error' => $error, 'theme' => Request::input('theme') ]; } public function onLoadRenamePopup() { $this->validateRequestTheme(); $path = Input::get('renamePath'); if (!$this->validatePath($path)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path')); } $this->vars['originalPath'] = $path; $this->vars['name'] = basename($path); return $this->makePartial('rename_form'); } public function onApplyName() { $this->validateRequestTheme(); $newName = trim(Input::get('name')); if (!strlen($newName)) { throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty')); } if (!$this->validatePath($newName)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path')); } if (!$this->validateName($newName)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name')); } $originalPath = Input::get('originalPath'); if (!$this->validatePath($originalPath)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path')); } $originalFullPath = $this->getFullPath($originalPath); if (!file_exists($originalFullPath)) { throw new ApplicationException(Lang::get('cms::lang.asset.original_not_found')); } if (!is_dir($originalFullPath) && !$this->validateFileType($newName)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.type_not_allowed', ['allowed_types' => implode(', ', $this->assetExtensions)] )); } $newFullPath = $this->getFullPath(dirname($originalPath).'/'.$newName); if (file_exists($newFullPath) && $newFullPath !== $originalFullPath) { throw new ApplicationException(Lang::get('cms::lang.asset.already_exists')); } if (!@rename($originalFullPath, $newFullPath)) { throw new ApplicationException(Lang::get('cms::lang.asset.error_renaming')); } return [ '#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()]) ]; } public function onLoadNewDirPopup() { $this->validateRequestTheme(); return $this->makePartial('new_dir_form'); } public function onNewDirectory() { $this->validateRequestTheme(); $newName = trim(Input::get('name')); if (!strlen($newName)) { throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty')); } if (!$this->validatePath($newName)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path')); } if (!$this->validateName($newName)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name')); } $newFullPath = $this->getCurrentPath().'/'.$newName; if (file_exists($newFullPath)) { throw new ApplicationException(Lang::get('cms::lang.asset.already_exists')); } if (!File::makeDirectory($newFullPath)) { throw new ApplicationException(Lang::get( 'cms::lang.cms_object.error_creating_directory', ['name' => $newName] )); } return [ '#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()]) ]; } public function onLoadMovePopup() { $this->validateRequestTheme(); $fileList = Request::input('file'); $directories = []; $selectedList = array_filter($fileList, function ($value) { return $value == 1; }); $this->listDestinationDirectories($directories, $selectedList); $this->vars['directories'] = $directories; $this->vars['selectedList'] = base64_encode(json_encode(array_keys($selectedList))); return $this->makePartial('move_form'); } public function onMove() { $this->validateRequestTheme(); $selectedList = Input::get('selectedList'); if (!strlen($selectedList)) { throw new ApplicationException(Lang::get('cms::lang.asset.selected_files_not_found')); } $destinationDir = Input::get('dest'); if (!strlen($destinationDir)) { throw new ApplicationException(Lang::get('cms::lang.asset.select_destination_dir')); } $destinationFullPath = $this->getFullPath($destinationDir); if (!file_exists($destinationFullPath) || !is_dir($destinationFullPath)) { throw new ApplicationException(Lang::get('cms::lang.asset.destination_not_found')); } $list = @json_decode(@base64_decode($selectedList)); if ($list === false) { throw new ApplicationException(Lang::get('cms::lang.asset.selected_files_not_found')); } foreach ($list as $path) { if (!$this->validatePath($path)) { throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path')); } $basename = basename($path); $originalFullPath = $this->getFullPath($path); $newFullPath = realpath(rtrim($destinationFullPath, '/')) . '/' . $basename; $safeDir = $this->getAssetsPath(); if ($originalFullPath == $newFullPath) { continue; } if (!starts_with($newFullPath, $safeDir)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_moving_file', ['file' => $basename] )); } if (is_file($originalFullPath)) { if (!@File::move($originalFullPath, $newFullPath)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_moving_file', ['file' => $basename] )); } } elseif (is_dir($originalFullPath)) { if (!@File::copyDirectory($originalFullPath, $newFullPath)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_moving_directory', ['dir' => $basename] )); } if (strpos($originalFullPath, '../') !== false) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_deleting_directory', ['dir' => $basename] )); } if (strpos($originalFullPath, $safeDir) !== 0) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_deleting_directory', ['dir' => $basename] )); } if (!@File::deleteDirectory($originalFullPath)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.error_deleting_directory', ['dir' => $basename] )); } } } return [ '#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()]) ]; } public function onSearch() { $this->setSearchTerm(Input::get('search')); $this->extendSelection(); return $this->onRefresh(); } /* * Methods for the internal use */ protected function getData() { $assetsPath = $this->getAssetsPath(); if (!file_exists($assetsPath) || !is_dir($assetsPath)) { if (!File::makeDirectory($assetsPath)) { throw new ApplicationException(Lang::get( 'cms::lang.cms_object.error_creating_directory', ['name' => $assetsPath] )); } } $searchTerm = Str::lower($this->getSearchTerm()); if (!strlen($searchTerm)) { $currentPath = $this->getCurrentPath(); return $this->getDirectoryContents( new DirectoryIterator($currentPath) ); } return $this->findFiles(); } protected function getAssetsPath() { return $this->theme->getPath().'/assets'; } protected function getThemeFileUrl($path) { return Url::to('themes/'.$this->theme->getDirName().'/assets'.$path); } public function getCurrentRelativePath() { $path = $this->getSession('currentPath', '/'); if (!$this->validatePath($path)) { return null; } if ($path == '.') { return null; } return ltrim($path, '/'); } protected function getCurrentPath() { $assetsPath = $this->getAssetsPath(); $path = $assetsPath.'/'.$this->getCurrentRelativePath(); if (!is_dir($path)) { return $assetsPath; } return $path; } protected function getRelativePath($path) { $prefix = $this->getAssetsPath(); if (substr($path, 0, strlen($prefix)) == $prefix) { $path = substr($path, strlen($prefix)); } return $path; } protected function getFullPath($path) { return $this->getAssetsPath().'/'.ltrim($path, '/'); } protected function validatePath($path) { if (!preg_match('/^[0-9a-z\.\s_\-\/]+$/i', $path)) { return false; } if (strpos($path, '..') !== false || strpos($path, './') !== false) { return false; } return true; } protected function validateName($name) { if (!preg_match('/^[0-9a-z\.\s_\-]+$/i', $name)) { return false; } if (strpos($name, '..') !== false) { return false; } return true; } protected function getDirectoryContents($dir) { $editableAssetTypes = Asset::getEditableExtensions(); $result = []; $files = []; foreach ($dir as $node) { if (substr($node->getFileName(), 0, 1) == '.') { continue; } if ($node->isDir() && !$node->isDot()) { $result[$node->getFilename()] = (object)[ 'type' => 'directory', 'path' => File::normalizePath($this->getRelativePath($node->getPathname())), 'name' => $node->getFilename(), 'editable' => false ]; } elseif ($node->isFile()) { $files[] = (object)[ 'type' => 'file', 'path' => File::normalizePath($this->getRelativePath($node->getPathname())), 'name' => $node->getFilename(), 'editable' => in_array(strtolower($node->getExtension()), $editableAssetTypes) ]; } } foreach ($files as $file) { $result[] = $file; } return $result; } protected function listDestinationDirectories(&$result, $excludeList, $startDir = null, $level = 0) { if ($startDir === null) { $startDir = $this->getAssetsPath(); $result['/'] = 'assets'; $level = 1; } $dirs = new DirectoryIterator($startDir); foreach ($dirs as $node) { if (substr($node->getFileName(), 0, 1) == '.') { continue; } if ($node->isDir() && !$node->isDot()) { $fullPath = $node->getPathname(); $relativePath = $this->getRelativePath($fullPath); if (array_key_exists($relativePath, $excludeList)) { continue; } $result[$relativePath] = str_repeat(' ', $level*4).$node->getFilename(); $this->listDestinationDirectories($result, $excludeList, $fullPath, $level+1); } } } protected function getSearchTerm() { return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search'); } protected function isSearchMode() { return strlen($this->getSearchTerm()); } protected function getThemeSessionKey($prefix) { return $prefix.$this->theme->getDirName(); } protected function getUpPath() { $path = $this->getCurrentRelativePath(); if (!strlen(rtrim(ltrim($path, '/'), '/'))) { return null; } return dirname($path); } protected function validateRequestTheme() { if ($this->theme->getDirName() != Request::input('theme')) { throw new ApplicationException(trans('cms::lang.theme.edit.not_match')); } } /** * Check for valid asset file extension * @param string * @return bool */ protected function validateFileType($name) { $extension = strtolower(File::extension($name)); if (!in_array($extension, $this->assetExtensions)) { return false; } return true; } /** * Process file uploads submitted via AJAX * * @return void * @throws ApplicationException If the file "file_data" wasn't detected in the request or if the file failed to pass validation / security checks */ public function onUpload() { $fileName = null; try { $uploadedFile = Input::file('file_data'); if (!is_object($uploadedFile)) { return; } $fileName = $uploadedFile->getClientOriginalName(); /* * Check valid upload */ if (!$uploadedFile->isValid()) { throw new ApplicationException(Lang::get('cms::lang.asset.file_not_valid')); } /* * Check file size */ $maxSize = UploadedFile::getMaxFilesize(); if ($uploadedFile->getSize() > $maxSize) { throw new ApplicationException(Lang::get( 'cms::lang.asset.too_large', ['max_size' => File::sizeToString($maxSize)] )); } /* * Check for valid file extensions */ if (!$this->validateFileType($fileName)) { throw new ApplicationException(Lang::get( 'cms::lang.asset.type_not_allowed', ['allowed_types' => implode(', ', $this->assetExtensions)] )); } /* * Accept the uploaded file */ $uploadedFile = $uploadedFile->move($this->getCurrentPath(), $uploadedFile->getClientOriginalName()); File::chmod($uploadedFile->getRealPath()); $response = Response::make('success'); } catch (Exception $ex) { $message = $fileName !== null ? Lang::get('cms::lang.asset.error_uploading_file', ['name' => $fileName, 'error' => $ex->getMessage()]) : $ex->getMessage(); $response = Response::make($message); } // Override the controller response $this->controller->setResponse($response); } protected function setSearchTerm($term) { $this->searchTerm = trim($term); $this->putSession('search', $this->searchTerm); } protected function findFiles() { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($this->getAssetsPath(), RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD ); $editableAssetTypes = Asset::getEditableExtensions(); $searchTerm = Str::lower($this->getSearchTerm()); $words = explode(' ', $searchTerm); $result = []; foreach ($iterator as $item) { if (!$item->isDir()) { if (substr($item->getFileName(), 0, 1) == '.') { continue; } $path = $this->getRelativePath($item->getPathname()); if ($this->pathMatchesSearch($words, $path)) { $result[] = (object)[ 'type' => 'file', 'path' => File::normalizePath($path), 'name' => $item->getFilename(), 'editable' => in_array(strtolower($item->getExtension()), $editableAssetTypes) ]; } } } return $result; } protected function pathMatchesSearch(&$words, $path) { foreach ($words as $word) { $word = trim($word); if (!strlen($word)) { continue; } if (!Str::contains(Str::lower($path), $word)) { return false; } } return true; } }