PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/components/com_jce/editor/extensions/browser/file.php

https://bitbucket.org/organicdevelopment/joomla-2.5
PHP | 1348 lines | 781 code | 251 blank | 316 comment | 126 complexity | 357b305fe4c80b6f622b7042fd8e1470 MD5 | raw file
Possible License(s): LGPL-3.0, GPL-2.0, MIT, BSD-3-Clause, LGPL-2.1
  1. <?php
  2. /**
  3. * @package JCE
  4. * @copyright Copyright (c) 2009-2012 Ryan Demmer. All rights reserved.
  5. * @license GNU/GPL 2 or later - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  6. * JCE is free software. This version may have been modified pursuant
  7. * to the GNU General Public License, and as distributed it includes or
  8. * is derivative of works licensed under the GNU General Public License or
  9. * other free or open source software licenses.
  10. */
  11. defined('_JEXEC') or die('RESTRICTED');
  12. wfimport('editor.libraries.classes.extensions.browser');
  13. wfimport('editor.libraries.classes.extensions.filesystem');
  14. class WFFileBrowser extends WFBrowserExtension {
  15. /*
  16. * @var array
  17. */
  18. private $_buttons = array();
  19. /*
  20. * @var array
  21. */
  22. private $_actions = array();
  23. /*
  24. * @var array
  25. */
  26. private $_events = array();
  27. /*
  28. * @var array
  29. */
  30. private $_result = array('error' => array(), 'files' => array(), 'folders' => array());
  31. /**
  32. * @access protected
  33. */
  34. public function __construct($config = array()) {
  35. $default = array(
  36. 'dir' => '',
  37. 'filesystem' => 'joomla',
  38. 'filetypes' => 'images=jpg,jpeg,png,gif',
  39. 'upload' => array(
  40. 'runtimes' => 'html5,flash,silverlight',
  41. 'chunk_size' => null,
  42. 'max_size' => 1024,
  43. 'validate_mimetype' => 1,
  44. 'add_random' => 0
  45. ),
  46. 'folder_tree' => 1,
  47. 'list_limit' => 'all',
  48. 'features' => array(
  49. 'upload' => 1,
  50. 'folder' => array(
  51. 'create' => 1,
  52. 'delete' => 1,
  53. 'rename' => 1,
  54. 'move' => 1
  55. ),
  56. 'file' => array(
  57. 'rename' => 1,
  58. 'delete' => 1,
  59. 'move' => 1
  60. )
  61. ),
  62. 'websafe_mode' => 'utf-8'
  63. );
  64. $config = array_merge($default, $config);
  65. // Call parent
  66. parent::__construct($config);
  67. // Setup XHR callback funtions
  68. $this->setRequest(array($this, 'getItems'));
  69. $this->setRequest(array($this, 'getFileDetails'));
  70. $this->setRequest(array($this, 'getFolderDetails'));
  71. $this->setRequest(array($this, 'getTree'));
  72. $this->setRequest(array($this, 'getTreeItem'));
  73. // Get actions
  74. $this->getStdActions();
  75. // Get buttons
  76. $this->getStdButtons();
  77. }
  78. /**
  79. * Display the browser
  80. * @access public
  81. */
  82. public function display() {
  83. parent::display();
  84. // Get the Document instance
  85. $document = WFDocument::getInstance();
  86. $document->addScript(array(
  87. 'tree',
  88. 'upload'
  89. ), 'libraries');
  90. $document->addScript(array(
  91. 'plupload.full',
  92. ), 'jce.libraries.plupload');
  93. $document->addScript(array(
  94. 'file',
  95. 'sort',
  96. 'filter',
  97. 'manager'
  98. ), 'extensions.browser.js');
  99. //$document->addStyleSheet(array('files', 'tree', 'upload'), 'libraries');
  100. $document->addStyleSheet(array('manager'), 'extensions.browser.css');
  101. // custom stylesheet
  102. //$document->addStyleSheet(array('custom'), 'libraries.css');
  103. // file browser options
  104. $document->addScriptDeclaration('WFFileBrowser.settings=' . json_encode($this->getSettings()) . ';');
  105. }
  106. /**
  107. * Render the browser view
  108. * @access public
  109. */
  110. public function render() {
  111. $session = JFactory::getSession();
  112. // create file view
  113. $view = $this->getView('file');
  114. // assign session data
  115. $view->assignRef('session', $session);
  116. // assign form action
  117. $view->assign('action', $this->getFormAction());
  118. // return view output
  119. $view->display();
  120. }
  121. /**
  122. * Set a WFRequest item
  123. * @access public
  124. * @param array $request
  125. */
  126. public function setRequest($request) {
  127. $xhr = WFRequest::getInstance();
  128. $xhr->setRequest($request);
  129. }
  130. /**
  131. * Upload form action url
  132. *
  133. * @access public
  134. * @param string The target action file eg: upload.php
  135. * @return Joomla! component url
  136. * @since 1.5
  137. */
  138. protected function getFormAction() {
  139. $wf = WFEditorPlugin::getInstance();
  140. $component_id = JRequest::getInt('component_id');
  141. $query = '';
  142. $args = array(
  143. 'plugin' => $wf->getName()
  144. );
  145. if ($component_id) {
  146. $args['component_id'] = $component_id;
  147. }
  148. foreach ($args as $k => $v) {
  149. $query .= '&' . $k . '=' . $v;
  150. }
  151. return JURI::base(true) . '/index.php?option=com_jce&view=editor&layout=plugin' . $query;
  152. }
  153. public function getFileSystem() {
  154. static $filesystem;
  155. if (!is_object($filesystem)) {
  156. $wf = WFEditorPlugin::getInstance();
  157. $config = array(
  158. 'dir' => $this->get('dir'),
  159. 'upload_conflict' => $wf->getParam('editor.upload_conflict', 'overwrite')
  160. );
  161. $filesystem = WFFileSystem::getInstance($this->get('filesystem'), $config);
  162. }
  163. return $filesystem;
  164. }
  165. private function getViewable() {
  166. return 'jpeg,jpg,gif,png,avi,wmv,wm,asf,asx,wmx,wvx,mov,qt,mpg,mp3,mp4,m4v,mpeg,ogg,ogv,webm,swf,flv,f4v,xml,dcr,rm,ra,ram,divx,html,htm,txt,rtf,pdf,doc,docx,xls,xlsx,ppt,pptx';
  167. }
  168. /**
  169. * Return a list of allowed file extensions in selected format
  170. *
  171. * @access public
  172. * @return extension list
  173. */
  174. private function getFileTypes($format = 'map') {
  175. $list = $this->get('filetypes');
  176. // Remove excluded file types (those that have a - prefix character) from the list
  177. $data = array();
  178. foreach (explode(';', $list) as $group) {
  179. if (substr(trim($group), 0, 1) === '-') {
  180. continue;
  181. }
  182. // remove excluded file types (those that have a - prefix character) from the list
  183. $data[] = preg_replace('#(,)?-([\w]+)#', '', $group);
  184. }
  185. $list = implode(';', $data);
  186. switch ($format) {
  187. case 'list':
  188. return $this->listFileTypes($list);
  189. break;
  190. case 'array':
  191. return explode(',', $this->listFileTypes($list));
  192. break;
  193. default:
  194. case 'map':
  195. return $list;
  196. break;
  197. }
  198. }
  199. public function setFileTypes($list = 'images=jpg,jpeg,png,gif') {
  200. $this->set('filetypes', $list);
  201. }
  202. /**
  203. * Converts the extensions map to a list
  204. * @param string $map The extensions map eg: images=jpg,jpeg,gif,png
  205. * @return string jpg,jpeg,gif,png
  206. */
  207. private function listFileTypes($map) {
  208. return preg_replace(array('/([\w]+)=([\w]+)/', '/;/'), array('$2', ','), $map);
  209. }
  210. public function addFileTypes($types) {
  211. $list = explode(';', $this->get('filetypes'));
  212. foreach ($types as $group => $extensions) {
  213. $list[] = $group . '=' . $extensions;
  214. }
  215. $this->set('filetypes', implode(';', $list));
  216. }
  217. /**
  218. * Maps upload file types to an upload dialog list, eg: 'images', 'jpeg,jpg,gif,png'
  219. * @return json encoded list
  220. */
  221. private function mapUploadFileTypes() {
  222. $map = array();
  223. // Get the filetype map
  224. $list = $this->getFileTypes();
  225. if ($list) {
  226. $items = explode(';', $list);
  227. $all = array();
  228. // [images=jpeg,jpg,gif,png]
  229. foreach ($items as $item) {
  230. // ['images', 'jpeg,jpg,gif,png']
  231. $kv = explode('=', $item);
  232. $extensions = implode(';', preg_replace('/(\w+)/i', '*.$1', explode(',', $kv[1])));
  233. $map[WFText::_('WF_FILEGROUP_' . $kv[0], WFText::_($kv[0])) . ' (' . $extensions . ')'] = $kv[1];
  234. $all[] = $kv[1];
  235. }
  236. if (count($items) > 1) {
  237. // All file types
  238. $map[WFText::_('WF_FILEGROUP_ALL') . ' (*.*)'] = implode(',', $all);
  239. }
  240. }
  241. return $map;
  242. }
  243. /**
  244. * Returns the result variable
  245. * @return var $_result
  246. */
  247. public function getResult() {
  248. return $this->_result;
  249. }
  250. public function setResult($value, $key = null) {
  251. if ($key) {
  252. if (is_array($this->_result[$key])) {
  253. $this->_result[$key][] = $value;
  254. } else {
  255. $this->_result[$key] = $value;
  256. }
  257. } else {
  258. $this->_result = $value;
  259. }
  260. }
  261. function checkFeature($action, $type = null) {
  262. $features = $this->get('features');
  263. if ($type) {
  264. if (isset($features[$type])) {
  265. $type = $features[$type];
  266. if (isset($type[$action])) {
  267. return (bool) $type[$action];
  268. }
  269. }
  270. } else {
  271. if (isset($features[$action])) {
  272. return (bool) $features[$action];
  273. }
  274. }
  275. return false;
  276. }
  277. public function getBaseDir() {
  278. $filesystem = $this->getFileSystem();
  279. return $filesystem->getBaseDir();
  280. }
  281. /**
  282. * Get the list of files in a given folder
  283. * @param string $relative The relative path of the folder
  284. * @param string $filter A regex filter option
  285. * @return File list array
  286. */
  287. private function getFiles($relative, $filter = '.') {
  288. $filesystem = $this->getFileSystem();
  289. $list = $filesystem->getFiles($relative, $filter);
  290. return $list;
  291. }
  292. /**
  293. * Get the list of folder in a given folder
  294. * @param string $relative The relative path of the folder
  295. * @return Folder list array
  296. */
  297. private function getFolders($relative) {
  298. $filesystem = $this->getFileSystem();
  299. $list = $filesystem->getFolders($relative);
  300. return $list;
  301. }
  302. /**
  303. * Get file and folder lists
  304. * @return array Array of file and folder list objects
  305. * @param string $relative Relative or absolute path based either on source url or current directory
  306. * @param int $limit List limit
  307. * @param int $start list start point
  308. */
  309. public function getItems($path, $limit = 25, $start = 0) {
  310. $filesystem = $this->getFileSystem();
  311. clearstatcache();
  312. // decode path
  313. $path = rawurldecode($path);
  314. WFUtility::checkPath($path);
  315. // get source dir from path eg: images/stories/fruit.jpg = images/stories
  316. $dir = $filesystem->getSourceDir($path);
  317. // get file list by filter
  318. $files = self::getFiles($dir, '\.(?i)(' . str_replace(',', '|', $this->getFileTypes('list')) . ')$');
  319. // get folder list
  320. $folders = self::getFolders($dir);
  321. $folderArray = array();
  322. $fileArray = array();
  323. $items = array_merge($folders, $files);
  324. if ($items) {
  325. if (is_numeric($limit)) {
  326. $items = array_slice($items, $start, $limit);
  327. }
  328. foreach ($items as $item) {
  329. $item['classes'] = '';
  330. if ($item['type'] == 'folders') {
  331. $folderArray[] = $item;
  332. } else {
  333. // check for selected item
  334. $item['selected'] = $filesystem->isMatch($item['url'], $path);
  335. $fileArray[] = $item;
  336. }
  337. }
  338. }
  339. $result = array(
  340. 'folders' => $folderArray,
  341. 'files' => $fileArray,
  342. 'total' => array(
  343. 'folders' => count($folders),
  344. 'files' => count($files)
  345. )
  346. );
  347. // Fire Event passing result as reference
  348. $this->fireEvent('onGetItems', array(&$result));
  349. return $result;
  350. }
  351. /**
  352. * Get a tree node
  353. * @param string $dir The relative path of the folder to search
  354. * @return Tree node array
  355. */
  356. public function getTreeItem($path) {
  357. $filesystem = $this->getFileSystem();
  358. $path = rawurldecode($path);
  359. WFUtility::checkPath($path);
  360. // get source dir from path eg: images/stories/fruit.jpg = images/stories
  361. $dir = $filesystem->getSourceDir($path);
  362. $folders = $this->getFolders($dir);
  363. $array = array();
  364. if (!empty($folders)) {
  365. foreach ($folders as $folder) {
  366. $array[] = array(
  367. 'id' => $folder['id'],
  368. 'name' => $folder['name'],
  369. 'class' => 'folder'
  370. );
  371. }
  372. }
  373. $result = array(
  374. 'folders' => $array
  375. );
  376. return $result;
  377. }
  378. /**
  379. * Escape a string
  380. *
  381. * @return string Escaped string
  382. * @param string $string
  383. */
  384. private function escape($string) {
  385. return preg_replace(array(
  386. '/%2F/',
  387. '/%3F/',
  388. '/%40/',
  389. '/%2A/',
  390. '/%2B/'
  391. ), array(
  392. '/',
  393. '?',
  394. '@',
  395. '*',
  396. '+'
  397. ), rawurlencode($string));
  398. }
  399. /**
  400. * Build a tree list
  401. * @param string $dir The relative path of the folder to search
  402. * @return Tree html string
  403. */
  404. public function getTree($path) {
  405. $filesystem = $this->getFileSystem();
  406. // decode path
  407. $path = rawurldecode($path);
  408. WFUtility::checkPath($path);
  409. // get source dir from path eg: images/stories/fruit.jpg = images/stories
  410. $dir = $filesystem->getSourceDir($path);
  411. $result = $this->getTreeItems($dir);
  412. return $result;
  413. }
  414. /**
  415. * Get Tree list items as html list
  416. *
  417. * @return Tree list html string
  418. * @param string $dir Current directory
  419. * @param boolean $root[optional] Is root directory
  420. * @param boolean $init[optional] Is tree initialisation
  421. */
  422. public function getTreeItems($dir, $root = true, $init = true) {
  423. $result = '';
  424. static $treedir = null;
  425. if ($init) {
  426. $treedir = $dir;
  427. if ($root) {
  428. $result = '<ul><li id="/" class="open"><div class="tree-row"><div class="tree-image"></div><span class="root"><a href="javascript:;">' . WFText::_('WF_LABEL_ROOT') . '</a></span></div>';
  429. $dir = '/';
  430. }
  431. }
  432. $folders = $this->getFolders($dir);
  433. if ($folders) {
  434. $result .= '<ul class="tree-node">';
  435. foreach ($folders as $folder) {
  436. $open = strpos($treedir, ltrim($folder['id'], '/')) === 0 ? ' open' : '';
  437. $result .= '<li id="' . $this->escape($folder['id']) . '" class="' . $open . '"><div class="tree-row"><div class="tree-image"></div><span class="folder"><a href="javascript:;">' . $folder['name'] . '</a></span></div>';
  438. if ($open) {
  439. if ($h = $this->getTreeItems($folder['id'], false, false)) {
  440. $result .= $h;
  441. }
  442. }
  443. $result .= '</li>';
  444. }
  445. $result .= '</ul>';
  446. }
  447. if ($init && $root) {
  448. $result .= '</li></ul>';
  449. }
  450. $init = false;
  451. return $result;
  452. }
  453. /**
  454. * Get a folders properties
  455. *
  456. * @return array Array of properties
  457. * @param string $dir Folder relative path
  458. */
  459. public function getFolderDetails($dir) {
  460. WFUtility::checkPath($dir);
  461. $filesystem = $this->getFileSystem();
  462. // get array with folder date and content count eg: array('date'=>'00-00-000', 'folders'=>1, 'files'=>2);
  463. return $filesystem->getFolderDetails($dir);
  464. }
  465. /**
  466. * Get a files properties
  467. *
  468. * @return array Array of properties
  469. * @param string $file File relative path
  470. */
  471. function getFileDetails($file) {
  472. WFUtility::checkPath($file);
  473. $filesystem = $this->getFileSystem();
  474. // get array with folder date and content count eg: array('date'=>'00-00-000', 'folders'=>1, 'files'=>2);
  475. return $filesystem->getFileDetails($file);
  476. }
  477. /**
  478. * Create standard actions based on access
  479. */
  480. private function getStdActions() {
  481. $this->addAction('help', '', '', WFText::_('WF_BUTTON_HELP'));
  482. if ($this->checkFeature('upload')) {
  483. $this->addAction('upload');
  484. $this->setRequest(array($this, 'upload'));
  485. }
  486. if ($this->checkFeature('create', 'folder')) {
  487. $this->addAction('folder_new');
  488. $this->setRequest(array($this, 'folderNew'));
  489. }
  490. }
  491. /**
  492. * Add an action to the list
  493. *
  494. * @param string $name Action name
  495. * @param array $options Array of options
  496. */
  497. public function addAction($name, $options = array()) {
  498. /* TODO */
  499. // backwards compatability (remove in stable)
  500. $args = func_get_args();
  501. if (count($args) == 4) {
  502. $options['icon'] = $args[1];
  503. $options['action'] = $args[2];
  504. $options['title'] = $args[3];
  505. }
  506. $options = array_merge(array('name' => $name), $options);
  507. // set some defaults
  508. if (!array_key_exists('icon', $options)) {
  509. $options['icon'] = '';
  510. }
  511. if (!array_key_exists('action', $options)) {
  512. $options['action'] = '';
  513. }
  514. if (!array_key_exists('title', $options)) {
  515. $options['title'] = WFText::_('WF_BUTTON_' . strtoupper($name));
  516. }
  517. $this->_actions[$name] = $options;
  518. }
  519. /**
  520. * Get all actions
  521. * @return object
  522. */
  523. private function getActions() {
  524. return array_reverse($this->_actions);
  525. }
  526. /**
  527. * Remove an action from the list by name
  528. * @param string $name Action name to remove
  529. */
  530. public function removeAction($name) {
  531. if (array_key_exists($this->_actions[$name])) {
  532. unset($this->_actions[$name]);
  533. }
  534. }
  535. /**
  536. * Create all standard buttons based on access
  537. */
  538. private function getStdButtons() {
  539. if ($this->checkFeature('delete', 'folder')) {
  540. $this->addButton('folder', 'delete', array('multiple' => true));
  541. $this->setRequest(array($this, 'deleteItem'));
  542. }
  543. if ($this->checkFeature('rename', 'folder')) {
  544. $this->addButton('folder', 'rename');
  545. $this->setRequest(array($this, 'renameItem'));
  546. }
  547. if ($this->checkFeature('move', 'folder')) {
  548. $this->addButton('folder', 'copy', array('multiple' => true));
  549. $this->addButton('folder', 'cut', array('multiple' => true));
  550. $this->addButton('folder', 'paste', array('multiple' => true, 'trigger' => true));
  551. $this->setRequest(array($this, 'copyItem'));
  552. $this->setRequest(array($this, 'moveItem'));
  553. }
  554. if ($this->checkFeature('rename', 'file')) {
  555. $this->addButton('file', 'rename');
  556. $this->setRequest(array($this, 'renameItem'));
  557. }
  558. if ($this->checkFeature('delete', 'file')) {
  559. $this->addButton('file', 'delete', array('multiple' => true));
  560. $this->setRequest(array($this, 'deleteItem'));
  561. }
  562. if ($this->checkFeature('move', 'file')) {
  563. $this->addButton('file', 'copy', array('multiple' => true));
  564. $this->addButton('file', 'cut', array('multiple' => true));
  565. $this->addButton('file', 'paste', array('multiple' => true, 'trigger' => true));
  566. $this->setRequest(array($this, 'copyItem'));
  567. $this->setRequest(array($this, 'moveItem'));
  568. }
  569. $this->addButton('file', 'view', array('restrict' => $this->getViewable()));
  570. $this->addButton('file', 'insert');
  571. }
  572. /**
  573. * Add a button
  574. *
  575. * @param string $type[optional] Button type (file or folder)
  576. * @param string $name Button name
  577. * @param string $icon[optional] Button icon
  578. * @param string $action[optional] Button action / function
  579. * @param string $title Button title
  580. * @param boolean $multiple[optional] Supports multiple file selection
  581. * @param boolean $trigger[optional]
  582. */
  583. public function addButton($type = 'file', $name, $options = array()) {
  584. $options = array_merge(array('name' => $name), $options);
  585. // set some defaults
  586. if (!array_key_exists('icon', $options)) {
  587. $options['icon'] = '';
  588. }
  589. if (!array_key_exists('action', $options)) {
  590. $options['action'] = '';
  591. }
  592. if (!array_key_exists('title', $options)) {
  593. $options['title'] = WFText::_('WF_BUTTON_' . strtoupper($name));
  594. }
  595. if (!array_key_exists('multiple', $options)) {
  596. $options['multiple'] = false;
  597. }
  598. if (!array_key_exists('trigger', $options)) {
  599. $options['trigger'] = false;
  600. }
  601. if (!array_key_exists('restrict', $options)) {
  602. $options['restrict'] = '';
  603. }
  604. $this->_buttons[$type][$name] = $options;
  605. }
  606. /**
  607. * Return an object list of all buttons
  608. * @return object
  609. */
  610. private function getButtons() {
  611. return $this->_buttons;
  612. }
  613. /**
  614. * Remove a button
  615. * @param string $type Button type
  616. * @param string $name Button name
  617. */
  618. public function removeButton($type, $name) {
  619. if (array_key_exists($name, $this->_buttons[$type])) {
  620. unset($this->_buttons[$type][$name]);
  621. }
  622. }
  623. /**
  624. * Change a buttons properties
  625. * @param string $type Button type
  626. * @param string $name Button name
  627. * @param string $keys Button keys
  628. */
  629. public function changeButton($type, $name, $keys) {
  630. foreach ($keys as $key => $value) {
  631. if (isset($this->_buttons[$type][$name][$key])) {
  632. $this->_buttons[$type][$name][$key] = $value;
  633. }
  634. }
  635. }
  636. /**
  637. * Add an event
  638. * @param string $name Event name
  639. * @param string $function Event function name
  640. */
  641. public function addEvent($name, $function) {
  642. $this->_events[$name] = $function;
  643. }
  644. /**
  645. * Execute an event
  646. * @return Evenet result
  647. * @param object $name Event name
  648. * @param array $args[optional] Optional arguments
  649. */
  650. protected function fireEvent($name, $args = null) {
  651. if (array_key_exists($name, $this->_events)) {
  652. $event = $this->_events[$name];
  653. if (is_array($event)) {
  654. return call_user_func_array($event, $args);
  655. } else {
  656. return call_user_func($event, $args);
  657. }
  658. }
  659. return $this->_result;
  660. }
  661. /**
  662. * Get a file icon based on extension
  663. * @return string Path to file icon
  664. * @param string $ext File extension
  665. */
  666. public function getFileIcon($ext) {
  667. if (JFile::exists(WF_EDITOR_LIBRARIES . '/img/icons/' . $ext . '.gif')) {
  668. return $this->image('libraries.icons/' . $ext . '.gif');
  669. } elseif (JFile::exists($this->getPluginPath() . '/img/icons/' . $ext . '.gif')) {
  670. return $this->image('plugins.icons/' . $ext . '.gif');
  671. } else {
  672. return $this->image('libraries.icons/def.gif');
  673. }
  674. }
  675. public function getFileSuffix() {
  676. $suffix = WFText::_('WF_MANAGER_FILE_SUFFIX');
  677. return str_replace('WF_MANAGER_FILE_SUFFIX', '_copy', $suffix);
  678. }
  679. private function validateUploadedFile($file) {
  680. // Null byte check
  681. if (strstr($file['name'], "\u0000")) {
  682. @unlink($file['tmp_name']);
  683. JError::raiseError(403, 'INVALID UPLOAD DATA');
  684. }
  685. // check for invalid extension in file name
  686. if (preg_match('#\.(php|php(3|4|5)|phtml|pl|py|jsp|asp|htm|html|shtml|sh|cgi)\b#i', $file['name'])) {
  687. JError::raiseError(403, 'INVALID FILE NAME');
  688. }
  689. // validate image
  690. if (preg_match('#\.(jpeg|jpg|jpe|png|gif|wbmp|bmp|tiff|tif)$#i', $file['name'])) {
  691. if (@getimagesize($file['tmp_name']) === false) {
  692. @unlink($file['tmp_name']);
  693. JError::raiseError(403, 'INVALID IMAGE FILE');
  694. }
  695. }
  696. $upload = $this->get('upload');
  697. // validate mimetype
  698. if ($upload['validate_mimetype']) {
  699. wfimport('editor.libraries.classes.mime');
  700. if (WFMimeType::check($file['name'], $file['tmp_name'], $file['type']) === false) {
  701. @unlink($file['tmp_name']);
  702. JError::raiseError(403, 'INVALID MIME TYPE');
  703. }
  704. }
  705. // xss check
  706. $xss_check = JFile::read($file['tmp_name'], false, 1024);
  707. // check for hidden php tags
  708. if (strstr($xss_check, '<?php')) {
  709. @unlink($file['tmp_name']);
  710. JError::raiseError(403, 'INVALID CODE IN FILE');
  711. }
  712. // check for hidden short php tags
  713. if (preg_match('#\.(inc|phps|class|php|php(3|4)|txt|dat|tpl|tmpl)$#i', $file['name'])) {
  714. if (strstr($xss_check, '<?')) {
  715. @unlink($file['tmp_name']);
  716. JError::raiseError(403, 'INVALID CODE IN FILE');
  717. }
  718. }
  719. // check for html tags (skip some files)
  720. if (!preg_match('#\.(txt|htm|html|xml|kml)$#i', $file['name'])) {
  721. $tags = array('abbr', 'acronym', 'address', 'applet', 'area', 'audioscope', 'base', 'basefont', 'bdo', 'bgsound', 'big', 'blackface', 'blink', 'blockquote', 'body', 'bq', 'br', 'button', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'comment', 'custom', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'fn', 'font', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html', 'iframe', 'ilayer', 'img', 'input', 'ins', 'isindex', 'keygen', 'kbd', 'label', 'layer', 'legend', 'li', 'limittext', 'link', 'listing', 'map', 'marquee', 'menu', 'meta', 'multicol', 'nobr', 'noembed', 'noframes', 'noscript', 'nosmartquotes', 'object', 'ol', 'optgroup', 'option', 'param', 'plaintext', 'pre', 'rt', 'ruby', 's', 'samp', 'script', 'select', 'server', 'shadow', 'sidebar', 'small', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'tt', 'ul', 'var', 'wbr', 'xml', 'xmp', '!DOCTYPE', '!--');
  722. // check for tags
  723. if (preg_match('#<(' . implode('|', $tags) . ')(\s|>)#i', $xss_check)) {
  724. //if (preg_match('#<(\w+)(\s|>)#i', $xss_check)) {
  725. @unlink($file['tmp_name']);
  726. JError::raiseError(403, 'INVALID TAG IN FILE');
  727. }
  728. }
  729. }
  730. /**
  731. * Upload a file.
  732. * @return array $error on failure or uploaded file name on success
  733. */
  734. public function upload() {
  735. // Check for request forgeries
  736. WFToken::checkToken() or die();
  737. //JError::setErrorHandling(E_ALL, 'callback', array('WFError', 'raiseError'));
  738. // check for feature access
  739. if (!$this->checkFeature('upload')) {
  740. JError::raiseError(403, 'RESTRICTED ACCESS');
  741. }
  742. jimport('joomla.filesystem.file');
  743. // get uploaded file
  744. $file = JRequest::getVar('file', '', 'files', 'array');
  745. if (empty($file)) {
  746. JError::raiseError(403, 'INVALID UPLOAD DATA');
  747. }
  748. // validate file data
  749. $this->validateUploadedFile($file);
  750. $wf = WFEditor::getInstance();
  751. // HTTP headers for no cache etc
  752. //header('Content-type: text/plain; charset=UTF-8');
  753. header("Expires: Wed, 4 Apr 1984 13:00:00 GMT");
  754. header("Last-Modified: " . gmdate("D, d M_Y H:i:s") . " GMT");
  755. header("Cache-Control: no-store, no-cache, must-revalidate");
  756. header("Cache-Control: post-check=0, pre-check=0", false);
  757. header("Pragma: no-cache");
  758. // get file name
  759. $name = JRequest::getVar('name', $file['name']);
  760. // target directory
  761. $dir = JRequest::getVar('upload-dir');
  762. // deocode directory
  763. $dir = rawurldecode($dir);
  764. // check destination path
  765. WFUtility::checkPath($dir);
  766. // decode name
  767. $name = rawurldecode($name);
  768. // check file name
  769. WFUtility::checkPath($name);
  770. // check for invalid extensions
  771. if (preg_match('#\.(php|phtml|pl|py|jsp|asp|shtml|sh|cgi)$#i', $name)) {
  772. JError::raiseError(403, 'INVALID FILE NAME');
  773. }
  774. // get extension
  775. $ext = WFUtility::getExtension($name);
  776. // check extension is allowed
  777. $allowed = $this->getFileTypes('array');
  778. if (is_array($allowed) && !empty($allowed) && in_array(strtolower($ext), $allowed) === false) {
  779. JError::raiseError(403, WFText::_('WF_MANAGER_UPLOAD_INVALID_EXT_ERROR'));
  780. @unlink($file['tmp_name']);
  781. }
  782. // strip extension
  783. $name = WFUtility::stripExtension($name);
  784. // make file name 'web safe'
  785. $name = WFUtility::makeSafe($name, $this->get('websafe_mode', 'utf-8'));
  786. // empty name
  787. if ($name == '') {
  788. JError::raiseError(403, 'INVALID FILE NAME');
  789. }
  790. // check for extension in file name
  791. if (preg_match('#\.(php|php(3|4|5)|phtml|pl|py|jsp|asp|htm|html|shtml|sh|cgi)\b#i', $name)) {
  792. JError::raiseError(403, 'INVALID FILE NAME');
  793. }
  794. $upload = $this->get('upload');
  795. // add random string
  796. if ($upload['add_random']) {
  797. $name = $name . '_' . substr(md5(uniqid(rand(), 1)), 0, 5);
  798. }
  799. // rebuild file name - name + extension
  800. $name = $name . '.' . $ext;
  801. // create a filesystem result object
  802. $result = new WFFileSystemResult();
  803. $filesystem = $this->getFileSystem();
  804. $complete = false;
  805. $contentType = JRequest::getVar('CONTENT_TYPE', '', 'SERVER');
  806. // Only multipart uploading is supported for now
  807. if ($contentType && strpos($contentType, "multipart") !== false) {
  808. if (isset($file['tmp_name']) && is_uploaded_file($file['tmp_name'])) {
  809. $result = $filesystem->upload('multipart', trim($file['tmp_name']), $dir, $name);
  810. if (!$result->state) {
  811. $result->message = WFText::_('WF_MANAGER_UPLOAD_ERROR');
  812. $result->code = 103;
  813. }
  814. @unlink($file['tmp_name']);
  815. $complete = true;
  816. }
  817. } else {
  818. $result->state = false;
  819. $result->code = 103;
  820. $result->message = WFText::_('WF_MANAGER_UPLOAD_ERROR');
  821. $complete = true;
  822. }
  823. // upload finished
  824. if ($complete) {
  825. if ($result instanceof WFFileSystemResult) {
  826. if ($result->state === true) {
  827. $path = $result->path;
  828. $this->setResult($this->fireEvent('onUpload', array($result->path)));
  829. $this->setResult(basename($result->path), 'files');
  830. } else {
  831. $this->setResult($result->message, 'error');
  832. }
  833. }
  834. die(json_encode($this->getResult()));
  835. }
  836. }
  837. /**
  838. * Delete the relative file(s).
  839. * @param $files the relative path to the file name or comma seperated list of multiple paths.
  840. * @return string $error on failure.
  841. */
  842. public function deleteItem($items) {
  843. // check for feature access
  844. if (!$this->checkFeature('delete', 'folder') && !$this->checkFeature('delete', 'file')) {
  845. JError::raiseError(403, 'RESTRICTED ACCESS');
  846. }
  847. $filesystem = $this->getFileSystem();
  848. $items = explode(",", rawurldecode($items));
  849. foreach ($items as $item) {
  850. // decode
  851. $item = rawurldecode($item);
  852. // check path
  853. WFUtility::checkPath($item);
  854. $result = $filesystem->delete($item);
  855. if ($result instanceof WFFileSystemResult) {
  856. if (!$result->state) {
  857. if ($result->message) {
  858. $this->setResult($result->message, 'error');
  859. } else {
  860. $this->setResult(JText::sprintf('WF_MANAGER_DELETE_' . strtoupper($result->type) . '_ERROR', basename($item)), 'error');
  861. }
  862. } else {
  863. $this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Delete', array($item)));
  864. $this->setResult($item, $result->type);
  865. }
  866. }
  867. }
  868. return $this->getResult();
  869. }
  870. /**
  871. * Rename a file.
  872. * @param string $src The relative path of the source file
  873. * @param string $dest The name of the new file
  874. * @return string $error
  875. */
  876. public function renameItem() {
  877. // check for feature access
  878. if (!$this->checkFeature('rename', 'folder') && !$this->checkFeature('rename', 'file')) {
  879. JError::raiseError(403, 'RESTRICTED ACCESS');
  880. }
  881. $args = func_get_args();
  882. $source = array_shift($args);
  883. $destination = array_shift($args);
  884. $source = rawurldecode($source);
  885. $destination = rawurldecode($destination);
  886. WFUtility::checkPath($source);
  887. WFUtility::checkPath($destination);
  888. // check for extension in destination name
  889. if (preg_match('#\.(php|php(3|4|5)|phtml|pl|py|jsp|asp|htm|html|shtml|sh|cgi)\b#i', $destination)) {
  890. JError::raiseError(403, 'INVALID FILE NAME');
  891. }
  892. $filesystem = $this->getFileSystem();
  893. $result = $filesystem->rename($source, WFUtility::makeSafe($destination, $this->get('websafe_mode')), $args);
  894. if ($result instanceof WFFileSystemResult) {
  895. if (!$result->state) {
  896. $this->setResult(WFText::sprintf('WF_MANAGER_RENAME_' . strtoupper($result->type) . '_ERROR', basename($source)), 'error');
  897. if ($result->message) {
  898. $this->setResult($result->message, 'error');
  899. }
  900. } else {
  901. $this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Rename', array($destination)));
  902. $this->setResult($destination, $result->type);
  903. }
  904. }
  905. return $this->getResult();
  906. }
  907. /**
  908. * Copy a file.
  909. * @param string $files The relative file or comma seperated list of files
  910. * @param string $dest The relative path of the destination dir
  911. * @return string $error on failure
  912. */
  913. public function copyItem($items, $destination) {
  914. // check for feature access
  915. if (!$this->checkFeature('move', 'folder') && !$this->checkFeature('move', 'file')) {
  916. JError::raiseError(403, 'RESTRICTED ACCESS');
  917. }
  918. $filesystem = $this->getFileSystem();
  919. $items = explode(",", rawurldecode($items));
  920. // decode
  921. $destination = rawurldecode($destination);
  922. // check destination path
  923. WFUtility::checkPath($destination);
  924. foreach ($items as $item) {
  925. // decode
  926. $item = rawurldecode($item);
  927. // check source path
  928. WFUtility::checkPath($item);
  929. $result = $filesystem->copy($item, $destination);
  930. if ($result instanceof WFFileSystemResult) {
  931. if (!$result->state) {
  932. if ($result->message) {
  933. $this->setResult($result->message, 'error');
  934. } else {
  935. $this->setResult(JText::sprintf('WF_MANAGER_COPY_' . strtoupper($result->type) . '_ERROR', basename($item)), 'error');
  936. }
  937. } else {
  938. $this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Copy', array($item)));
  939. $this->setResult($destination, $result->type);
  940. }
  941. }
  942. }
  943. return $this->getResult();
  944. }
  945. /**
  946. * Copy a file.
  947. * @param string $files The relative file or comma seperated list of files
  948. * @param string $dest The relative path of the destination dir
  949. * @return string $error on failure
  950. */
  951. public function moveItem($items, $destination) {
  952. // check for feature access
  953. if (!$this->checkFeature('move', 'folder') && !$this->checkFeature('move', 'file')) {
  954. JError::raiseError(403, 'RESTRICTED ACCESS');
  955. }
  956. $filesystem = $this->getFileSystem();
  957. $items = explode(",", rawurldecode($items));
  958. // decode
  959. $destination = rawurldecode($destination);
  960. // check destination path
  961. WFUtility::checkPath($destination);
  962. foreach ($items as $item) {
  963. // decode
  964. $item = rawurldecode($item);
  965. // check source path
  966. WFUtility::checkPath($item);
  967. $result = $filesystem->move($item, $destination);
  968. if ($result instanceof WFFileSystemResult) {
  969. if (!$result->state) {
  970. if ($result->message) {
  971. $this->setResult($result->message, 'error');
  972. } else {
  973. $this->setResult(JText::sprintf('WF_MANAGER_MOVE_' . strtoupper($result->type) . '_ERROR', basename($item)), 'error');
  974. }
  975. } else {
  976. $this->setResult($this->fireEvent('on' . ucfirst($result->type) . 'Move', array($item)));
  977. $this->setResult($destination, $result->type);
  978. }
  979. }
  980. }
  981. return $this->getResult();
  982. }
  983. /**
  984. * New folder
  985. * @param string $dir The base dir
  986. * @param string $new_dir The folder to be created
  987. * @return string $error on failure
  988. */
  989. public function folderNew() {
  990. if (!$this->checkFeature('create', 'folder')) {
  991. JError::raiseError(403, 'RESTRICTED ACCESS');
  992. }
  993. $args = func_get_args();
  994. $dir = array_shift($args);
  995. $new = array_shift($args);
  996. // decode
  997. $dir = rawurldecode($dir);
  998. $new = rawurldecode($new);
  999. $filesystem = $this->getFileSystem();
  1000. $result = $filesystem->createFolder($dir, WFUtility::makeSafe($new, $this->get('websafe_mode')), $args);
  1001. if ($result instanceof WFFileSystemResult) {
  1002. if (!$result->state) {
  1003. if ($result->message) {
  1004. $this->setResult($result->message, 'error');
  1005. } else {
  1006. $this->setResult(JText::sprintf('WF_MANAGER_NEW_FOLDER_ERROR', basename($new)), 'error');
  1007. }
  1008. } else {
  1009. $this->setResult($this->fireEvent('onFolderNew', array($new)));
  1010. }
  1011. }
  1012. return $this->getResult();
  1013. }
  1014. private function getUploadValue() {
  1015. $upload = trim(ini_get('upload_max_filesize'));
  1016. $post = trim(ini_get('post_max_size'));
  1017. $upload = WFUtility::convertSize($upload);
  1018. $post = WFUtility::convertSize($post);
  1019. if (intval($upload) <= intval($post)) {
  1020. return $upload;
  1021. }
  1022. return $post;
  1023. }
  1024. private function getUploadDefaults() {
  1025. $filesystem = $this->getFileSystem();
  1026. $features = $filesystem->get('upload');
  1027. $elements = isset($features['elements']) ? $features['elements'] : array();
  1028. $upload_max = $this->getUploadValue();
  1029. $upload = $this->get('upload');
  1030. /* $chunk_size = '512KB'; //$upload_max ? $upload_max / 1024 . 'KB' : '1MB';
  1031. $chunk_size = isset($upload['chunk_size']) ? $upload['chunk_size'] : $chunk_size;
  1032. // chunking not yet supported in safe_mode, check base directory is writable and chunking supported by filesystem
  1033. if (!$features['chunking']) {
  1034. $chunk_size = 0;
  1035. } */
  1036. // get upload size
  1037. $size = intval(preg_replace('/[^0-9]/', '', $upload['max_size'])) . 'kb';
  1038. // must not exceed server maximum
  1039. if ((int) $size * 1024 > (int) $upload_max) {
  1040. $size = $upload_max / 1024 . 'kb';
  1041. }
  1042. $runtimes = array();
  1043. if (is_string($upload['runtimes'])) {
  1044. $runtimes = explode(',', $upload['runtimes']);
  1045. } else {
  1046. foreach ($upload['runtimes'] as $k => $v) {
  1047. $runtimes[] = $v;
  1048. }
  1049. }
  1050. // add html4
  1051. $runtimes[] = 'html4';
  1052. // remove flash runtime if $chunk_size is 0 (no chunking)
  1053. /* if (!$chunk_size) {
  1054. unset($runtimes[array_search('flash', $runtimes)]);
  1055. } */
  1056. $defaults = array(
  1057. 'runtimes' => implode(',', $runtimes),
  1058. 'size' => $size,
  1059. 'filter' => $this->mapUploadFileTypes(true),
  1060. 'elements' => $elements
  1061. );
  1062. // only add chunk size if it has a value
  1063. /* if ($chunk_size) {
  1064. $defaults['chunk_size'] = $chunk_size;
  1065. } */
  1066. if (isset($features['dialog'])) {
  1067. $defaults['dialog'] = $features['dialog'];
  1068. }
  1069. return $defaults;
  1070. }
  1071. public function getDimensions($file) {
  1072. $filesystem = $this->getFileSystem();
  1073. return $filesystem->getDimensions($file);
  1074. }
  1075. protected function getSettings($settings = array()) {
  1076. $filesystem = $this->getFileSystem();
  1077. $default = array(
  1078. 'dir' => $filesystem->getRootDir(),
  1079. 'actions' => $this->getActions(),
  1080. 'buttons' => $this->getButtons(),
  1081. 'upload' => $this->getUploadDefaults(),
  1082. 'tree' => $this->get('folder_tree'),
  1083. 'listlimit' => $this->get('list_limit'),
  1084. 'websafe_mode' => $this->get('websafe_mode')
  1085. );
  1086. $properties = array('base', 'delete', 'rename', 'folder_new', 'copy', 'move');
  1087. foreach ($properties as $property) {
  1088. if ($filesystem->get($property)) {
  1089. $default[$property] = $filesystem->get($property);
  1090. }
  1091. }
  1092. $settings = array_merge_recursive($default, $settings);
  1093. return $settings;
  1094. }
  1095. }
  1096. ?>