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
250 lines
7.8 KiB
PHP
250 lines
7.8 KiB
PHP
<?php namespace Backend\Traits;
|
|
|
|
use ApplicationException;
|
|
use Event;
|
|
use File;
|
|
use Illuminate\Filesystem\FilesystemAdapter;
|
|
use Lang;
|
|
use Request;
|
|
use Response;
|
|
use Str;
|
|
use System\Classes\MediaLibrary;
|
|
use Winter\Storm\Filesystem\Definitions as FileDefinitions;
|
|
use Winter\Storm\Support\Svg;
|
|
|
|
/**
|
|
* Uploadable Widget Trait
|
|
* Adds media library upload features to back-end widgets
|
|
*
|
|
* @package winter\wn-backend-module
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
*/
|
|
trait UploadableWidget
|
|
{
|
|
// /**
|
|
// * @var string Path in the Media Library where uploaded files should be stored. If empty it will be pulled from Request::input('path');
|
|
// */
|
|
// public $uploadPath;
|
|
|
|
/**
|
|
* Returns the disk that will be used to store the uploaded file
|
|
*/
|
|
public function uploadableGetDisk(): FilesystemAdapter
|
|
{
|
|
return MediaLibrary::instance()->getStorageDisk();
|
|
}
|
|
|
|
/**
|
|
* Returns the path on the disk to store the uploaded file
|
|
*/
|
|
public function uploadableGetUploadPath(string $fileName): string
|
|
{
|
|
// Use the configured upload path unless it's null, in which case use the user-provided path
|
|
$path = !empty($this->uploadPath) ? $this->uploadPath : Request::input('path');
|
|
$path = MediaLibrary::validatePath($path);
|
|
$path = MediaLibrary::instance()->getMediaPath($path);
|
|
$filePath = rtrim($path, '/') . '/' . $fileName;
|
|
return $filePath;
|
|
}
|
|
|
|
/**
|
|
* Returns the URL to the uploaded file
|
|
*
|
|
* @TODO: Replace cms.storage system with real disks
|
|
*/
|
|
public function uploadableGetUploadUrl(string $diskPath): string
|
|
{
|
|
// Get the media folder
|
|
$storageFolder = MediaLibrary::instance()->getMediaPath('');
|
|
|
|
// Remove the media folder from the provided disk path since it already has it
|
|
$url = MediaLibrary::url(Str::after($diskPath, $storageFolder));
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Process file uploads submitted via AJAX
|
|
*
|
|
* @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(): ?\Illuminate\Http\Response
|
|
{
|
|
if ($this->readOnly) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @event backend.widgets.uploadable.onUpload
|
|
* Provides an opportunity to process the file upload using custom logic.
|
|
*
|
|
* Example usage ()
|
|
*/
|
|
if ($result = Event::fire('backend.widgets.uploadable.onUpload', [$this], true)) {
|
|
return $result;
|
|
}
|
|
|
|
return $this->onUploadDirect();
|
|
}
|
|
|
|
protected function onUploadDirect(): \Illuminate\Http\Response
|
|
{
|
|
if (!Request::hasFile('file_data')) {
|
|
throw new ApplicationException('File missing from request');
|
|
}
|
|
|
|
try {
|
|
$uploadedFile = Request::file('file_data');
|
|
|
|
$fileName = $this->validateMediaFileName(
|
|
$uploadedFile->getClientOriginalName(),
|
|
$uploadedFile->getClientOriginalExtension()
|
|
);
|
|
|
|
/*
|
|
* See mime type handling in the asset manager
|
|
*/
|
|
if (!$uploadedFile->isValid()) {
|
|
if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
|
|
$message = "The file \"{$uploadedFile->getClientOriginalName()}\" uploaded successfully but wasn't "
|
|
. "available at {$uploadedFile->getPathName()}. Check to make sure that nothing moved it away.";
|
|
} else {
|
|
$message = $uploadedFile->getErrorMessage();
|
|
}
|
|
throw new ApplicationException($message);
|
|
}
|
|
|
|
/*
|
|
* getRealPath() can be empty for some environments (IIS)
|
|
*/
|
|
$sourcePath = empty(trim($uploadedFile->getRealPath()))
|
|
? $uploadedFile->getPath() . DIRECTORY_SEPARATOR . $uploadedFile->getFileName()
|
|
: $uploadedFile->getRealPath();
|
|
|
|
$filePath = $this->uploadableGetUploadPath($fileName);
|
|
|
|
// Filter SVG files
|
|
if (pathinfo($filePath, PATHINFO_EXTENSION) === 'svg') {
|
|
file_put_contents($sourcePath, Svg::extract($sourcePath));
|
|
}
|
|
|
|
$this->uploadableGetDisk()->put($filePath, File::get($sourcePath));
|
|
|
|
/**
|
|
* @event media.file.upload
|
|
* Called after a file is uploaded
|
|
*
|
|
* Example usage:
|
|
*
|
|
* Event::listen('media.file.upload', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) &$path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) {
|
|
* \Log::info($path . " was upoaded.");
|
|
* });
|
|
*
|
|
* Or
|
|
*
|
|
* $mediaWidget->bindEvent('file.upload', function ((string) &$path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) {
|
|
* \Log::info($path . " was uploaded");
|
|
* });
|
|
*
|
|
*/
|
|
$this->fireSystemEvent('media.file.upload', [&$filePath, $uploadedFile]);
|
|
|
|
$response = Response::make([
|
|
'link' => $this->uploadableGetUploadUrl($filePath),
|
|
'result' => 'success'
|
|
]);
|
|
} catch (\Exception $ex) {
|
|
throw new ApplicationException($ex->getMessage());
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
public function validateMediaFileName(string $fileName, string $extension): string
|
|
{
|
|
/*
|
|
* Convert uppcare case file extensions to lower case
|
|
*/
|
|
$extension = strtolower($extension);
|
|
$fileName = File::name($fileName).'.'.$extension;
|
|
|
|
/*
|
|
* File name contains non-latin characters, attempt to slug the value
|
|
*/
|
|
if (!$this->validateFileName($fileName)) {
|
|
$fileName = $this->cleanFileName(File::name($fileName)) . '.' . $extension;
|
|
}
|
|
|
|
/*
|
|
* Check for unsafe file extensions
|
|
*/
|
|
if (!$this->validateFileType($fileName)) {
|
|
throw new ApplicationException(Lang::get('backend::lang.media.type_blocked'));
|
|
}
|
|
|
|
return $fileName;
|
|
}
|
|
|
|
/**
|
|
* Validate a proposed media item file name.
|
|
*
|
|
* @param string
|
|
* @return bool
|
|
*/
|
|
protected function validateFileName($name): bool
|
|
{
|
|
if (!preg_match('/^[\w@\.\s_\-]+$/iu', $name)) {
|
|
return false;
|
|
}
|
|
|
|
if (strpos($name, '..') !== false) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check for blocked / unsafe file extensions
|
|
*
|
|
* @param string
|
|
* @return bool
|
|
*/
|
|
protected function validateFileType($name): bool
|
|
{
|
|
$extension = strtolower(File::extension($name));
|
|
|
|
$allowedFileTypes = FileDefinitions::get('defaultExtensions');
|
|
|
|
if (!in_array($extension, $allowedFileTypes)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates a slug form the string. A modified version of Str::slug
|
|
* with the main difference that it accepts @-signs
|
|
*
|
|
* @param string $name
|
|
* @return string
|
|
*/
|
|
protected function cleanFileName($name)
|
|
{
|
|
$title = Str::ascii($name);
|
|
|
|
// Convert all dashes/underscores into separator
|
|
$flip = $separator = '-';
|
|
$title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
|
|
|
|
// Remove all characters that are not the separator, letters, numbers, whitespace or @.
|
|
$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s@]+!u', '', mb_strtolower($title));
|
|
|
|
// Replace all separator characters and whitespace by a single separator
|
|
$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
|
|
|
|
return trim($title, $separator);
|
|
}
|
|
}
|