文件系统插件 - 基础
简介
在 Joomla 4 版本之前,对于您希望在 Joomla 网站上显示的图像和其他类型的媒体,您必须将文件存储在 Joomla 实例的子文件夹中,默认情况下为 /images。
随着 Joomla 4 中引入的媒体管理器,开发团队将“文件系统”的概念抽象化为“文件系统适配器” - 即提供文件式功能(如文件创建、重命名、删除等)的 PHP 类。然后,这为将媒体文件存储在本地文件系统以外的位置提供了可能性,例如,存储在“云”中的服务器上。
Joomla 设计还允许插件自定义媒体文件在文件系统中的存储方式,例如,通过限制不同用户组可用的操作类型。
本节演示如何开发一个基本的文件系统插件。它将类似于存储 /images 文件夹中媒体的标准 Joomla 功能。我们的文件系统插件将改为使用 /restricted 文件夹,并且我们将定义它,以便只有超级用户才能删除该 /restricted 文件夹中的文件或子文件夹。在其他方面,它将与标准的 Joomla 文件系统功能相同。
在下一节中,FTP 文件系统插件 将演示如何将媒体文件存储在 FTP 服务器上。
文件系统提供程序和适配器
媒体管理器屏幕截图显示了一个具有 2 个文件系统提供程序的 Joomla 实例
- 一个本地提供程序,它通过本地文件存储提供访问 - 这是 Joomla 附带的
- 一个受限提供程序,它提供对存储在受限目录中的文件的访问 - 这是本节中描述的基本文件系统插件启用的功能。
此外,本地提供程序具有 2 个文件系统适配器
- images - 显示存储在 /images 文件夹中的文件
- videos - 显示存储在 /videos 文件夹中的文件。
这纯粹是通过配置实现的
- 在 Joomla 实例下创建 /videos 文件夹
- 配置 Joomla “文件系统 - 本地”插件以包含第二个名为 videos 的目录。
受限提供程序只有一个名为“restricted”的文件系统适配器。这反映了 /restricted 文件夹中的文件存储,要使其工作,您应该在 Joomla 实例的根目录下创建此目录。
显示的提供程序的顺序由文件系统插件的 ordering
确定,该顺序通过管理员系统/插件页面设置。
Joomla 媒体管理器的工作原理
为了了解媒体管理器的工作原理,我们考虑一下用户在管理员后端单击内容/媒体按钮时会发生什么,并描述图中显示的步骤。它被简化了一点;接下来的序列图更准确地表示了这一点。
- 用户在管理员后端(在菜单侧边栏中,以深蓝色显示)单击内容/媒体按钮。
- 这会导致对服务器上的
com_media
发出 HTTP GET 请求。 - 响应此请求,
com_media
导入“文件系统”插件,并触发事件onSetupProviders
。 - 该图显示了 2 个文件系统提供程序,Joomla 附带的本地提供程序和我们插件的受限提供程序。每个提供程序都向
com_media
反馈“我是一个文件系统提供程序,这些是我的文件系统适配器”。 - 在 HTTP 响应中,
com_media
向客户端浏览器发送包含媒体窗口容器(以灰色显示)的 HTML 页面,其中显示所有媒体文件。在此窗口的左侧窗格中,显示了可用的文件系统提供程序及其关联的适配器。此外,响应告诉浏览器下载并运行media-manager.js
代码。 media-manager.js
代码向服务器发送 Ajax 请求,请求左侧窗格中第一个提供程序和适配器的文件详细信息。(实际上,这并不完全正确;代码会记住会话状态和此用户之前查看的文件夹,如果已设置,则请求该文件夹中文件的详细信息)。- 由于 HTTP 的无状态特性,
com_media
必须重复步骤 3,请求导入文件系统插件并触发onSetupProviders
。 - 同样,提供程序重复步骤 4。
- 根据 Ajax 请求中设置的适配器和文件夹,
com_media
调用该适配器的getFiles
函数,并传递文件夹名称。 - 适配器
getFiles
函数返回该文件夹中文件(包括子文件夹)的详细信息。 com_media
通过 Ajax 请求的 JSON 响应将这些文件详细信息传递给media-manager.js
。media-manager.js
代码使用这些文件和子文件夹的详细信息更新媒体窗口。
随后,当用户对媒体窗口中的文件或文件夹执行操作时,media-manager.js
代码向 com_media
发送 Ajax 请求以执行这些操作。
下面的序列图显示了一个示例,其中用户请求删除文件。序列图比上面的概述更详细,并描绘了与 com_media
ProviderManager 类的交互。
编写文件系统插件
要编写文件系统插件,您需要提供 2 个类
- 提供程序类,它在 administrator/components/com_media/src/Provider/ProviderInterface.php 中实现
Joomla\Component\Media\Administrator\Provider\ProviderInterface
。这很简单,您只需复制 plugins/filesystem/local/src/Extension/Local.php 中本地文件系统插件的方法即可。主插件(扩展)类也充当提供程序类。 - 适配器类,它在 administrator/components/com_media/src/Adapter/AdapterInterface.php 中实现
Joomla\Component\Media\Administrator\Adapter\AdapterInterface
。这是大部分工作所在,因为您必须根据您的文件存储实现各种类型的文件操作。
在此基本示例中,我们将利用 Joomla 本地文件存储,因此我们的适配器类将继承自 Joomla LocalAdapter 类,并且我们将只覆盖 delete 函数(它被调用以删除文件或文件夹)
public function delete(string $path)
{
// If the user has superuser privilege then allow this, otherwise raise an exception
$user = Factory::getApplication()->getIdentity();
if ($user->authorise('core.admin'))
{
parent::delete($path);
}
else
{
throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'));
}
}
插件源代码
您可以将下面的源代码复制到 plg_filesystem_restricted
目录中,或从 下载受限文件系统插件 下载完整的插件。
安装后,请务必启用插件!您还需要在 Joomla 实例的根文件夹中创建 /restricted
文件夹。
清单文件
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="filesystem" method="upgrade">
<name>plg_filesystem_restricted</name>
<author>me</author>
<creationDate>today</creationDate>
<version>1.0.0</version>
<description>My restricted filesystem</description>
<namespace path="src">My\Plugin\Filesystem\Restricted</namespace>
<files>
<folder plugin="restricted">services</folder>
<folder>src</folder>
</files>
</extension>
服务提供程序文件
这是通过 Joomla 依赖项注入容器实例化插件的样板代码。
<?php
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 My\Plugin\Filesystem\Restricted\Extension\Restricted;
return new class () implements ServiceProviderInterface {
public function register(Container $container)
{
$container->set(
PluginInterface::class,
function (Container $container) {
$dispatcher = $container->get(DispatcherInterface::class);
$plugin = new Restricted(
$dispatcher,
(array) PluginHelper::getPlugin('filesystem', 'restricted')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
插件/提供程序类
这是根据 Joomla 本地文件系统插件中的等效类进行调整的。
<?php
namespace My\Plugin\Filesystem\Restricted\Extension;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Component\Media\Administrator\Event\MediaProviderEvent;
use Joomla\Component\Media\Administrator\Provider\ProviderInterface;
use My\Plugin\Filesystem\Restricted\Adapter\RestrictedAdapter;
\defined('_JEXEC') or die;
final class Restricted extends CMSPlugin implements ProviderInterface
{
public static function getSubscribedEvents(): array {
return [
'onSetupProviders' => 'onSetupProviders',
];
}
/**
* Setup Providers for Restricted 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; // from "element" field of plugin's record in extensions table
}
/**
* Returns the display name of the provider
*
* @return string
*
* @since 4.0.0
*/
public function getDisplayName()
{
return 'Restricted';
}
/**
* Returns an array of adapters
*
* @return \Joomla\Component\Media\Administrator\Adapter\AdapterInterface[]
*
* @since 4.0.0
*/
public function getAdapters()
{
$adapters = [];
$adapter = new RestrictedAdapter(JPATH_ROOT . '/restricted', 'restricted');
$adapters[$adapter->getAdapterName()] = $adapter;
return $adapters;
}
}
适配器类
对于我们的适配器类,我们扩展 Joomla LocalAdapter 并覆盖 delete
函数。
<?php
namespace My\Plugin\Filesystem\Restricted\Adapter;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Factory;
\defined('_JEXEC') or die;
class RestrictedAdapter extends \Joomla\Plugin\Filesystem\Local\Adapter\LocalAdapter
{
/**
* Adapter constructor - based on the Joomla LocalAdapter
*
* @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])
{
parent::__construct($rootPath, $filePath);
}
/**
* 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)
{
// If the user has superuser privilege then allow this, otherwise raise an exception
$user = Factory::getApplication()->getIdentity();
if ($user->authorise('core.admin'))
{
parent::delete($path);
}
else
{
throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'));
}
}
/**
* Returns the name of this adapter.
*
* @return string
*
* @since 4.0.0
*/
public function getAdapterName(): string
{
return "Restricted";
}
}