first commit
This commit is contained in:
63
plugins/filesystem/local/local.xml
Normal file
63
plugins/filesystem/local/local.xml
Normal file
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="filesystem" method="upgrade">
|
||||
<name>plg_filesystem_local</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2017-04</creationDate>
|
||||
<copyright>(C) 2017 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<version>4.0.0</version>
|
||||
<description>PLG_FILESYSTEM_LOCAL_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\Filesystem\Local</namespace>
|
||||
<files>
|
||||
<folder plugin="local">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_filesystem_local.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_filesystem_local.sys.ini</language>
|
||||
</languages>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="directories"
|
||||
type="subform"
|
||||
label="PLG_FILESYSTEM_LOCAL_DIRECTORIES_LABEL"
|
||||
multiple="true"
|
||||
layout="joomla.form.field.subform.repeatable-table"
|
||||
buttons="add,remove,move"
|
||||
default='[{"directory":"images"}]'
|
||||
>
|
||||
<form>
|
||||
<field
|
||||
name="directory"
|
||||
type="folderlist"
|
||||
default="images"
|
||||
label="PLG_FILESYSTEM_LOCAL_DIRECTORIES_DIRECTORY_LABEL"
|
||||
folderFilter=""
|
||||
exclude=""
|
||||
stripext=""
|
||||
hide_none="true"
|
||||
validate="options"
|
||||
/>
|
||||
<field
|
||||
name="thumbs"
|
||||
type="radio"
|
||||
label="PLG_FILESYSTEM_LOCAL_DIRECTORIES_DIRECTORY_THUMBNAILS_LABEL"
|
||||
layout="joomla.form.field.radio.switcher"
|
||||
default="0"
|
||||
filter="integer"
|
||||
>
|
||||
<option value="0">JNO</option>
|
||||
<option value="1">JYES</option>
|
||||
</field>
|
||||
</form>
|
||||
</field>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
47
plugins/filesystem/local/services/provider.php
Normal file
47
plugins/filesystem/local/services/provider.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Filesystem.local
|
||||
*
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
\defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\Filesystem\Local\Extension\Local;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$plugin = new Local(
|
||||
$container->get(DispatcherInterface::class),
|
||||
(array) PluginHelper::getPlugin('filesystem', 'local'),
|
||||
JPATH_ROOT
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
965
plugins/filesystem/local/src/Adapter/LocalAdapter.php
Normal file
965
plugins/filesystem/local/src/Adapter/LocalAdapter.php
Normal file
@ -0,0 +1,965 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage Filesystem.local
|
||||
*
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Filesystem\Local\Adapter;
|
||||
|
||||
use Joomla\CMS\Date\Date;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Filesystem\File;
|
||||
use Joomla\CMS\Filesystem\Folder;
|
||||
use Joomla\CMS\Helper\MediaHelper;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Image\Exception\UnparsableImageException;
|
||||
use Joomla\CMS\Image\Image;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\String\PunycodeHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\CMS\User\CurrentUserTrait;
|
||||
use Joomla\Component\Media\Administrator\Adapter\AdapterInterface;
|
||||
use Joomla\Component\Media\Administrator\Exception\FileNotFoundException;
|
||||
use Joomla\Component\Media\Administrator\Exception\InvalidPathException;
|
||||
use Joomla\Filesystem\Path;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Local file adapter.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class LocalAdapter implements AdapterInterface
|
||||
{
|
||||
use CurrentUserTrait;
|
||||
|
||||
/**
|
||||
* The root path to gather file information from.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $rootPath = null;
|
||||
|
||||
/**
|
||||
* The file_path of media directory related to site
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private $filePath = null;
|
||||
|
||||
/**
|
||||
* Should the adapter create a thumbnail for the image?
|
||||
*
|
||||
* @var boolean
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private $thumbnails = false;
|
||||
|
||||
/**
|
||||
* Thumbnail dimensions in pixels, [0] = width, [1] = height
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private $thumbnailSize = [200, 200];
|
||||
|
||||
/**
|
||||
* The absolute root path in the local file system.
|
||||
*
|
||||
* @param string $rootPath The root path
|
||||
* @param string $filePath The file path of media folder
|
||||
* @param boolean $thumbnails The thumbnails option
|
||||
* @param array $thumbnailSize The thumbnail dimensions in pixels
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function __construct(string $rootPath, string $filePath, bool $thumbnails = false, array $thumbnailSize = [200, 200])
|
||||
{
|
||||
if (!file_exists($rootPath)) {
|
||||
throw new \InvalidArgumentException(Text::_('COM_MEDIA_ERROR_MISSING_DIR'));
|
||||
}
|
||||
|
||||
$this->rootPath = Path::clean(realpath($rootPath), '/');
|
||||
$this->filePath = $filePath;
|
||||
$this->thumbnails = $thumbnails;
|
||||
$this->thumbnailSize = $thumbnailSize;
|
||||
|
||||
if ($this->thumbnails) {
|
||||
$dir = JPATH_ROOT . '/media/cache/com_media/thumbs/' . $this->filePath;
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
Folder::create($dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested file or folder. The returned object
|
||||
* has the following properties available:
|
||||
* - type: The type can be file or dir
|
||||
* - name: The name of the file
|
||||
* - path: The relative path to the root
|
||||
* - extension: The file extension
|
||||
* - size: The size of the file
|
||||
* - create_date: The date created
|
||||
* - modified_date: The date modified
|
||||
* - mime_type: The mime type
|
||||
* - width: The width, when available
|
||||
* - height: The height, when available
|
||||
*
|
||||
* If the path doesn't exist a FileNotFoundException is thrown.
|
||||
*
|
||||
* @param string $path The path to the file or folder
|
||||
*
|
||||
* @return \stdClass
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getFile(string $path = '/'): \stdClass
|
||||
{
|
||||
// Get the local path
|
||||
$basePath = $this->getLocalPath($path);
|
||||
|
||||
// Check if file exists
|
||||
if (!file_exists($basePath)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
return $this->getPathInformation($basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the folders and files for the given path. The returned objects
|
||||
* have the following properties available:
|
||||
* - type: The type can be file or dir
|
||||
* - name: The name of the file
|
||||
* - path: The relative path to the root
|
||||
* - extension: The file extension
|
||||
* - size: The size of the file
|
||||
* - create_date: The date created
|
||||
* - modified_date: The date modified
|
||||
* - mime_type: The mime type
|
||||
* - width: The width, when available
|
||||
* - height: The height, when available
|
||||
*
|
||||
* If the path doesn't exist a FileNotFoundException is thrown.
|
||||
*
|
||||
* @param string $path The folder
|
||||
*
|
||||
* @return \stdClass[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getFiles(string $path = '/'): array
|
||||
{
|
||||
// Get the local path
|
||||
$basePath = $this->getLocalPath($path);
|
||||
|
||||
// Check if file exists
|
||||
if (!file_exists($basePath)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
// Check if the path points to a file
|
||||
if (is_file($basePath)) {
|
||||
return [$this->getPathInformation($basePath)];
|
||||
}
|
||||
|
||||
// The data to return
|
||||
$data = [];
|
||||
|
||||
// Read the folders
|
||||
foreach (Folder::folders($basePath) as $folder) {
|
||||
$data[] = $this->getPathInformation(Path::clean($basePath . '/' . $folder));
|
||||
}
|
||||
|
||||
// Read the files
|
||||
foreach (Folder::files($basePath) as $file) {
|
||||
$data[] = $this->getPathInformation(Path::clean($basePath . '/' . $file));
|
||||
}
|
||||
|
||||
// Return the data
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a resource to download the path.
|
||||
*
|
||||
* @param string $path The path to download
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getResource(string $path)
|
||||
{
|
||||
return fopen($this->rootPath . '/' . $path, 'r');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a folder with the given name in the given path.
|
||||
*
|
||||
* It returns the new folder name. This allows the implementation
|
||||
* classes to normalise the file name.
|
||||
*
|
||||
* @param string $name The name
|
||||
* @param string $path The folder
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createFolder(string $name, string $path): string
|
||||
{
|
||||
$name = $this->getSafeName($name);
|
||||
|
||||
$localPath = $this->getLocalPath($path . '/' . $name);
|
||||
|
||||
Folder::create($localPath);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file with the given name in the given path with the data.
|
||||
*
|
||||
* It returns the new file name. This allows the implementation
|
||||
* classes to normalise the file name.
|
||||
*
|
||||
* @param string $name The name
|
||||
* @param string $path The folder
|
||||
* @param string $data The data
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createFile(string $name, string $path, $data): string
|
||||
{
|
||||
$name = $this->getSafeName($name);
|
||||
$localPath = $this->getLocalPath($path . '/' . $name);
|
||||
|
||||
$this->checkContent($localPath, $data);
|
||||
|
||||
File::write($localPath, $data);
|
||||
|
||||
if ($this->thumbnails && MediaHelper::isImage(pathinfo($localPath)['basename'])) {
|
||||
$thumbnailPaths = $this->getLocalThumbnailPaths($localPath);
|
||||
|
||||
if (empty($thumbnailPaths)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Create the thumbnail
|
||||
$this->createThumbnail($localPath, $thumbnailPaths['fs']);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file with the given name in the given path with the data.
|
||||
*
|
||||
* @param string $name The name
|
||||
* @param string $path The folder
|
||||
* @param string $data The data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateFile(string $name, string $path, $data)
|
||||
{
|
||||
$localPath = $this->getLocalPath($path . '/' . $name);
|
||||
|
||||
if (!is_file($localPath)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
$this->checkContent($localPath, $data);
|
||||
|
||||
File::write($localPath, $data);
|
||||
|
||||
if ($this->thumbnails && MediaHelper::isImage(pathinfo($localPath)['basename'])) {
|
||||
$thumbnailPaths = $this->getLocalThumbnailPaths($localPath);
|
||||
|
||||
if (empty($thumbnailPaths['fs'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the thumbnail
|
||||
$this->createThumbnail($localPath, $thumbnailPaths['fs']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the folder or file of the given path.
|
||||
*
|
||||
* @param string $path The path to the file or folder
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete(string $path)
|
||||
{
|
||||
$localPath = $this->getLocalPath($path);
|
||||
$thumbnailPaths = $this->getLocalThumbnailPaths($localPath);
|
||||
|
||||
if (is_file($localPath)) {
|
||||
if ($this->thumbnails && !empty($thumbnailPaths['fs']) && is_file($thumbnailPaths['fs'])) {
|
||||
File::delete($thumbnailPaths['fs']);
|
||||
}
|
||||
|
||||
$success = File::delete($localPath);
|
||||
} else {
|
||||
if (!Folder::exists($localPath)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
$success = Folder::delete($localPath);
|
||||
|
||||
if ($this->thumbnails && !empty($thumbnailPaths['fs']) && is_dir($thumbnailPaths['fs'])) {
|
||||
Folder::delete($thumbnailPaths['fs']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new \Exception('Delete not possible!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the folder or file information for the given path. The returned object
|
||||
* has the following properties:
|
||||
* - type: The type can be file or dir
|
||||
* - name: The name of the file
|
||||
* - path: The relative path to the root
|
||||
* - extension: The file extension
|
||||
* - size: The size of the file
|
||||
* - create_date: The date created
|
||||
* - modified_date: The date modified
|
||||
* - mime_type: The mime type
|
||||
* - width: The width, when available
|
||||
* - height: The height, when available
|
||||
* - thumb_path: The thumbnail path of file, when available
|
||||
*
|
||||
* @param string $path The folder
|
||||
*
|
||||
* @return \stdClass
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getPathInformation(string $path): \stdClass
|
||||
{
|
||||
// Prepare the path
|
||||
$path = Path::clean($path, '/');
|
||||
|
||||
// The boolean if it is a dir
|
||||
$isDir = is_dir($path);
|
||||
|
||||
$createDate = $this->getDate(filectime($path));
|
||||
$modifiedDate = $this->getDate(filemtime($path));
|
||||
|
||||
// Set the values
|
||||
$obj = new \stdClass();
|
||||
$obj->type = $isDir ? 'dir' : 'file';
|
||||
$obj->name = $this->getFileName($path);
|
||||
$obj->path = str_replace($this->rootPath, '', $path);
|
||||
$obj->extension = !$isDir ? File::getExt($obj->name) : '';
|
||||
$obj->size = !$isDir ? filesize($path) : '';
|
||||
$obj->mime_type = MediaHelper::getMimeType($path, MediaHelper::isImage($obj->name));
|
||||
$obj->width = 0;
|
||||
$obj->height = 0;
|
||||
|
||||
// Dates
|
||||
$obj->create_date = $createDate->format('c', true);
|
||||
$obj->create_date_formatted = HTMLHelper::_('date', $createDate, Text::_('DATE_FORMAT_LC5'));
|
||||
$obj->modified_date = $modifiedDate->format('c', true);
|
||||
$obj->modified_date_formatted = HTMLHelper::_('date', $modifiedDate, Text::_('DATE_FORMAT_LC5'));
|
||||
|
||||
if ($obj->mime_type === 'image/svg+xml' && $obj->extension === 'svg') {
|
||||
$obj->thumb_path = $this->getUrl($obj->path);
|
||||
return $obj;
|
||||
}
|
||||
|
||||
if (MediaHelper::isImage($obj->name)) {
|
||||
// Get the image properties
|
||||
try {
|
||||
$props = Image::getImageFileProperties($path);
|
||||
$obj->width = $props->width;
|
||||
$obj->height = $props->height;
|
||||
|
||||
$obj->thumb_path = $this->thumbnails ? $this->getThumbnail($path) : $this->getUrl($obj->path);
|
||||
} catch (UnparsableImageException $e) {
|
||||
// Ignore the exception - it's an image that we don't know how to parse right now
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Date with the correct Joomla timezone for the given date.
|
||||
*
|
||||
* @param string $date The date to create a Date from
|
||||
*
|
||||
* @return Date
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function getDate($date = null): Date
|
||||
{
|
||||
$dateObj = Factory::getDate($date);
|
||||
|
||||
$timezone = Factory::getApplication()->get('offset');
|
||||
$user = $this->getCurrentUser();
|
||||
|
||||
if ($user->id) {
|
||||
$userTimezone = $user->getParam('timezone');
|
||||
|
||||
if (!empty($userTimezone)) {
|
||||
$timezone = $userTimezone;
|
||||
}
|
||||
}
|
||||
|
||||
if ($timezone) {
|
||||
$dateObj->setTimezone(new \DateTimeZone($timezone));
|
||||
}
|
||||
|
||||
return $dateObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file or folder from source to destination.
|
||||
*
|
||||
* It returns the new destination path. This allows the implementation
|
||||
* classes to normalise the file name.
|
||||
*
|
||||
* @param string $sourcePath The source path
|
||||
* @param string $destinationPath The destination path
|
||||
* @param bool $force Force to overwrite
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function copy(string $sourcePath, string $destinationPath, bool $force = false): string
|
||||
{
|
||||
// Get absolute paths from relative paths
|
||||
$sourcePath = Path::clean($this->getLocalPath($sourcePath), '/');
|
||||
$destinationPath = Path::clean($this->getLocalPath($destinationPath), '/');
|
||||
|
||||
if (!file_exists($sourcePath)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
$name = $this->getFileName($destinationPath);
|
||||
$safeName = $this->getSafeName($name);
|
||||
|
||||
// If the safe name is different normalise the file name
|
||||
if ($safeName != $name) {
|
||||
$destinationPath = substr($destinationPath, 0, -\strlen($name)) . '/' . $safeName;
|
||||
}
|
||||
|
||||
// Check for existence of the file in destination
|
||||
// if it does not exists simply copy source to destination
|
||||
if (is_dir($sourcePath)) {
|
||||
$this->copyFolder($sourcePath, $destinationPath, $force);
|
||||
} else {
|
||||
$this->copyFile($sourcePath, $destinationPath, $force);
|
||||
}
|
||||
|
||||
// Get the relative path
|
||||
$destinationPath = str_replace($this->rootPath, '', $destinationPath);
|
||||
|
||||
return $destinationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a file
|
||||
*
|
||||
* @param string $sourcePath Source path of the file or directory
|
||||
* @param string $destinationPath Destination path of the file or directory
|
||||
* @param bool $force Set true to overwrite files or directories
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function copyFile(string $sourcePath, string $destinationPath, bool $force = false)
|
||||
{
|
||||
if (is_dir($destinationPath)) {
|
||||
// If the destination is a folder we create a file with the same name as the source
|
||||
$destinationPath .= '/' . $this->getFileName($sourcePath);
|
||||
}
|
||||
|
||||
if (file_exists($destinationPath) && !$force) {
|
||||
throw new \Exception('Copy file is not possible as destination file already exists');
|
||||
}
|
||||
|
||||
if (!File::copy($sourcePath, $destinationPath)) {
|
||||
throw new \Exception('Copy file is not possible');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a folder
|
||||
*
|
||||
* @param string $sourcePath Source path of the file or directory
|
||||
* @param string $destinationPath Destination path of the file or directory
|
||||
* @param bool $force Set true to overwrite files or directories
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function copyFolder(string $sourcePath, string $destinationPath, bool $force = false)
|
||||
{
|
||||
if (file_exists($destinationPath) && !$force) {
|
||||
throw new \Exception('Copy folder is not possible as destination folder already exists');
|
||||
}
|
||||
|
||||
if (is_file($destinationPath) && !File::delete($destinationPath)) {
|
||||
throw new \Exception('Copy folder is not possible as destination folder is a file and can not be deleted');
|
||||
}
|
||||
|
||||
if (!Folder::copy($sourcePath, $destinationPath, '', $force)) {
|
||||
throw new \Exception('Copy folder is not possible');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a file or folder from source to destination.
|
||||
*
|
||||
* It returns the new destination path. This allows the implementation
|
||||
* classes to normalise the file name.
|
||||
*
|
||||
* @param string $sourcePath The source path
|
||||
* @param string $destinationPath The destination path
|
||||
* @param bool $force Force to overwrite
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function move(string $sourcePath, string $destinationPath, bool $force = false): string
|
||||
{
|
||||
// Get absolute paths from relative paths
|
||||
$sourcePath = Path::clean($this->getLocalPath($sourcePath), '/');
|
||||
$destinationPath = Path::clean($this->getLocalPath($destinationPath), '/');
|
||||
|
||||
if (!file_exists($sourcePath)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
$name = $this->getFileName($destinationPath);
|
||||
$safeName = $this->getSafeName($name);
|
||||
|
||||
// If transliterating could not happen, and all characters except of the file extension are filtered out, then throw an error.
|
||||
if ($safeName === pathinfo($sourcePath, PATHINFO_EXTENSION)) {
|
||||
throw new \Exception(Text::_('COM_MEDIA_ERROR_MAKESAFE'));
|
||||
}
|
||||
|
||||
// If the safe name is different normalise the file name
|
||||
if ($safeName != $name) {
|
||||
$destinationPath = substr($destinationPath, 0, -\strlen($name)) . $safeName;
|
||||
}
|
||||
|
||||
if (is_dir($sourcePath)) {
|
||||
$this->moveFolder($sourcePath, $destinationPath, $force);
|
||||
} else {
|
||||
$this->moveFile($sourcePath, $destinationPath, $force);
|
||||
}
|
||||
|
||||
// Get the relative path
|
||||
$destinationPath = str_replace($this->rootPath, '', $destinationPath);
|
||||
|
||||
return $destinationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a file
|
||||
*
|
||||
* @param string $sourcePath Absolute path of source
|
||||
* @param string $destinationPath Absolute path of destination
|
||||
* @param bool $force Set true to overwrite file if exists
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function moveFile(string $sourcePath, string $destinationPath, bool $force = false)
|
||||
{
|
||||
if (is_dir($destinationPath)) {
|
||||
// If the destination is a folder we create a file with the same name as the source
|
||||
$destinationPath .= '/' . $this->getFileName($sourcePath);
|
||||
}
|
||||
|
||||
if (!MediaHelper::checkFileExtension(pathinfo($destinationPath, PATHINFO_EXTENSION))) {
|
||||
throw new \Exception('Move file is not possible as the extension is invalid');
|
||||
}
|
||||
|
||||
if (file_exists($destinationPath) && !$force) {
|
||||
throw new \Exception('Move file is not possible as destination file already exists');
|
||||
}
|
||||
|
||||
if (!File::move($sourcePath, $destinationPath)) {
|
||||
throw new \Exception('Move file is not possible');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a folder from source to destination
|
||||
*
|
||||
* @param string $sourcePath Source path of the file or directory
|
||||
* @param string $destinationPath Destination path of the file or directory
|
||||
* @param bool $force Set true to overwrite files or directories
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function moveFolder(string $sourcePath, string $destinationPath, bool $force = false)
|
||||
{
|
||||
if (file_exists($destinationPath) && !$force) {
|
||||
throw new \Exception('Move folder is not possible as destination folder already exists');
|
||||
}
|
||||
|
||||
if (is_file($destinationPath) && !File::delete($destinationPath)) {
|
||||
throw new \Exception('Move folder is not possible as destination folder is a file and can not be deleted');
|
||||
}
|
||||
|
||||
if (is_dir($destinationPath)) {
|
||||
// We need to bypass exception thrown in JFolder when destination exists
|
||||
// So we only copy it in forced condition, then delete the source to simulate a move
|
||||
if (!Folder::copy($sourcePath, $destinationPath, '', true)) {
|
||||
throw new \Exception('Move folder to an existing destination failed');
|
||||
}
|
||||
|
||||
// Delete the source
|
||||
Folder::delete($sourcePath);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform usual moves
|
||||
$value = Folder::move($sourcePath, $destinationPath);
|
||||
|
||||
if ($value !== true) {
|
||||
throw new \Exception($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a url which can be used to display an image from within the "images" directory.
|
||||
*
|
||||
* @param string $path Path of the file relative to adapter
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getUrl(string $path): string
|
||||
{
|
||||
return Uri::root() . $this->getEncodedPath($this->filePath . $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this adapter.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getAdapterName(): string
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a pattern in a given path
|
||||
*
|
||||
* @param string $path The base path for the search
|
||||
* @param string $needle The path to file
|
||||
* @param bool $recursive Do a recursive search
|
||||
*
|
||||
* @return \stdClass[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function search(string $path, string $needle, bool $recursive = false): array
|
||||
{
|
||||
$pattern = Path::clean($this->getLocalPath($path) . '/*' . $needle . '*');
|
||||
|
||||
if ($recursive) {
|
||||
$results = $this->rglob($pattern);
|
||||
} else {
|
||||
$results = glob($pattern);
|
||||
}
|
||||
|
||||
$searchResults = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
$searchResults[] = $this->getPathInformation($result);
|
||||
}
|
||||
|
||||
return $searchResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a recursive search on a given path
|
||||
*
|
||||
* @param string $pattern The pattern for search
|
||||
* @param int $flags Flags for search
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
private function rglob(string $pattern, int $flags = 0): array
|
||||
{
|
||||
$files = glob($pattern, $flags);
|
||||
|
||||
foreach (glob(\dirname($pattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
|
||||
$files = array_merge($files, $this->rglob($dir . '/' . $this->getFileName($pattern), $flags));
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace spaces on a path with %20
|
||||
*
|
||||
* @param string $path The Path to be encoded
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
private function getEncodedPath(string $path): string
|
||||
{
|
||||
return str_replace(" ", "%20", $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a safe file name for the given name.
|
||||
*
|
||||
* @param string $name The filename
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getSafeName(string $name): string
|
||||
{
|
||||
// Make the filename safe
|
||||
if (!$name = File::makeSafe($name)) {
|
||||
throw new \Exception(Text::_('COM_MEDIA_ERROR_MAKESAFE'));
|
||||
}
|
||||
|
||||
// Transform filename to punycode
|
||||
$name = PunycodeHelper::toPunycode($name);
|
||||
|
||||
// Get the extension
|
||||
$extension = File::getExt($name);
|
||||
|
||||
// Normalise extension, always lower case
|
||||
if ($extension) {
|
||||
$extension = '.' . strtolower($extension);
|
||||
}
|
||||
|
||||
$nameWithoutExtension = substr($name, 0, \strlen($name) - \strlen($extension));
|
||||
|
||||
return $nameWithoutExtension . $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs various check if it is allowed to save the content with the given name.
|
||||
*
|
||||
* @param string $localPath The local path
|
||||
* @param string $mediaContent The media content
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function checkContent(string $localPath, string $mediaContent)
|
||||
{
|
||||
$name = $this->getFileName($localPath);
|
||||
|
||||
// The helper
|
||||
$helper = new MediaHelper();
|
||||
|
||||
// @todo find a better way to check the input, by not writing the file to the disk
|
||||
$tmpFile = Path::clean(\dirname($localPath) . '/' . uniqid() . '.' . File::getExt($name));
|
||||
|
||||
if (!File::write($tmpFile, $mediaContent)) {
|
||||
throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'), 500);
|
||||
}
|
||||
|
||||
$can = $helper->canUpload(['name' => $name, 'size' => \strlen($mediaContent), 'tmp_name' => $tmpFile], 'com_media');
|
||||
|
||||
File::delete($tmpFile);
|
||||
|
||||
if (!$can) {
|
||||
throw new \Exception(Text::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'), 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file name of the given path.
|
||||
*
|
||||
* @param string $path The path
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getFileName(string $path): string
|
||||
{
|
||||
$path = Path::clean($path);
|
||||
|
||||
// Basename does not work here as it strips out certain characters like upper case umlaut u
|
||||
$path = explode(DIRECTORY_SEPARATOR, $path);
|
||||
|
||||
// Return the last element
|
||||
return array_pop($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local filesystem path for the given path.
|
||||
*
|
||||
* Throws an InvalidPathException if the path is invalid.
|
||||
*
|
||||
* @param string $path The path
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @throws InvalidPathException
|
||||
*/
|
||||
private function getLocalPath(string $path): string
|
||||
{
|
||||
try {
|
||||
return Path::check($this->rootPath . '/' . $path);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidPathException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local filesystem thumbnail path for the given path.
|
||||
*
|
||||
* Throws an InvalidPathException if the path is invalid.
|
||||
*
|
||||
* @param string $path The path
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 4.3.0
|
||||
* @throws InvalidPathException
|
||||
*/
|
||||
private function getLocalThumbnailPaths(string $path): array
|
||||
{
|
||||
$rootPath = str_replace(['\\', '/'], '/', $this->rootPath);
|
||||
$path = str_replace(['\\', '/'], '/', $path);
|
||||
|
||||
try {
|
||||
$fs = Path::check(str_replace($rootPath, JPATH_ROOT . '/media/cache/com_media/thumbs/' . $this->filePath, $path));
|
||||
$url = str_replace($rootPath, 'media/cache/com_media/thumbs/' . $this->filePath, $path);
|
||||
|
||||
return [
|
||||
'fs' => $fs,
|
||||
'url' => $url,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidPathException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path for the thumbnail of the given image.
|
||||
* If the thumbnail does not exist, it will be created.
|
||||
*
|
||||
* @param string $path The path of the image
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private function getThumbnail(string $path): string
|
||||
{
|
||||
$thumbnailPaths = $this->getLocalThumbnailPaths($path);
|
||||
|
||||
if (empty($thumbnailPaths['fs'])) {
|
||||
return $this->getUrl($path);
|
||||
}
|
||||
|
||||
$dir = \dirname($thumbnailPaths['fs']);
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
Folder::create($dir);
|
||||
}
|
||||
|
||||
// Create the thumbnail
|
||||
if (!is_file($thumbnailPaths['fs']) && !$this->createThumbnail($path, $thumbnailPaths['fs'])) {
|
||||
return $this->getUrl($path);
|
||||
}
|
||||
|
||||
return Uri::root() . $this->getEncodedPath($thumbnailPaths['url']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a thumbnail of the given image.
|
||||
*
|
||||
* @param string $path The path of the image
|
||||
* @param string $thumbnailPath The path of the thumbnail
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private function createThumbnail(string $path, string $thumbnailPath): bool
|
||||
{
|
||||
$image = new Image($path);
|
||||
|
||||
try {
|
||||
$image->createThumbnails([$this->thumbnailSize[0] . 'x' . $this->thumbnailSize[1]], $image::SCALE_INSIDE, \dirname($thumbnailPath), true);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
143
plugins/filesystem/local/src/Extension/Local.php
Normal file
143
plugins/filesystem/local/src/Extension/Local.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage FileSystem.local
|
||||
*
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\Filesystem\Local\Extension;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\Media\Administrator\Event\MediaProviderEvent;
|
||||
use Joomla\Component\Media\Administrator\Provider\ProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\Filesystem\Local\Adapter\LocalAdapter;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* FileSystem Local plugin.
|
||||
*
|
||||
* The plugin to deal with the local filesystem in Media Manager.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
final class Local extends CMSPlugin implements ProviderInterface
|
||||
{
|
||||
/**
|
||||
* Affects constructor behavior. If true, language files will be loaded automatically.
|
||||
*
|
||||
* @var boolean
|
||||
* @since 4.0.0
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
/**
|
||||
* The root directory path
|
||||
*
|
||||
* @var string
|
||||
* @since 4.3.0
|
||||
*/
|
||||
private $rootDirectory;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param DispatcherInterface $dispatcher The dispatcher
|
||||
* @param array $config An optional associative array of configuration settings
|
||||
* @param string $rootDirectory The root directory to look for images
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function __construct(DispatcherInterface $dispatcher, array $config, string $rootDirectory)
|
||||
{
|
||||
parent::__construct($dispatcher, $config);
|
||||
|
||||
$this->rootDirectory = $rootDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup Providers for Local Adapter
|
||||
*
|
||||
* @param MediaProviderEvent $event Event for ProviderManager
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function onSetupProviders(MediaProviderEvent $event)
|
||||
{
|
||||
$event->getProviderManager()->registerProvider($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the provider
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getID()
|
||||
{
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name of the provider
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getDisplayName()
|
||||
{
|
||||
return $this->getLanguage()->_('PLG_FILESYSTEM_LOCAL_DEFAULT_NAME');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and array of adapters
|
||||
*
|
||||
* @return \Joomla\Component\Media\Administrator\Adapter\AdapterInterface[]
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function getAdapters()
|
||||
{
|
||||
$adapters = [];
|
||||
$directories = $this->params->get('directories', '[{"directory": "images", "thumbs": 0}]');
|
||||
|
||||
// Do a check if default settings are not saved by user, if not initialize them manually
|
||||
if (\is_string($directories)) {
|
||||
$directories = json_decode($directories);
|
||||
}
|
||||
|
||||
foreach ($directories as $directoryEntity) {
|
||||
if (!$directoryEntity->directory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$directoryPath = $this->rootDirectory . '/' . $directoryEntity->directory;
|
||||
$directoryPath = rtrim($directoryPath) . '/';
|
||||
|
||||
if (!isset($directoryEntity->thumbs)) {
|
||||
$directoryEntity->thumbs = 0;
|
||||
}
|
||||
|
||||
$adapter = new LocalAdapter(
|
||||
$directoryPath,
|
||||
$directoryEntity->directory,
|
||||
$directoryEntity->thumbs,
|
||||
[200, 200]
|
||||
);
|
||||
$adapter->setCurrentUser($this->getApplication()->getIdentity());
|
||||
|
||||
$adapters[$adapter->getAdapterName()] = $adapter;
|
||||
}
|
||||
|
||||
return $adapters;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user