| Current Path : /var/www/html/components/com_jce/editor/plugins/browser/ |
| Current File : /var/www/html/components/com_jce/editor/plugins/browser/browser.php |
<?php
/**
* @package JCE
* @subpackage Editor
*
* @copyright Copyright (c) 2009-2024 Ryan Demmer. All rights reserved
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
class WFBrowserPlugin extends WFMediaManager
{
/*
* @var string
*/
protected $_filetypes = 'doc,docx,dot,dotx,ppt,pps,pptx,ppsx,xls,xlsx,gif,jpeg,jpg,png,webp,apng,avif,pdf,zip,tar,gz,swf,rar,mov,mp4,m4a,flv,mkv,webm,ogg,ogv,qt,wmv,asx,asf,avi,wav,mp3,aiff,oga,odt,odg,odp,ods,odf,rtf,txt,csv';
private function isMediaField()
{
$app = Factory::getApplication();
return $app->input->getString('mediatype') && $app->input->getCmd('fieldid', $app->input->getCmd('element', ''));
}
/**
* Get a parameter by key.
*
* @param string $key Parameter key eg: editor.width
* @param mixed $fallback Fallback value
* @param mixed $default Default value
* @param string $type Variable type eg: string, boolean, integer, array
*
* @return mixed
*/
public function getParam($key, $fallback = '', $default = '', $type = 'string')
{
$wf = WFApplication::getInstance();
$value = parent::getParam($key, $fallback, $default, $type);
// get all keys
$keys = explode('.', $key);
// get caller if any
$caller = $this->get('caller');
// create new namespaced key
if ($caller && ($keys[0] === $caller || count($keys) == 1)) {
// create new key
$key = $caller . '.' . 'browser' . '.' . array_pop($keys);
// get namespaced value, fallback to base parameter
$value = $wf->getParam($key, $value, $default, $type);
}
return $value;
}
private function hasFileBrowser()
{
$app = Factory::getApplication();
if ($app->input->getInt('standalone')) {
return true;
}
// media field usage: element present with a mediatype (standalone=0, no caller)
if ($this->isMediaField()) {
return true;
}
$map = array(
'imgmanager' => 'basic_dialog_filebrowser',
'mediamanager' => 'basic_dialog_filebrowser',
'link' => 'file_browser',
'iframe' => 'file_browser',
'table' => 'file_browser',
'style' => 'file_browser'
);
$caller = $this->get('caller');
if (!$caller) {
return false;
}
$key = $map[$caller] ?? null;
if (!$key) {
return false;
}
if ((int) $this->getParam($caller . '.' . $key, 1) === 0) {
return false;
}
return true;
}
public function __construct($config = array())
{
$app = Factory::getApplication();
$config = array(
'layout' => 'browser',
'can_edit_images' => 1,
'show_view_mode' => 1,
);
parent::__construct($config);
if (!$this->hasFileBrowser()) {
throw new Exception(Text::_('JERROR_ALERTNOAUTHOR'));
}
$standalone = $app->input->getInt('standalone');
$browser = $this->getFileBrowser();
// add upload event
$browser->addEvent('onUpload', array($this, 'onUpload'));
// standalone shows all configured extensions with no mediatype filter
// but media fields always apply their mediatype filter, even when standalone=1
if (!$standalone || $this->isMediaField()) {
// use mediatype from input; default to 'images' if not set
$mediatype = $app->input->getString('mediatype', $app->input->getString('filter', 'images'));
// clean and lowercase
$mediatype = (string) preg_replace('/[^\w_,]/i', '', strtolower($mediatype));
// get filetypes from params
$filetypes = $this->getParam('extensions', $this->get('_filetypes'));
// map to comma-separated list
$filetypes = $browser->getFileTypes('list', $filetypes);
$accept = explode(',', $filetypes);
$map = array(
'images' => array('jpg', 'jpeg', 'png', 'apng', 'gif', 'webp', 'avif'),
'media' => array('avi', 'wmv', 'wm', 'asf', 'asx', 'wmx', 'wvx', 'mov', 'qt', 'mpg', 'mpeg', 'm4a', 'm4v', 'swf', 'dcr', 'rm', 'ra', 'ram', 'divx', 'mp4', 'ogv', 'ogg', 'webm', 'flv', 'f4v', 'mp3', 'ogg', 'wav', 'xap'),
'documents' => array('doc', 'docx', 'odg', 'odp', 'ods', 'odt', 'pdf', 'ppt', 'pptx', 'txt', 'xcf', 'xls', 'xlsx', 'csv'),
'html' => array('html', 'htm', 'txt', 'md'),
'files' => $accept, // “files” == everything allowed
);
// add svg support to images if it is allowed in filetypes
if (in_array('svg', $accept)) {
$map['images'][] = 'svg';
}
$selected = array();
foreach (explode(',', $mediatype) as $type) {
$type = trim($type);
if (array_key_exists($type, $map)) {
$selected = array_merge($selected, array_values(array_intersect($map[$type], $accept)));
} elseif (in_array($type, $accept, true)) {
$selected[] = $type;
}
}
$this->setFileTypes(implode(',', array_values(array_unique($selected))));
}
$folder = $this->getMediaFolder();
if ($folder) {
// process any variables in the path
$path = $browser->getFileSystem()->toRelative($folder, false);
if ($browser->checkPathAccess($path)) {
// set new path for browser
$browser->set('source', $folder);
}
}
}
private function getMediaFolder()
{
$app = Factory::getApplication();
$folder = $app->input->getString('mediafolder', '');
if ($folder) {
// trim the path of leading : if any
$folder = trim($folder, ':');
// trim the path of leading and trailing /
$folder = trim($folder, '/');
// clean
$folder = WFUtility::cleanPath($folder);
// split by / and each part "safe"
$parts = explode('/', $folder);
foreach ($parts as $key => $part) {
$parts[$key] = WFUtility::makeSafe($part);
}
// rejoin parts
$folder = implode('/', $parts);
// clean path again
$folder = WFUtility::cleanPath($folder);
// still intact after clean?
if ($folder) {
$browser = $this->getFileBrowser();
// check this path is within an existing store
$store = $browser->getDirectoryStoreFromPath($folder);
if (!empty($store)) {
// check path exists
if ($browser->getFileSystem()->is_dir($folder)) {
return $folder;
}
}
}
}
return '';
}
/**
* Normalize a Joomla Media Field path
*
* @param string $folder
*
* @return string
*/
private function normalizeLocalJoomlaFolder($folder)
{
if (empty($folder)) {
return '';
}
$folder = rawurldecode($folder);
// must not be an absolute URL
if (strpos($folder, '://') !== false) {
return '';
}
$pos = strpos($folder, ':');
if ($pos === false) {
// Joomla 3: plain folder value with no local-* scheme, eg: "images/foo" — treat as local-images
$scheme = 'local-images';
$path = trim($folder, " \t\n\r\0\x0B/");
} else {
$scheme = substr($folder, 0, $pos);
$path = trim(substr($folder, $pos + 1), " \t\n\r\0\x0B/");
}
// must be a Joomla local adapter (local-images, local-files, local-media, etc.)
if (strpos($scheme, 'local-') !== 0) {
return '';
}
// strip 'local-' prefix to get the root folder name
$root = substr($scheme, strlen('local-'));
if (empty($root)) {
return '';
}
// root must be a plain folder name — no path separators (checkPath allows / by design)
if (strpos($root, '/') !== false || strpos($root, '\\') !== false) {
return '';
}
// validate root and path: traversal, null bytes, character whitelist
try {
WFUtility::checkPath($root);
WFUtility::checkPath($path);
} catch (\InvalidArgumentException $e) {
return '';
}
// build normalized path: root alone, or root/path
return $path !== '' ? $root . '/' . $path : $root;
}
/**
* Update the File Browser configuration with the current media folder.
*
* @param array $config Configuration array to update.
* @return array $config Updated configuration array.
*/
protected function getFileBrowserConfig($config = array())
{
$app = Factory::getApplication();
$config = parent::getFileBrowserConfig($config);
// update folder path if a value is passed from a mediafield url
if ($this->isMediaField()) {
// get the mediafolder value from a JCE Media Field if any
$folder = $app->input->getString('mediafolder', '');
$folder = trim(rawurldecode($folder));
$prefix = '';
// check if this is a root folder by looking for a : character at the start of the path value
$isRootFolder = strpos($folder, ':') === 0;
// trim the path of leading : if any
$folder = trim($folder, ':');
// trim the path of leading and trailing /
$folder = trim($folder, '/');
if (empty($config['dir'])) {
$root = array('path' => '');
} else {
if ($isRootFolder) {
if (!empty($folder)) {
$tmpPath = $folder . '/';
foreach ($config['dir'] as $key => $store) {
$base = trim($store['path'], '/');
// strip any variable segments (eg: $usergroup) to get the comparable literal prefix
$literalBase = trim(preg_replace('/\/?\$.*$/', '', $base), '/');
// check if the folder is within this directory store path
if (!empty($literalBase) && ($folder === $literalBase || strpos($tmpPath, $literalBase . '/') === 0)) {
$hash = md5($folder);
$config['dir'] = array(
$hash => array(
'label' => '',
'path' => $folder,
),
);
return $config;
}
}
}
// no match found - fall through and treat as relative to dir value
}
// get the first directory store prefix
$prefix = key($config['dir']);
// get the first directory store
$root = reset($config['dir']);
}
if ($app->input->getInt('converted', 0) === 1) {
// get the path from a converted media field
$folder = $app->input->getString('path', $app->input->getString('folder', '')); // include "folder" for Joomla 3
// normalize the Joomla Media Field path, eg: local-images:/folder/subfolder => images/folder/subfolder, local-media:/cache => media/cache
$folder = $this->normalizeLocalJoomlaFolder($folder);
if ($folder) {
$tmpPath = $folder . '/';
foreach ($config['dir'] as $store) {
$base = trim($store['path'], '/');
// check if the normalized path is within any profile-allowed directory store
if ($tmpPath === $base . '/' || strpos($tmpPath, $base . '/') === 0) {
$root['path'] = $folder;
break;
}
}
}
// reset folder so it is not appended again below
$folder = '';
}
$path = WFUtility::makePath($root['path'], $folder);
$path = trim($path, '/');
if (empty($prefix)) {
$hash = md5($path);
} else {
$hash = $prefix;
}
$config['dir'] = array(
$hash => array(
'label' => '',
'path' => $path,
),
);
}
return $config;
}
public function setFileTypes($filetypes = '')
{
// get file browser reference
$browser = $this->getFileBrowser();
// set updated filetypes
$browser->setFileTypes($filetypes);
}
/**
* Display the plugin.
*/
public function display()
{
parent::display();
$app = Factory::getApplication();
$document = WFDocument::getInstance();
$slot = $app->input->getCmd('slot', 'plugin');
// update some document variables
$document->setName('browser');
$document->setTitle(Text::_('WF_BROWSER_TITLE'));
if ($document->get('standalone') == 1) {
if ($slot === 'plugin') {
$document->addScript(array('window.min'));
$callback = $app->input->getCmd('callback', '');
$element = $app->input->getCmd('fieldid', 'field-media-id');
// Joomla 4 field variable not converted
if ($element == 'field-media-id') {
$element = $app->input->getCmd('element', '');
}
$settings = array(
'site_url' => Uri::base(true) . '/',
'document_base_url' => Uri::root(),
'language' => WFLanguage::getCode(),
'element' => $element,
'token' => Session::getFormToken(),
);
if ($callback) {
$settings['callback'] = $callback;
}
$document->addScriptDeclaration('tinymce.settings=' . json_encode($settings) . ';');
}
$document->addScript(array('popup.min'), 'plugins');
$document->addStyleSheet(array('browser.min'), 'plugins');
}
if ($slot === 'plugin') {
$document->addScript(array('browser'), 'plugins');
}
}
public function onUpload($file, $relative = '')
{
$app = Factory::getApplication();
parent::onUpload($file, $relative);
// inline upload
if ($app->input->getInt('inline', 0) === 1) {
$result = array(
'file' => $relative,
'name' => basename($file),
);
return $result;
}
return array();
}
}