feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
777
modules/cms/widgets/AssetList.php
Normal file
777
modules/cms/widgets/AssetList.php
Normal file
@@ -0,0 +1,777 @@
|
||||
<?php
|
||||
|
||||
namespace Cms\Widgets;
|
||||
|
||||
use Backend\Classes\WidgetBase;
|
||||
use Cms\Classes\Asset;
|
||||
use Cms\Classes\Theme;
|
||||
use DirectoryIterator;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Winter\Storm\Exception\ApplicationException;
|
||||
use Winter\Storm\Filesystem\Definitions as FileDefinitions;
|
||||
use Winter\Storm\Support\Facades\File;
|
||||
use Winter\Storm\Support\Facades\Input;
|
||||
use Winter\Storm\Support\Facades\Url;
|
||||
use Winter\Storm\Support\Str;
|
||||
use Winter\Storm\Support\Svg;
|
||||
|
||||
/**
|
||||
* CMS asset list widget.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class AssetList extends WidgetBase
|
||||
{
|
||||
use \Backend\Traits\SelectableWidget;
|
||||
|
||||
protected $searchTerm = false;
|
||||
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* @var string Message to display when there are no records in the list.
|
||||
*/
|
||||
public $noRecordsMessage = 'cms::lang.asset.no_list_records';
|
||||
|
||||
/**
|
||||
* @var string Message to display when the Delete button is clicked.
|
||||
*/
|
||||
public $deleteConfirmation = 'cms::lang.asset.delete_confirm';
|
||||
|
||||
/**
|
||||
* @var array Valid asset file extensions
|
||||
*/
|
||||
protected $assetExtensions;
|
||||
|
||||
public function __construct($controller, $alias)
|
||||
{
|
||||
$this->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'));
|
||||
}
|
||||
|
||||
// Sanitize content if the file is being renamed to an SVG extension
|
||||
$newExt = strtolower(File::extension($newName));
|
||||
$oldExt = strtolower(File::extension(basename($originalPath)));
|
||||
if ($newExt === 'svg' && $oldExt !== $newExt) {
|
||||
File::put($originalFullPath, Svg::sanitize(File::get($originalFullPath)));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// theme dir does not exist (i.e. in a child theme without an assets directory
|
||||
if (!is_dir(dirname($assetsPath))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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::asset('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)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort directories & files in alphabetical order
|
||||
$sortByName = function ($a, $b) {
|
||||
return strcmp($a->name, $b->name);
|
||||
};
|
||||
usort($result, $sortByName);
|
||||
usort($files, $sortByName);
|
||||
|
||||
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()
|
||||
{
|
||||
$this->validateRequestTheme();
|
||||
|
||||
$fileName = null;
|
||||
|
||||
try {
|
||||
/**
|
||||
* @var \Illuminate\Http\UploadedFile
|
||||
*/
|
||||
$uploadedFile = Request::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)]
|
||||
));
|
||||
}
|
||||
|
||||
if (strtolower(File::extension($fileName)) === 'svg') {
|
||||
File::put($uploadedFile->getRealPath(), Svg::extract($uploadedFile->getRealPath()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Accept the uploaded file
|
||||
*/
|
||||
$uploadedFile = $uploadedFile->move($this->getCurrentPath(), $fileName);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
247
modules/cms/widgets/ComponentList.php
Normal file
247
modules/cms/widgets/ComponentList.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php namespace Cms\Widgets;
|
||||
|
||||
use App;
|
||||
use Str;
|
||||
use Lang;
|
||||
use Input;
|
||||
use System\Classes\PluginManager;
|
||||
use Cms\Classes\ComponentHelpers;
|
||||
use Backend\Classes\WidgetBase;
|
||||
|
||||
/**
|
||||
* Component list widget.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ComponentList extends WidgetBase
|
||||
{
|
||||
use \Backend\Traits\CollapsableWidget;
|
||||
|
||||
protected $searchTerm = false;
|
||||
|
||||
protected $pluginComponentList;
|
||||
|
||||
public function __construct($controller, $alias)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
|
||||
parent::__construct($controller, []);
|
||||
$this->bindToController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget.
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return $this->makePartial('body', [
|
||||
'data' => $this->getData()
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
public function onSearch()
|
||||
{
|
||||
$this->setSearchTerm(Input::get('search'));
|
||||
|
||||
return $this->updateList();
|
||||
}
|
||||
|
||||
/*
|
||||
* Methods for th internal use
|
||||
*/
|
||||
|
||||
protected function getData()
|
||||
{
|
||||
$searchTerm = Str::lower($this->getSearchTerm());
|
||||
$searchWords = [];
|
||||
if (strlen($searchTerm)) {
|
||||
$searchWords = explode(' ', $searchTerm);
|
||||
}
|
||||
|
||||
$pluginManager = PluginManager::instance();
|
||||
$plugins = $pluginManager->getPlugins();
|
||||
|
||||
$this->prepareComponentList();
|
||||
|
||||
$items = [];
|
||||
foreach ($plugins as $plugin) {
|
||||
$components = $this->getPluginComponents($plugin);
|
||||
if (!is_array($components)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pluginDetails = $plugin->pluginDetails();
|
||||
|
||||
$pluginName = $pluginDetails['name'] ?? Lang::get('system::lang.plugin.unnamed');
|
||||
$pluginIcon = $pluginDetails['icon'] ?? 'icon-puzzle-piece';
|
||||
$pluginDescription = $pluginDetails['description'] ?? null;
|
||||
|
||||
$pluginClass = get_class($plugin);
|
||||
|
||||
$pluginItems = [];
|
||||
foreach ($components as $componentInfo) {
|
||||
$className = $componentInfo->className;
|
||||
$alias = $componentInfo->alias;
|
||||
$component = App::make($className);
|
||||
|
||||
if ($component->isHidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$componentDetails = $component->componentDetails();
|
||||
$component->alias = '--alias--';
|
||||
|
||||
$item = (object)[
|
||||
'title' => ComponentHelpers::getComponentName($component),
|
||||
'description' => ComponentHelpers::getComponentDescription($component),
|
||||
'plugin' => $pluginName,
|
||||
'propertyConfig' => ComponentHelpers::getComponentsPropertyConfig($component),
|
||||
'propertyValues' => ComponentHelpers::getComponentPropertyValues($component, $alias),
|
||||
'className' => get_class($component),
|
||||
'pluginIcon' => $pluginIcon,
|
||||
'alias' => $alias,
|
||||
'name' => $componentInfo->duplicateAlias
|
||||
? $componentInfo->className
|
||||
: $componentInfo->alias
|
||||
];
|
||||
|
||||
if ($searchWords && !$this->itemMatchesSearch($searchWords, $item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($pluginClass, $items)) {
|
||||
$group = (object)[
|
||||
'title' => $pluginName,
|
||||
'description' => $pluginDescription,
|
||||
'pluginClass' => $pluginClass,
|
||||
'icon' => $pluginIcon,
|
||||
'items' => []
|
||||
];
|
||||
|
||||
$items[$pluginClass] = $group;
|
||||
}
|
||||
|
||||
$pluginItems[] = $item;
|
||||
}
|
||||
|
||||
usort($pluginItems, function ($a, $b) {
|
||||
return strcmp($a->title, $b->title);
|
||||
});
|
||||
|
||||
if (isset($items[$pluginClass])) {
|
||||
$items[$pluginClass]->items = $pluginItems;
|
||||
}
|
||||
}
|
||||
|
||||
uasort($items, function ($a, $b) {
|
||||
return strcmp($a->title, $b->title);
|
||||
});
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function prepareComponentList()
|
||||
{
|
||||
$pluginManager = PluginManager::instance();
|
||||
$plugins = $pluginManager->getPlugins();
|
||||
|
||||
$componentList = [];
|
||||
foreach ($plugins as $plugin) {
|
||||
$components = $plugin->registerComponents();
|
||||
if (!is_array($components)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($components as $className => $alias) {
|
||||
$duplicateAlias = false;
|
||||
foreach ($componentList as $componentInfo) {
|
||||
if ($componentInfo->alias == $alias) {
|
||||
$componentInfo->duplicateAlias = true;
|
||||
$duplicateAlias = true;
|
||||
}
|
||||
}
|
||||
|
||||
$componentList[] = (object)[
|
||||
'className' => $className,
|
||||
'alias' => $alias,
|
||||
'duplicateAlias' => $duplicateAlias,
|
||||
'pluginClass' => get_class($plugin)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->pluginComponentList = $componentList;
|
||||
}
|
||||
|
||||
protected function getPluginComponents($plugin)
|
||||
{
|
||||
$result = [];
|
||||
$pluginClass = get_class($plugin);
|
||||
foreach ($this->pluginComponentList as $componentInfo) {
|
||||
if ($componentInfo->pluginClass == $pluginClass) {
|
||||
$result[] = $componentInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getSearchTerm()
|
||||
{
|
||||
return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search');
|
||||
}
|
||||
|
||||
protected function setSearchTerm($term)
|
||||
{
|
||||
$this->searchTerm = trim($term);
|
||||
$this->putSession('search', $this->searchTerm);
|
||||
}
|
||||
|
||||
protected function updateList()
|
||||
{
|
||||
return [
|
||||
'#' . $this->getId('component-list') => $this->makePartial('items', [
|
||||
'items' => $this->getData()
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
protected function itemMatchesSearch(&$words, $item)
|
||||
{
|
||||
foreach ($words as $word) {
|
||||
$word = trim($word);
|
||||
if (!strlen($word)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->itemContainsWord($word, $item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function itemContainsWord($word, $item)
|
||||
{
|
||||
if (Str::contains(Str::lower($item->title), $word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Str::contains(Str::lower($item->description), $word) && strlen($item->description)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Str::contains(Str::lower($item->plugin), $word) && strlen($item->plugin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
26
modules/cms/widgets/MediaManager.php
Normal file
26
modules/cms/widgets/MediaManager.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php namespace Cms\Widgets;
|
||||
|
||||
use Backend\Widgets\MediaManager as BackendMediaManager;
|
||||
|
||||
/**
|
||||
* Media Manager widget.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
* @deprecated Use Backend\Widgets\MediaManager. Remove if year >= 2020.
|
||||
*/
|
||||
class MediaManager extends BackendMediaManager
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
traceLog('Widget Cms\Widgets\MediaManager has been deprecated, use ' . BackendMediaManager::class . ' instead.');
|
||||
|
||||
$this->assetPath = '/modules/backend/widgets/mediamanager/assets';
|
||||
$this->viewPath = base_path('/modules/backend/widgets/mediamanager/partials');
|
||||
|
||||
parent::__construct(...func_get_args());
|
||||
}
|
||||
}
|
||||
415
modules/cms/widgets/TemplateList.php
Normal file
415
modules/cms/widgets/TemplateList.php
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php namespace Cms\Widgets;
|
||||
|
||||
use Str;
|
||||
use File;
|
||||
use Input;
|
||||
use Request;
|
||||
use Cms\Classes\Theme;
|
||||
use Backend\Classes\WidgetBase;
|
||||
|
||||
/**
|
||||
* Template list widget.
|
||||
* This widget displays templates of different types.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class TemplateList extends WidgetBase
|
||||
{
|
||||
const SORTING_FILENAME = 'fileName';
|
||||
|
||||
use \Backend\Traits\SelectableWidget;
|
||||
use \Backend\Traits\CollapsableWidget;
|
||||
|
||||
protected $searchTerm = false;
|
||||
|
||||
protected $dataSource;
|
||||
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* @var string object property to use as a title.
|
||||
*/
|
||||
public $titleProperty;
|
||||
|
||||
/**
|
||||
* @var array a list of object properties to use in the description area.
|
||||
* The array should include the property names and corresponding titles:
|
||||
* ['url'=>'URL']
|
||||
*/
|
||||
public $descriptionProperties = [];
|
||||
|
||||
/**
|
||||
* @var string object property to use as a description.
|
||||
*/
|
||||
public $descriptionProperty;
|
||||
|
||||
/**
|
||||
* @var string Message to display when there are no records in the list.
|
||||
*/
|
||||
public $noRecordsMessage = 'cms::lang.template.no_list_records';
|
||||
|
||||
/**
|
||||
* @var string Message to display when the Delete button is clicked.
|
||||
*/
|
||||
public $deleteConfirmation = 'cms::lang.template.delete_confirm';
|
||||
|
||||
/**
|
||||
* @var string Specifies the item type.
|
||||
*/
|
||||
public $itemType;
|
||||
|
||||
/**
|
||||
* @var string Extra CSS class name to apply to the control.
|
||||
*/
|
||||
public $controlClass;
|
||||
|
||||
/**
|
||||
* @var string A list of file name patterns to suppress / hide.
|
||||
*/
|
||||
public $ignoreDirectories = [];
|
||||
|
||||
/**
|
||||
* @var boolean Defines sorting properties.
|
||||
* The sorting feature is disabled if there are no sorting properties defined.
|
||||
*/
|
||||
public $sortingProperties = [];
|
||||
|
||||
/*
|
||||
* Public methods
|
||||
*/
|
||||
|
||||
public function __construct($controller, $alias, callable $dataSource)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
$this->dataSource = $dataSource;
|
||||
$this->theme = Theme::getEditTheme();
|
||||
$this->selectionInputName = 'template';
|
||||
$this->collapseSessionKey = $this->getThemeSessionKey('groups');
|
||||
|
||||
parent::__construct($controller, []);
|
||||
|
||||
if (!Request::isXmlHttpRequest()) {
|
||||
$this->resetSelection();
|
||||
}
|
||||
|
||||
$configFile = 'config_' . snake_case($alias) .'.yaml';
|
||||
$config = $this->makeConfig($configFile);
|
||||
|
||||
foreach ($config as $field => $value) {
|
||||
if (property_exists($this, $field)) {
|
||||
$this->$field = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->bindToController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget.
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$toolbarClass = Str::contains($this->controlClass, 'hero') ? 'separator' : null;
|
||||
|
||||
$this->vars['toolbarClass'] = $toolbarClass;
|
||||
|
||||
return $this->makePartial('body', [
|
||||
'data' => $this->getData()
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
public function onSearch()
|
||||
{
|
||||
$this->setSearchTerm(Input::get('search'));
|
||||
$this->extendSelection();
|
||||
|
||||
return $this->updateList();
|
||||
}
|
||||
|
||||
public function onUpdate()
|
||||
{
|
||||
$this->extendSelection();
|
||||
|
||||
return $this->updateList();
|
||||
}
|
||||
|
||||
public function onApplySorting()
|
||||
{
|
||||
$this->setSortingProperty(Input::get('sortProperty'));
|
||||
|
||||
$result = $this->updateList();
|
||||
$result['#'.$this->getId('sorting-options')] = $this->makePartial('sorting-options');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
//
|
||||
// Methods for the internal use
|
||||
//
|
||||
|
||||
protected function getData()
|
||||
{
|
||||
/*
|
||||
* Load the data
|
||||
*/
|
||||
$items = call_user_func($this->dataSource);
|
||||
|
||||
if ($items instanceof \Winter\Storm\Support\Collection) {
|
||||
$items = $items->all();
|
||||
}
|
||||
|
||||
$items = $this->removeIgnoredDirectories($items);
|
||||
|
||||
$items = array_map([$this, 'normalizeItem'], $items);
|
||||
|
||||
$this->sortItems($items);
|
||||
|
||||
/*
|
||||
* Apply the search
|
||||
*/
|
||||
$filteredItems = [];
|
||||
$searchTerm = Str::lower($this->getSearchTerm());
|
||||
|
||||
if (strlen($searchTerm)) {
|
||||
/*
|
||||
* Exact
|
||||
*/
|
||||
foreach ($items as $index => $item) {
|
||||
if ($this->itemContainsWord($searchTerm, $item, true)) {
|
||||
$filteredItems[] = $item;
|
||||
unset($items[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuzzy
|
||||
*/
|
||||
$words = explode(' ', $searchTerm);
|
||||
foreach ($items as $item) {
|
||||
if ($this->itemMatchesSearch($words, $item)) {
|
||||
$filteredItems[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$filteredItems = $items;
|
||||
}
|
||||
|
||||
/*
|
||||
* Group the items
|
||||
*/
|
||||
$result = [];
|
||||
$foundGroups = [];
|
||||
foreach ($filteredItems as $itemData) {
|
||||
$pos = strpos($itemData->fileName, '/');
|
||||
|
||||
if ($pos !== false) {
|
||||
$group = substr($itemData->fileName, 0, $pos);
|
||||
if (!array_key_exists($group, $foundGroups)) {
|
||||
$newGroup = (object)[
|
||||
'title' => $group,
|
||||
'items' => []
|
||||
];
|
||||
|
||||
$foundGroups[$group] = $newGroup;
|
||||
}
|
||||
|
||||
$foundGroups[$group]->items[] = $itemData;
|
||||
}
|
||||
else {
|
||||
$result[] = $itemData;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort folders by name regardless of the
|
||||
// selected sorting options.
|
||||
ksort($foundGroups);
|
||||
|
||||
foreach ($foundGroups as $group) {
|
||||
$result[] = $group;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function sortItems(&$items)
|
||||
{
|
||||
$sortingProperty = $this->getSortingProperty();
|
||||
|
||||
usort($items, function ($a, $b) use ($sortingProperty) {
|
||||
return strcmp($a->$sortingProperty, $b->$sortingProperty);
|
||||
});
|
||||
}
|
||||
|
||||
protected function removeIgnoredDirectories($items)
|
||||
{
|
||||
if (!$this->ignoreDirectories) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$ignoreCache = [];
|
||||
|
||||
$items = array_filter($items, function ($item) use (&$ignoreCache) {
|
||||
$fileName = $item->getBaseFileName();
|
||||
$dirName = dirname($fileName);
|
||||
|
||||
if (isset($ignoreCache[$dirName])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->ignoreDirectories as $ignoreDir) {
|
||||
if (File::fileNameMatch($dirName, $ignoreDir)) {
|
||||
$ignoreCache[$dirName] = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function normalizeItem($item)
|
||||
{
|
||||
$description = null;
|
||||
if ($descriptionProperty = $this->descriptionProperty) {
|
||||
$description = $item->$descriptionProperty;
|
||||
}
|
||||
|
||||
$descriptions = [];
|
||||
foreach ($this->descriptionProperties as $property => $title) {
|
||||
if ($item->$property) {
|
||||
$descriptions[$title] = $item->$property;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [
|
||||
'title' => $this->getItemTitle($item),
|
||||
'fileName' => $item->getFileName(),
|
||||
'description' => $description,
|
||||
'descriptions' => $descriptions,
|
||||
'dragValue' => $this->getItemDragValue($item)
|
||||
];
|
||||
|
||||
foreach ($this->sortingProperties as $property => $name) {
|
||||
$result[$property] = $item->$property;
|
||||
}
|
||||
|
||||
return (object) $result;
|
||||
}
|
||||
|
||||
protected function getItemDragValue($item)
|
||||
{
|
||||
if ($item instanceof \Cms\Classes\Partial) {
|
||||
return "{% partial '".$item->getBaseFileName()."' %}";
|
||||
}
|
||||
|
||||
if ($item instanceof \Cms\Classes\Content) {
|
||||
return "{% content '".$item->getBaseFileName()."' %}";
|
||||
}
|
||||
|
||||
if ($item instanceof \Cms\Classes\Page) {
|
||||
return "{{ '".$item->getBaseFileName()."'|page }}";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function getItemTitle($item)
|
||||
{
|
||||
$titleProperty = $this->titleProperty;
|
||||
|
||||
if ($titleProperty) {
|
||||
return $item->$titleProperty ?: basename($item->getFileName());
|
||||
}
|
||||
|
||||
return basename($item->getFileName());
|
||||
}
|
||||
|
||||
protected function setSearchTerm($term)
|
||||
{
|
||||
$this->searchTerm = trim($term);
|
||||
$this->putSession('search', $this->searchTerm);
|
||||
}
|
||||
|
||||
protected function getSearchTerm()
|
||||
{
|
||||
return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search');
|
||||
}
|
||||
|
||||
protected function updateList()
|
||||
{
|
||||
return [
|
||||
'#'.$this->getId('template-list') => $this->makePartial('items', ['items' => $this->getData()])
|
||||
];
|
||||
}
|
||||
|
||||
protected function itemMatchesSearch($words, $item)
|
||||
{
|
||||
foreach ($words as $word) {
|
||||
$word = trim($word);
|
||||
if (!strlen($word)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->itemContainsWord($word, $item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function itemContainsWord($word, $item, $exact = false)
|
||||
{
|
||||
$operator = $exact ? 'is' : 'contains';
|
||||
|
||||
if (strlen($item->title) && Str::$operator(Str::lower($item->title), $word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Str::$operator(Str::lower($item->fileName), $word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Str::$operator(Str::lower($item->description), $word) && strlen($item->description)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($item->descriptions as $value) {
|
||||
if (Str::$operator(Str::lower($value), $word) && strlen($value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getThemeSessionKey($prefix)
|
||||
{
|
||||
return $prefix.$this->theme->getDirName();
|
||||
}
|
||||
|
||||
protected function getSortingProperty()
|
||||
{
|
||||
$property = $this->getSession($this->getThemeSessionKey('sorting_property'), self::SORTING_FILENAME);
|
||||
|
||||
if (!array_key_exists($property, $this->sortingProperties)) {
|
||||
return self::SORTING_FILENAME;
|
||||
}
|
||||
|
||||
return $property;
|
||||
}
|
||||
|
||||
protected function setSortingProperty($property)
|
||||
{
|
||||
$this->putSession($this->getThemeSessionKey('sorting_property'), $property);
|
||||
}
|
||||
}
|
||||
63
modules/cms/widgets/assetlist/assets/css/assetlist.css
Normal file
63
modules/cms/widgets/assetlist/assets/css/assetlist.css
Normal file
@@ -0,0 +1,63 @@
|
||||
.control-assetlist p.no-data{padding:22px;margin:0;color:#666;font-size:14px;text-align:center;font-weight:400;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}
|
||||
.control-assetlist p.parent,
|
||||
.control-assetlist ul li{font-weight:300;line-height:150%;margin-bottom:0}
|
||||
.control-assetlist p.parent.active a,
|
||||
.control-assetlist ul li.active a{background:#ddd;position:relative}
|
||||
.control-assetlist p.parent.active a:after,
|
||||
.control-assetlist ul li.active a:after{position:absolute;height:100%;width:4px;left:0;top:0;background:#2da7c7;display:block;content:' '}
|
||||
.control-assetlist p.parent a.link,
|
||||
.control-assetlist ul li a.link{display:block;position:relative;word-wrap:break-word;padding:10px 50px 10px 20px;outline:none;font-weight:400;color:#405261;font-size:14px}
|
||||
.control-assetlist p.parent a.link:hover,
|
||||
.control-assetlist ul li a.link:hover,
|
||||
.control-assetlist p.parent a.link:focus,
|
||||
.control-assetlist ul li a.link:focus,
|
||||
.control-assetlist p.parent a.link:active,
|
||||
.control-assetlist ul li a.link:active{text-decoration:none}
|
||||
.control-assetlist p.parent a.link span,
|
||||
.control-assetlist ul li a.link span{display:block}
|
||||
.control-assetlist p.parent a.link span.description,
|
||||
.control-assetlist ul li a.link span.description{color:#8f8f8f;font-size:12px;font-weight:400;word-wrap:break-word}
|
||||
.control-assetlist p.parent a.link span.description strong,
|
||||
.control-assetlist ul li a.link span.description strong{color:#405261;font-weight:400}
|
||||
.control-assetlist p.parent.directory a.link,
|
||||
.control-assetlist ul li.directory a.link,
|
||||
.control-assetlist p.parent.parent a.link,
|
||||
.control-assetlist ul li.parent a.link{padding-left:40px}
|
||||
.control-assetlist p.parent.directory a.link:after,
|
||||
.control-assetlist ul li.directory a.link:after,
|
||||
.control-assetlist p.parent.parent a.link:after,
|
||||
.control-assetlist ul li.parent a.link:after{display:block;position:absolute;width:10px;height:10px;top:10px;left:20px;font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f07b";color:#a1aab1;font-size:14px}
|
||||
.control-assetlist p.parent.parent a.link,
|
||||
.control-assetlist ul li.parent a.link{padding-left:41px;background-color:#fff;word-wrap:break-word}
|
||||
.control-assetlist p.parent.parent a.link:before,
|
||||
.control-assetlist ul li.parent a.link:before{content:'';display:block;position:absolute;left:0;top:0;width:100%;height:1px;background:#ecf0f1}
|
||||
.control-assetlist p.parent.parent a.link:after,
|
||||
.control-assetlist ul li.parent a.link:after{font-size:13px;color:#103141;width:18px;height:18px;top:11px;left:22px;opacity:0.5;filter:alpha(opacity=50);font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f053"}
|
||||
.control-assetlist p.parent a.link:hover{background:#ddd !important}
|
||||
.control-assetlist p.parent a.link:hover:after{opacity:1;filter:alpha(opacity=100)}
|
||||
.control-assetlist p.parent a.link:hover:before{display:none}
|
||||
.control-assetlist ul{padding:0;margin:0}
|
||||
.control-assetlist ul li{font-weight:300;line-height:150%;position:relative;list-style:none}
|
||||
.control-assetlist ul li.active a.link,
|
||||
.control-assetlist ul li a.link:hover{background:#ddd}
|
||||
.control-assetlist ul li.active a.link{position:relative}
|
||||
.control-assetlist ul li.active a.link:after{position:absolute;height:100%;width:4px;left:0;top:0;background:#2da7c7;display:block;content:' '}
|
||||
.control-assetlist ul li div.controls{position:absolute;right:45px;top:10px}
|
||||
.control-assetlist ul li div.controls .dropdown{width:14px;height:21px}
|
||||
.control-assetlist ul li div.controls .dropdown.open a.control{display:block!important}
|
||||
.control-assetlist ul li div.controls .dropdown.open a.control:before{visibility:visible;display:block}
|
||||
.control-assetlist ul li div.controls a.control{color:#405261;font-size:14px;visibility:hidden;overflow:hidden;width:14px;height:21px;display:none;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}
|
||||
.control-assetlist ul li div.controls a.control:before{visibility:visible;display:block;margin-right:0}
|
||||
.control-assetlist ul li div.controls a.control:hover{opacity:1;filter:alpha(opacity=100)}
|
||||
.control-assetlist ul li:hover{background:#ddd}
|
||||
.control-assetlist ul li:hover div.controls,
|
||||
.control-assetlist ul li:hover a.control{display:block!important}
|
||||
.control-assetlist ul li:hover div.controls>a.control,
|
||||
.control-assetlist ul li:hover a.control>a.control{display:block!important}
|
||||
.control-assetlist ul li .checkbox{position:absolute;top:-5px;right:-5px}
|
||||
.control-assetlist ul li .checkbox label{margin-right:0}
|
||||
.control-assetlist ul li .checkbox label:before{border-color:#ccc}
|
||||
.control-assetlist div.list-container{position:relative;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}
|
||||
.control-assetlist div.list-container.animate ul{-webkit-transition:all 0.2s ease;transition:all 0.2s ease}
|
||||
.control-assetlist div.list-container.goForward ul{-webkit-transform:translate(-350px,0);-ms-transform:translate(-350px,0);transform:translate(-350px,0)}
|
||||
.control-assetlist div.list-container.goBackward ul{-webkit-transform:translate(350px,0);-ms-transform:translate(350px,0);transform:translate(350px,0)}
|
||||
184
modules/cms/widgets/assetlist/assets/js/assetlist.js
Normal file
184
modules/cms/widgets/assetlist/assets/js/assetlist.js
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Asset list
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
var AssetList = function (form, alias) {
|
||||
this.$form = $(form)
|
||||
this.alias = alias
|
||||
|
||||
|
||||
this.$form.on('ajaxSuccess', $.proxy(this.onAjaxSuccess, this))
|
||||
this.$form.on('click', 'ul.list > li.directory > a', $.proxy(this.onDirectoryClick, this))
|
||||
this.$form.on('click', 'ul.list > li.file > a', $.proxy(this.onFileClick, this))
|
||||
this.$form.on('click', 'p.parent > a', $.proxy(this.onDirectoryClick, this))
|
||||
this.$form.on('click', 'a[data-control=delete-asset]', $.proxy(this.onDeleteClick, this))
|
||||
this.$form.on('oc.list.setActiveItem', $.proxy(this.onSetActiveItem, this))
|
||||
|
||||
this.setupUploader()
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
// =================
|
||||
|
||||
AssetList.prototype.onDirectoryClick = function(e) {
|
||||
this.gotoDirectory(
|
||||
$(e.currentTarget).data('path'),
|
||||
$(e.currentTarget).parent().hasClass('parent')
|
||||
)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AssetList.prototype.gotoDirectory = function(path, gotoParent) {
|
||||
var $container = $('div.list-container', this.$form),
|
||||
self = this
|
||||
|
||||
if (gotoParent !== undefined && gotoParent)
|
||||
$container.addClass('goBackward')
|
||||
else
|
||||
$container.addClass('goForward')
|
||||
|
||||
$.wn.stripeLoadIndicator.show()
|
||||
this.$form.request(this.alias+'::onOpenDirectory', {
|
||||
data: {
|
||||
path: path,
|
||||
d: 0.2
|
||||
},
|
||||
complete: function() {
|
||||
self.updateUi()
|
||||
$container.trigger('oc.scrollbar.gotoStart')
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
$container.removeClass('goForward goBackward')
|
||||
alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText)
|
||||
}
|
||||
}).always(function(){
|
||||
$.wn.stripeLoadIndicator.hide()
|
||||
})
|
||||
}
|
||||
|
||||
AssetList.prototype.onDeleteClick = function(e) {
|
||||
var $el = $(e.currentTarget),
|
||||
self = this
|
||||
|
||||
if (!confirm($el.data('confirmation')))
|
||||
return false
|
||||
|
||||
this.$form.request(this.alias+'::onDeleteFiles', {
|
||||
success: function(data) {
|
||||
if (data.error !== undefined && $.type(data.error) === 'string' && data.error.length)
|
||||
$.wn.flashMsg({text: data.error, 'class': 'error'})
|
||||
},
|
||||
complete: function() {
|
||||
self.refresh()
|
||||
}
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
AssetList.prototype.onAjaxSuccess = function() {
|
||||
this.updateUi()
|
||||
}
|
||||
|
||||
AssetList.prototype.onUploadFail = function(file, message) {
|
||||
if (file.xhr.status === 413) {
|
||||
message = 'Server rejected the file because it was too large, try increasing post_max_size';
|
||||
}
|
||||
if (!message) {
|
||||
message = 'Error uploading file'
|
||||
}
|
||||
|
||||
$.wn.alert(message)
|
||||
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
AssetList.prototype.onUploadSuccess = function(file, data) {
|
||||
if (data !== 'success') {
|
||||
$.wn.alert(data)
|
||||
}
|
||||
}
|
||||
|
||||
AssetList.prototype.onUploadComplete = function(file, data) {
|
||||
$.wn.stripeLoadIndicator.hide()
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
AssetList.prototype.onUploadStart = function() {
|
||||
$.wn.stripeLoadIndicator.show()
|
||||
}
|
||||
|
||||
AssetList.prototype.onFileClick = function(event) {
|
||||
var $link = $(event.currentTarget),
|
||||
$li = $link.parent()
|
||||
|
||||
var e = $.Event('open.oc.list', {relatedTarget: $li.get(0), clickEvent: event})
|
||||
this.$form.trigger(e, this)
|
||||
|
||||
if (e.isDefaultPrevented())
|
||||
return false;
|
||||
}
|
||||
|
||||
AssetList.prototype.onSetActiveItem = function(event, dataId) {
|
||||
$('ul li.file', this.$form).removeClass('active')
|
||||
if (dataId)
|
||||
$('ul li.file[data-id="'+dataId+'"]', this.$form).addClass('active')
|
||||
}
|
||||
|
||||
// Service functions
|
||||
// =================
|
||||
|
||||
AssetList.prototype.updateUi = function() {
|
||||
$('button[data-control=asset-tools]', self.$form).trigger('oc.triggerOn.update')
|
||||
}
|
||||
|
||||
AssetList.prototype.refresh = function() {
|
||||
var self = this;
|
||||
|
||||
this.$form.request(this.alias+'::onRefresh', {
|
||||
complete: function() {
|
||||
self.updateUi()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
AssetList.prototype.setupUploader = function() {
|
||||
var self = this,
|
||||
$link = $('[data-control="upload-assets"]', this.$form),
|
||||
uploaderOptions = {
|
||||
method: 'POST',
|
||||
url: window.location,
|
||||
paramName: 'file_data',
|
||||
previewsContainer: $('<div />').get(0),
|
||||
clickable: $link.get(0),
|
||||
timeout: 0,
|
||||
headers: {}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add CSRF token to headers
|
||||
*/
|
||||
var token = $('meta[name="csrf-token"]').attr('content')
|
||||
if (token) {
|
||||
uploaderOptions.headers['X-CSRF-TOKEN'] = token
|
||||
}
|
||||
|
||||
var dropzone = new Dropzone($('<div />').get(0), uploaderOptions)
|
||||
dropzone.on('error', $.proxy(self.onUploadFail, self))
|
||||
dropzone.on('success', $.proxy(self.onUploadSuccess, self))
|
||||
dropzone.on('complete', $.proxy(self.onUploadComplete, self))
|
||||
dropzone.on('sending', function(file, xhr, formData) {
|
||||
$.each(self.$form.serializeArray(), function (index, field) {
|
||||
formData.append(field.name, field.value)
|
||||
})
|
||||
xhr.setRequestHeader('X-WINTER-REQUEST-HANDLER', self.alias + '::onUpload')
|
||||
self.onUploadStart()
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
new AssetList($('#asset-list-container').closest('form'), $('#asset-list-container').data('alias'))
|
||||
})
|
||||
}(window.jQuery);
|
||||
236
modules/cms/widgets/assetlist/assets/less/assetlist.less
Normal file
236
modules/cms/widgets/assetlist/assets/less/assetlist.less
Normal file
@@ -0,0 +1,236 @@
|
||||
@import "../../../../../backend/assets/less/core/boot.less";
|
||||
|
||||
.control-assetlist {
|
||||
p.no-data {
|
||||
padding: 22px;
|
||||
margin: 0;
|
||||
color: @color-filelist-norecords-text;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
.border-radius(@border-radius-base);
|
||||
}
|
||||
|
||||
p.parent, ul li {
|
||||
font-weight: 300;
|
||||
line-height: 150%;
|
||||
margin-bottom: 0;
|
||||
|
||||
&.active a {
|
||||
background: @color-list-active;
|
||||
position: relative;
|
||||
&:after {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 4px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: @color-list-active-border;
|
||||
display: block;
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
|
||||
a.link {
|
||||
display: block;
|
||||
position: relative;
|
||||
word-wrap: break-word;
|
||||
padding: 10px 50px 10px 20px;
|
||||
outline: none;
|
||||
font-weight: 400;
|
||||
color: @color-text-title;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover, &:focus, &:active {text-decoration: none;}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
|
||||
&.description {
|
||||
color: @color-text-description;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
word-wrap: break-word;
|
||||
|
||||
strong {
|
||||
color: @color-text-title;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.directory, &.parent {
|
||||
a.link {
|
||||
padding-left: 40px;
|
||||
|
||||
&:after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
.icon(@folder);
|
||||
color: @color-list-icon;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.parent {
|
||||
a.link {
|
||||
padding-left: 41px;
|
||||
background-color: @color-list-parent-bg;
|
||||
word-wrap: break-word;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
height: 1px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #ecf0f1;
|
||||
}
|
||||
|
||||
|
||||
&:after {
|
||||
font-size: 13px;
|
||||
color: @color-list-nav-arrow;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
top: 11px;
|
||||
left: 22px;
|
||||
.opacity(0.5);
|
||||
.icon(@chevron-left);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.parent a.link:hover {
|
||||
background: @color-list-active!important;
|
||||
|
||||
&:after {
|
||||
.opacity(1);
|
||||
}
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
font-weight: 300;
|
||||
line-height: 150%;
|
||||
position: relative;
|
||||
list-style: none;
|
||||
|
||||
&.active a.link, a.link:hover {background: @color-list-active;}
|
||||
&.active a.link {
|
||||
position: relative;
|
||||
&:after {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 4px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: @color-list-active-border;
|
||||
display: block;
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
|
||||
div.controls {
|
||||
position: absolute;
|
||||
right: 45px;
|
||||
top: 10px;
|
||||
|
||||
.dropdown {
|
||||
width: 14px;
|
||||
height: 21px;
|
||||
|
||||
&.open a.control {
|
||||
display: block!important;
|
||||
&:before {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.control {
|
||||
color: @color-text-title;
|
||||
font-size: 14px;
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
width: 14px;
|
||||
height: 21px;
|
||||
display: none;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
.opacity(0.5);
|
||||
&:before {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @color-list-active;
|
||||
div.controls, a.control {
|
||||
display: block!important;
|
||||
|
||||
> a.control {
|
||||
display: block!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
|
||||
label {
|
||||
margin-right: 0;
|
||||
|
||||
&:before {
|
||||
border-color: @color-filelist-cb-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.list-container {
|
||||
position: relative;
|
||||
.translate(0, 0);
|
||||
|
||||
&.animate ul {
|
||||
.transition(all 0.2s ease);
|
||||
}
|
||||
|
||||
&.goForward ul {
|
||||
.translate(-350px, 0);
|
||||
}
|
||||
|
||||
&.goBackward ul {
|
||||
.translate(350px, 0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
9
modules/cms/widgets/assetlist/partials/_body.php
Normal file
9
modules/cms/widgets/assetlist/partials/_body.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?= $this->makePartial('toolbar') ?>
|
||||
<div class="layout-row" id="asset-list-container" data-alias="<?= $this->alias ?>">
|
||||
<div class="layout-cell">
|
||||
<div class="layout-relative">
|
||||
<?= $this->makePartial('files', ['data'=>$data]) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="theme" value="<?= e($this->theme->getDirName()) ?>">
|
||||
7
modules/cms/widgets/assetlist/partials/_files.php
Normal file
7
modules/cms/widgets/assetlist/partials/_files.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="layout-absolute">
|
||||
<div class="control-scrollbar" data-control="scrollbar">
|
||||
<div class="control-assetlist" data-control="assetlist" id="<?= $this->getId('asset-list') ?>">
|
||||
<?= $this->makePartial('items', ['items'=>$data]) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
69
modules/cms/widgets/assetlist/partials/_items.php
Normal file
69
modules/cms/widgets/assetlist/partials/_items.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
$searchMode = $this->isSearchMode();
|
||||
|
||||
if (($upPath = $this->getUpPath()) !== null && !$searchMode):
|
||||
?>
|
||||
<p class="parent">
|
||||
<a href="<?= $upPath ?>" data-path="<?= $upPath ?>" class="link"><?= $this->getCurrentRelativePath() ?></a>
|
||||
</p>
|
||||
<?php endif ?>
|
||||
<div class="list-container animate">
|
||||
<?php if ($items): ?>
|
||||
<ul class="list">
|
||||
<?php foreach ($items as $item):
|
||||
$dataId = 'asset-'.$this->theme->getDirName().'-'.ltrim($item->path, '/');
|
||||
?>
|
||||
<li
|
||||
class="<?= $item->type ?>"
|
||||
<?php if ($item->editable): ?>
|
||||
data-editable
|
||||
<?php endif ?>
|
||||
data-item-path="<?= e(ltrim($item->path, '/')) ?>"
|
||||
data-item-theme="<?= e($this->theme->getDirName()) ?>"
|
||||
data-item-type="asset" data-id="<?= e($dataId) ?>"
|
||||
>
|
||||
<a class="link" target="_blank" data-path="<?= $item->path ?>" href="<?= $this->getThemeFileUrl($item->path) ?>">
|
||||
<?= e($item->name) ?>
|
||||
|
||||
<?php if ($searchMode): ?>
|
||||
<span class="description">
|
||||
<?= e(dirname($item->path)) ?>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</a>
|
||||
|
||||
<div class="controls">
|
||||
<a
|
||||
href="javascript:;"
|
||||
class="control icon btn-primary wn-icon-terminal"
|
||||
title="<?= e(trans('cms::lang.asset.rename')) ?>"
|
||||
data-control="popup"
|
||||
data-request-data="renamePath: '<?= e($item->path) ?>'"
|
||||
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
|
||||
><?= e(trans('cms::lang.asset.rename')) ?></a>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="file[<?= e($item->path) ?>]" value="0"/>
|
||||
<div class="checkbox custom-checkbox nolabel">
|
||||
<?php $cbId = 'cb'.md5($item->path) ?>
|
||||
<input
|
||||
id="<?= $cbId ?>"
|
||||
type="checkbox"
|
||||
name="file[<?= e($item->path) ?>]"
|
||||
<?= $this->isItemSelected($item->path) ? 'checked' : null ?>
|
||||
data-request="<?= $this->getEventHandler('onSelect') ?>"
|
||||
value="1">
|
||||
<label for="<?= $cbId ?>"><?= e(trans('cms::lang.asset.select')) ?></label>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="no-data"><?= e(trans($this->noRecordsMessage)) ?></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?php if (!isset($nested)): ?>
|
||||
<input type="hidden" name="theme" value="<?= e($this->theme->getDirName()) ?>">
|
||||
<?php endif ?>
|
||||
41
modules/cms/widgets/assetlist/partials/_move_form.php
Normal file
41
modules/cms/widgets/assetlist/partials/_move_form.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?= Form::open([
|
||||
'data-request'=>$this->getEventHandler('onMove'),
|
||||
'data-request-success'=>"\$(this).trigger('close.oc.popup')",
|
||||
'data-stripe-load-indicator'=>1,
|
||||
'id'=>'asset-move-popup-form'
|
||||
]) ?>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="popup">×</button>
|
||||
<h4 class="modal-title"><?= e(trans('cms::lang.asset.move_popup_title')) ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label><?= e(trans('cms::lang.asset.move_destination')) ?></label>
|
||||
<select
|
||||
class="form-control custom-select"
|
||||
name="dest"
|
||||
data-placeholder="<?= e(trans('backend::lang.media.move_please_select')) ?>">
|
||||
<option></option>
|
||||
<?php foreach ($directories as $path => $directory): ?>
|
||||
<option value="<?= e($path) ?>"><?= e($directory) ?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="theme" value="<?= e($this->theme->getDirName()) ?>">
|
||||
<input type="hidden" name="selectedList" value="<?= e($selectedList) ?>">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.media.move_button')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="popup">
|
||||
<?= e(trans('backend::lang.form.cancel')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
43
modules/cms/widgets/assetlist/partials/_new_dir_form.php
Normal file
43
modules/cms/widgets/assetlist/partials/_new_dir_form.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?= Form::open([
|
||||
'data-request'=>$this->getEventHandler('onNewDirectory'),
|
||||
'data-request-success'=>"\$(this).trigger('close.oc.popup')",
|
||||
'data-stripe-load-indicator'=>1,
|
||||
'id'=>'asset-new-dir-popup-form'
|
||||
]) ?>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="popup">×</button>
|
||||
<h4 class="modal-title"><?= e(trans('cms::lang.asset.directory_popup_title')) ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label><?= e(trans('cms::lang.asset.directory_name')) ?></label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value=""
|
||||
class="form-control"
|
||||
autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="theme" value="<?= e($this->theme->getDirName()) ?>">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.create')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="popup">
|
||||
<?= e(trans('backend::lang.form.cancel')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(
|
||||
function(){ $('#asset-new-dir-popup-form input.form-control').focus() },
|
||||
310
|
||||
)
|
||||
</script>
|
||||
<?= Form::close() ?>
|
||||
43
modules/cms/widgets/assetlist/partials/_rename_form.php
Normal file
43
modules/cms/widgets/assetlist/partials/_rename_form.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?= Form::ajax($this->getEventHandler('onApplyName'), [
|
||||
'success' => "\$el.trigger('close.oc.popup');",
|
||||
'data-stripe-load-indicator' => 1,
|
||||
'id' => 'asset-rename-popup-form'
|
||||
]) ?>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="popup">×</button>
|
||||
<h4 class="modal-title"><?= e(trans('backend::lang.media.rename_popup_title')) ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label><?= e(trans('backend::lang.media.rename_new_name')) ?></label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value="<?= e($name) ?>"
|
||||
class="form-control"
|
||||
autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="originalPath" value="<?= e($originalPath) ?>" />
|
||||
<input type="hidden" name="theme" value="<?= e($this->theme->getDirName()) ?>" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.apply')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="popup">
|
||||
<?= e(trans('backend::lang.form.cancel')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(
|
||||
function(){ $('#asset-rename-popup-form input.form-control').focus() },
|
||||
310
|
||||
)
|
||||
</script>
|
||||
<?= Form::close() ?>
|
||||
78
modules/cms/widgets/assetlist/partials/_toolbar.php
Normal file
78
modules/cms/widgets/assetlist/partials/_toolbar.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<div class="layout-row min-size">
|
||||
<div class="control-toolbar toolbar-padded">
|
||||
|
||||
<!-- Control Panel -->
|
||||
<div class="toolbar-item" data-calculate-width>
|
||||
<div class="btn-group">
|
||||
<div class="dropdown last">
|
||||
<button type="button" class="btn btn-default wn-icon-plus"
|
||||
data-control="create-asset"
|
||||
data-toggle="dropdown"
|
||||
><?= e(trans('cms::lang.sidebar.add')) ?></button>
|
||||
|
||||
<ul class="dropdown-menu offset-left" role="menu" data-dropdown-title="<?= e(trans('cms::lang.asset.drop_down_add_title')) ?>">
|
||||
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" data-control="create-template" class="wn-icon-file-text-o"><?= e(trans('cms::lang.asset.create_file')) ?></a></li>
|
||||
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" data-control="upload-assets" class="wn-icon-upload"><?= e(trans('cms::lang.asset.upload_files')) ?></a></li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
<li role="presentation"><a
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
href="javascript:;"
|
||||
class="wn-icon-folder-o"
|
||||
data-control="popup"
|
||||
data-handler="<?= $this->getEventHandler('onLoadNewDirPopup') ?>"
|
||||
><?= e(trans('cms::lang.asset.create_directory')) ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="dropdown hide"
|
||||
id="<?= $this->getId('tools-button') ?>"
|
||||
data-trigger-action="show"
|
||||
data-trigger="<?= '#'.$this->getId('asset-list') ?> input[type=checkbox]"
|
||||
data-trigger-condition="checked">
|
||||
<button type="button" class="btn btn-default empty wn-icon-wrench last"
|
||||
data-toggle="dropdown"
|
||||
data-control="asset-tools"
|
||||
></button>
|
||||
|
||||
<ul class="dropdown-menu" role="menu" data-dropdown-title="<?= e(trans('cms::lang.asset.drop_down_operation_title')) ?>">
|
||||
<li role="presentation"><a
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
href="javascript:;"
|
||||
data-control="delete-asset"
|
||||
data-confirmation="<?= e(trans($this->deleteConfirmation)) ?>"
|
||||
class="wn-icon-trash-o"
|
||||
><?= e(trans('cms::lang.asset.delete')) ?></a></li>
|
||||
|
||||
<li role="presentation"><a
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
href="javascript:;"
|
||||
class="wn-icon-angle-double-right"
|
||||
data-control="popup"
|
||||
data-handler="<?= $this->getEventHandler('onLoadMovePopup') ?>"
|
||||
><?= e(trans('cms::lang.asset.move')) ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Asset Search -->
|
||||
<div class="relative toolbar-item loading-indicator-container size-input-text">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
value="<?= e($this->getSearchTerm()) ?>"
|
||||
class="form-control icon search" autocomplete="off"
|
||||
placeholder="<?= e(trans('cms::lang.sidebar.search')) ?>"
|
||||
data-track-input
|
||||
data-load-indicator
|
||||
data-load-indicator-opaque
|
||||
data-request-success="$('<?= '#'.$this->getId('tools-button') ?>').trigger('oc.triggerOn.update')"
|
||||
data-request="<?= $this->getEventHandler('onSearch') ?>"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
8
modules/cms/widgets/componentlist/partials/_body.php
Normal file
8
modules/cms/widgets/componentlist/partials/_body.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?= $this->makePartial('toolbar') ?>
|
||||
<div class="layout-row">
|
||||
<div class="layout-cell">
|
||||
<div class="layout-relative">
|
||||
<?= $this->makePartial('components', ['data'=>$data]) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="components subitems">
|
||||
<div class="layout">
|
||||
<div class="layout-row">
|
||||
<?php
|
||||
$count = count($components);
|
||||
?>
|
||||
<?php foreach ($components as $index => $component): ?>
|
||||
<?php if ($index > 0 && ($index % 2) == 0): ?>
|
||||
</div>
|
||||
|
||||
<?php if ($index == ($count - 1)): ?>
|
||||
</div>
|
||||
<div class="layout single">
|
||||
<?php endif ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?php endif ?>
|
||||
<div class="layout-cell" data-control="dragcomponent" data-component>
|
||||
<div class="layout-relative">
|
||||
<span class="name"><?= e($component->title) ?></span>
|
||||
<span class="description"><?= e($component->description) ?></span>
|
||||
<span class="alias wn-icon-code"><?= e($component->alias) ?></span>
|
||||
|
||||
<input type="hidden" name="component_properties[]" data-inspector-values value="<?= e($component->propertyValues) ?>">
|
||||
<input type="hidden" data-inspector-config value="<?= e($component->propertyConfig) ?>">
|
||||
<input type="hidden" data-inspector-class value="<?= $component->className ?>">
|
||||
<input type="hidden" data-component-icon value="<?= 'wn-'.e($component->pluginIcon) ?>">
|
||||
<input type="hidden" data-component-default-alias value="<?= e($component->alias) ?>">
|
||||
<input type="hidden" data-component-name value="<?= e($component->name) ?>">
|
||||
<input type="hidden" name="component_names[]" value="">
|
||||
<input type="hidden" name="component_aliases[]" value="">
|
||||
|
||||
<a href="#" class="remove">×</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
11
modules/cms/widgets/componentlist/partials/_components.php
Normal file
11
modules/cms/widgets/componentlist/partials/_components.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="layout-absolute">
|
||||
<div class="control-scrollbar" data-control="scrollbar">
|
||||
<div
|
||||
class="control-filelist component-list"
|
||||
data-control="filelist"
|
||||
data-group-status-handler="<?= $this->getEventHandler('onSetCollapseStatus') ?>"
|
||||
id="<?= $this->getId('component-list') ?>">
|
||||
<?= $this->makePartial('items', ['items'=>$data]) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
16
modules/cms/widgets/componentlist/partials/_items.php
Normal file
16
modules/cms/widgets/componentlist/partials/_items.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php if ($items): ?>
|
||||
<ul>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<li class="group" data-status="<?= $this->getCollapseStatus($item->pluginClass, false) ? 'expanded' : 'collapsed' ?>" data-group-id="<?= e($item->pluginClass) ?>">
|
||||
<div class="group">
|
||||
<h4><a href="#"><?= e(trans($item->title)) ?></a></h4>
|
||||
<i class="<?= e($item->icon) ?>"></i>
|
||||
<span class="description"><?= e(trans($item->description)) ?></span>
|
||||
</div>
|
||||
<?= $this->makePartial('component_list', ['components'=>$item->items]) ?>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="no-data"><?= e(trans('cms::lang.component.no_records')) ?></p>
|
||||
<?php endif ?>
|
||||
16
modules/cms/widgets/componentlist/partials/_toolbar.php
Normal file
16
modules/cms/widgets/componentlist/partials/_toolbar.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<div class="layout-row min-size">
|
||||
<div class="control-toolbar toolbar-padded">
|
||||
|
||||
<!-- Component Search -->
|
||||
<div class="relative toolbar-item loading-indicator-container size-input-text">
|
||||
<input placeholder="<?= e(trans('cms::lang.sidebar.search')) ?>" type="text" name="search" value="<?= e($this->getSearchTerm()) ?>"
|
||||
class="form-control icon search" autocomplete="off"
|
||||
data-track-input
|
||||
data-load-indicator
|
||||
data-load-indicator-opaque
|
||||
data-request="<?= $this->getEventHandler('onSearch') ?>"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
8
modules/cms/widgets/templatelist/partials/_body.php
Normal file
8
modules/cms/widgets/templatelist/partials/_body.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?= $this->makePartial('toolbar') ?>
|
||||
<div class="layout-row">
|
||||
<div class="layout-cell">
|
||||
<div class="layout-relative">
|
||||
<?= $this->makePartial('templates', ['data' => $data]) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
59
modules/cms/widgets/templatelist/partials/_items.php
Normal file
59
modules/cms/widgets/templatelist/partials/_items.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php if ($items): ?>
|
||||
<ul>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<?php if (property_exists($item, 'items')): ?>
|
||||
<?php if ($item->items): ?>
|
||||
<li class="group" data-status="<?= $this->getCollapseStatus($item->title, false) ? 'expanded' : 'collapsed' ?>">
|
||||
<h4><a href="javascript:;"><?= e($item->title) ?></a></h4>
|
||||
<?= $this->makePartial('items', ['items'=>$item->items, 'nested'=>true]) ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$dataId = $this->itemType.'-'.$this->theme->getDirName().'-'.$item->fileName;
|
||||
?>
|
||||
<li
|
||||
class="item"
|
||||
data-item-path="<?= e($item->fileName) ?>"
|
||||
data-item-theme="<?= e($this->theme->getDirName()) ?>"
|
||||
data-item-type="<?= $this->itemType ?>"
|
||||
data-id="<?= e($dataId) ?>">
|
||||
<a href="javascript:;"
|
||||
data-control="dragvalue"
|
||||
data-text-value="<?= $item->dragValue ?>">
|
||||
<span class="title"><?= e($item->title) ?></span>
|
||||
<span class="description" title="<?= e($item->description) ?>">
|
||||
<?php foreach ($item->descriptions as $title => $value): ?>
|
||||
<?php if (strlen($value)): ?>
|
||||
<?= e($title) ?>: <strong><?= e($value) ?></strong>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
|
||||
<?= e($item->description) ?>
|
||||
</span>
|
||||
<span class="borders"></span>
|
||||
</a>
|
||||
|
||||
<input type="hidden" name="template[<?= e($item->fileName) ?>]" value="0" />
|
||||
<div class="checkbox custom-checkbox nolabel">
|
||||
<?php $cbId = 'cb' . md5($this->itemType . '/' . $item->fileName) ?>
|
||||
<input
|
||||
id="<?= $cbId ?>"
|
||||
type="checkbox"
|
||||
name="template[<?= e($item->fileName) ?>]"
|
||||
<?= $this->isItemSelected($item->fileName) ? 'checked' : null ?>
|
||||
data-request="<?= $this->getEventHandler('onSelect') ?>"
|
||||
value="1">
|
||||
<label for="<?= $cbId ?>">Select</label>
|
||||
</div>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="no-data"><?= e(trans($this->noRecordsMessage)) ?></p>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (!isset($nested)): ?>
|
||||
<input type="hidden" name="theme" value="<?= e($this->theme->getDirName()) ?>">
|
||||
<?php endif ?>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php foreach ($this->sortingProperties as $propertyName => $propertyTitle): ?>
|
||||
<li
|
||||
role="presentation"
|
||||
<?php if ($this->getSortingProperty() == $propertyName): ?>
|
||||
class="active"
|
||||
<?php endif ?>
|
||||
>
|
||||
<a role="menuitem" tabindex="-1" href="javascript:;" data-stripe-load-indicator data-request="<?= $this->getEventHandler('onApplySorting') ?>" data-request-data="sortProperty: '<?= e($propertyName) ?>'">
|
||||
<?= e(trans($propertyTitle)) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
11
modules/cms/widgets/templatelist/partials/_templates.php
Normal file
11
modules/cms/widgets/templatelist/partials/_templates.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="layout-absolute">
|
||||
<div class="control-scrollbar" data-control="scrollbar">
|
||||
<div
|
||||
class="control-filelist <?= $this->controlClass ?>"
|
||||
data-control="filelist"
|
||||
data-group-status-handler="<?= $this->getEventHandler('onSetCollapseStatus') ?>"
|
||||
id="<?= $this->getId('template-list') ?>">
|
||||
<?= $this->makePartial('items', ['items' => $data]) ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
51
modules/cms/widgets/templatelist/partials/_toolbar.php
Normal file
51
modules/cms/widgets/templatelist/partials/_toolbar.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<div class="layout-row min-size">
|
||||
<div class="control-toolbar toolbar-padded <?= $toolbarClass ?>">
|
||||
|
||||
<!-- Control Panel -->
|
||||
<div class="toolbar-item" data-calculate-width>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default wn-icon-plus <?= !$this->sortingProperties ? 'last' : null ?>"
|
||||
data-control="create-template"><?= e(trans('cms::lang.sidebar.add')) ?></button>
|
||||
|
||||
<?php if ($this->sortingProperties): ?>
|
||||
<div class="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default empty wn-icon-sort-alpha-asc"
|
||||
data-toggle="dropdown"></button>
|
||||
<ul
|
||||
class="dropdown-menu offset-left"
|
||||
data-dropdown-title="<?= e(trans('cms::lang.template.order_by')) ?>"
|
||||
id="<?= $this->getId('sorting-options') ?>"
|
||||
role="menu">
|
||||
<?= $this->makePartial('sorting-options') ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif?>
|
||||
|
||||
<button type="button" class="btn btn-danger empty wn-icon-trash-o hide"
|
||||
id="<?= $this->getId('delete-button') ?>"
|
||||
data-control="delete-template"
|
||||
data-confirmation="<?= e(trans($this->deleteConfirmation)) ?>"
|
||||
data-trigger-action="show"
|
||||
data-trigger="<?= '#'.$this->getId('template-list') ?> input[type=checkbox]"
|
||||
data-trigger-condition="checked"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Template Search -->
|
||||
<div class="relative toolbar-item loading-indicator-container size-input-text">
|
||||
<input placeholder="<?= e(trans('cms::lang.sidebar.search')) ?>" type="text" name="search" value="<?= e($this->getSearchTerm()) ?>"
|
||||
class="form-control icon search" autocomplete="off"
|
||||
data-track-input
|
||||
data-load-indicator
|
||||
data-load-indicator-opaque
|
||||
data-request-success="$('<?= '#'.$this->getId('delete-button') ?>').trigger('oc.triggerOn.update')"
|
||||
data-request="<?= $this->getEventHandler('onSearch') ?>"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user