PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/system/expressionengine/libraries/File_field.php

https://bitbucket.org/tdevonshire/hoolux
PHP | 698 lines | 391 code | 109 blank | 198 comment | 53 complexity | e16ee8693e7954fa38cba9caf73c113e MD5 | raw file
  1. <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
  2. /**
  3. * ExpressionEngine - by EllisLab
  4. *
  5. * @package ExpressionEngine
  6. * @author EllisLab Dev Team
  7. * @copyright Copyright (c) 2003 - 2012, EllisLab, Inc.
  8. * @license http://ellislab.com/expressionengine/user-guide/license.html
  9. * @link http://ellislab.com
  10. * @since Version 2.4
  11. * @filesource
  12. */
  13. // ------------------------------------------------------------------------
  14. /**
  15. * ExpressionEngine File_browser Class
  16. *
  17. * @package ExpressionEngine
  18. * @subpackage Core
  19. * @category File_field
  20. * @author EllisLab Dev Team
  21. * @link http://ellislab.com
  22. */
  23. class File_field {
  24. var $_files = array();
  25. var $_file_names = array();
  26. var $_file_ids = array();
  27. var $_dir_ids = array();
  28. var $_manipulations = array();
  29. var $_upload_prefs = array();
  30. public function __construct()
  31. {
  32. $this->EE =& get_instance();
  33. }
  34. // ------------------------------------------------------------------------
  35. /**
  36. * Creates a file field
  37. *
  38. * @param string $field_name The name of the field
  39. * @param string $data The data stored in the file field
  40. * e.g. {filedir_x}filename.ext
  41. * @param string $allowed_file_dirs The allowed file directory
  42. * Either 'all' or ONE directory ID
  43. * @param string $content_type The content type allowed.
  44. * Either 'all' or 'image'
  45. * @return string Fully rendered file field
  46. */
  47. public function field($field_name, $data = '', $allowed_file_dirs = 'all', $content_type = 'all', $view_type = 'list')
  48. {
  49. // Load necessary library, helper, model and langfile
  50. $this->EE->load->library('filemanager');
  51. $this->EE->load->helper(array('html', 'form'));
  52. $this->EE->load->model(array('file_model', 'file_upload_preferences_model'));
  53. $this->EE->lang->loadfile('fieldtypes');
  54. $vars = array(
  55. 'filedir' => '',
  56. 'filename' => '',
  57. 'upload_location_id' => ''
  58. );
  59. $allowed_file_dirs = ($allowed_file_dirs == 'all') ? '' : $allowed_file_dirs;
  60. $specified_directory = ($allowed_file_dirs == '') ? 'all' : $allowed_file_dirs;
  61. // Parse field data
  62. if ( ! empty($data) AND ($parsed_field = $this->parse_field($data)) !== FALSE)
  63. {
  64. $vars = $parsed_field;
  65. $vars['filename'] = $vars['filename'].'.'.$vars['extension'];
  66. }
  67. // Retrieve all directories that are both allowed for this user and
  68. // for this field
  69. $upload_dirs[''] = lang('directory');
  70. $upload_dirs = $this->EE->file_upload_preferences_model->get_dropdown_array(
  71. $this->EE->session->userdata('group_id'),
  72. $allowed_file_dirs,
  73. $upload_dirs
  74. );
  75. // Get the thumbnail
  76. $thumb_info = $this->EE->filemanager->get_thumb($vars['filename'], $vars['upload_location_id']);
  77. $vars['thumb'] = $thumb_info['thumb'];
  78. $vars['alt'] = $vars['filename'];
  79. // Create the hidden fields for the file and directory
  80. $vars['hidden'] = form_hidden($field_name.'_hidden', $vars['filename']);
  81. $vars['hidden'] .= form_hidden($field_name.'_hidden_dir', $vars['upload_location_id']);
  82. // Create a standard file upload field and dropdown for folks
  83. // without javascript
  84. $vars['upload'] = form_upload(array(
  85. 'name' => $field_name,
  86. 'value' => $vars['filename'],
  87. 'data-content-type' => $content_type,
  88. 'data-directory' => $specified_directory
  89. ));
  90. $vars['dropdown'] = form_dropdown($field_name.'_directory', $upload_dirs, $vars['upload_location_id']);
  91. // Check to see if they have access to any directories to create an upload link
  92. $vars['upload_link'] = (count($upload_dirs) > 0) ? '<a href="#" class="choose_file" data-directory="'.$specified_directory.'">'.lang('add_file').'</a>' : lang('directory_no_access');
  93. // If we have a file, show the thumbnail, filename and remove link
  94. $vars['set_class'] = $vars['filename'] ? '' : 'js_hide';
  95. return $this->EE->load->ee_view('_shared/file/field', $vars, TRUE);
  96. }
  97. // ------------------------------------------------------------------------
  98. /**
  99. * Initialize the file browser given a configuration array and an endpoint url
  100. * @param array $config Associative array containing five different keys and values:
  101. * - publish: set to TRUE if you're on the publish page, optionally
  102. * just pass an empty array or none at all for the same behavior
  103. * - trigger: the selector to pass to jQuery to create a trigger for
  104. * the file browser
  105. * - field_name: the field you're operating on. If undefined, it will
  106. * assume the name is userfile
  107. * - settings: JSON object defining the content type and directory
  108. * e.g. {"content_type": "all/image", "directory": "all/<directory_id>"}
  109. * - callback: Javascript function that will be called when an image
  110. * is selected. e.g. function (file, field) { console.log(file, field); }
  111. * file is an object of the selected file's data, and field is a
  112. * jQuery object representing the field from the field_name given
  113. * @param string $endpoint_url The URL the file browser will hit
  114. */
  115. public function browser($config = array(), $endpoint_url = 'C=content_publish&M=filemanager_actions')
  116. {
  117. $this->EE->lang->loadfile('content');
  118. // Are we on the publish page? If so, go ahead and load up the publish
  119. // page javascript files
  120. if (empty($config) OR (isset($config['publish']) AND $config['publish'] === TRUE))
  121. {
  122. $this->EE->javascript->set_global(array(
  123. 'filebrowser' => array(
  124. 'publish' => TRUE
  125. )
  126. ));
  127. }
  128. // No? Make sure we at least have a trigger and a callback
  129. elseif (isset($config['trigger'], $config['callback']))
  130. {
  131. $field_name = (isset($config['field_name'])) ? $config['field_name'].', ' : '';
  132. $settings = (isset($config['settings'])) ? $config['settings'].', ' : '';
  133. $this->EE->javascript->ready("
  134. $.ee_filebrowser.add_trigger('{$config['trigger']}', {$field_name}{$settings}{$config['callback']});
  135. ");
  136. }
  137. else
  138. {
  139. return;
  140. }
  141. $this->_browser_css();
  142. $this->_browser_javascript($endpoint_url);
  143. }
  144. // ------------------------------------------------------------------------
  145. /**
  146. * Validate's the data by checking to see if they used the normal file
  147. * field or the file browser
  148. *
  149. * USE THIS BEFORE format_data()
  150. *
  151. * @param string $data The data in the field we're validating
  152. * @param string $field_name The name of the field we're validating
  153. * @param string $required Set to 'y' if the field is required
  154. * @return array Associative array containing ONLY the name of the
  155. * file uploaded
  156. */
  157. public function validate($data, $field_name, $required = 'n')
  158. {
  159. $dir_field = $field_name.'_directory';
  160. $hidden_field = $field_name.'_hidden';
  161. $hidden_dir = ($this->EE->input->post($field_name.'_hidden_dir')) ? $this->EE->input->post($field_name.'_hidden_dir') : '';
  162. $allowed_dirs = array();
  163. // Default to blank - allows us to remove files
  164. $_POST[$field_name] = '';
  165. // Default directory
  166. $upload_directories = $this->_get_upload_prefs();
  167. // Directory selected - switch
  168. $filedir = ($this->EE->input->post($dir_field)) ? $this->EE->input->post($dir_field) : '';
  169. foreach($upload_directories as $row)
  170. {
  171. $allowed_dirs[] = $row['id'];
  172. }
  173. // Upload or maybe just a path in the hidden field?
  174. if (isset($_FILES[$field_name]) && $_FILES[$field_name]['size'] > 0 AND in_array($filedir, $allowed_dirs))
  175. {
  176. $this->EE->load->library('filemanager');
  177. $data = $this->EE->filemanager->upload_file($filedir, $field_name);
  178. if (array_key_exists('error', $data))
  179. {
  180. return $data['error'];
  181. }
  182. else
  183. {
  184. $_POST[$field_name] = $data['file_name'];
  185. }
  186. }
  187. elseif ($this->EE->input->post($hidden_field))
  188. {
  189. $_POST[$field_name] = $_POST[$hidden_field];
  190. }
  191. $_POST[$dir_field] = $filedir;
  192. unset($_POST[$hidden_field]);
  193. // If the current file directory is not one the user has access to
  194. // make sure it is an edit and value hasn't changed
  195. if ($_POST[$field_name] && ! in_array($filedir, $allowed_dirs))
  196. {
  197. if ($filedir != '' OR ( ! $this->EE->input->post('entry_id') OR $this->EE->input->post('entry_id') == ''))
  198. {
  199. return lang('directory_no_access');
  200. }
  201. // The existing directory couldn't be selected because they didn't have permission to upload
  202. // Let's make sure that the existing file in that directory is the one that's going back in
  203. $eid = (int) $this->EE->input->post('entry_id');
  204. $this->EE->db->select($field_name);
  205. $query = $this->EE->db->get_where('channel_data', array('entry_id'=>$eid));
  206. if ($query->num_rows() == 0)
  207. {
  208. return lang('directory_no_access');
  209. }
  210. if ('{filedir_'.$hidden_dir.'}'.$_POST[$field_name] != $query->row($field_name))
  211. {
  212. return lang('directory_no_access');
  213. }
  214. // Replace the empty directory with the existing directory
  215. $_POST[$field_name.'_directory'] = $hidden_dir;
  216. }
  217. if ($required == 'y' && ! $_POST[$field_name])
  218. {
  219. return lang('required');
  220. }
  221. unset($_POST[$field_name.'_hidden_dir']);
  222. return array('value' => $this->format_data($_POST[$field_name], $hidden_dir));
  223. }
  224. // ------------------------------------------------------------------------
  225. /**
  226. * Format's the data of a file field given the name of the file and
  227. * the directory_id
  228. *
  229. * @param string $data The name of the file
  230. * @param integer $directory_id The directory ID
  231. * @return string The formatted field data e.g. {filedir_1}file.ext
  232. */
  233. public function format_data($file_name, $directory_id = 0)
  234. {
  235. if ($file_name != '')
  236. {
  237. if ( ! empty($directory_id))
  238. {
  239. return '{filedir_'.$directory_id.'}'.$file_name;
  240. }
  241. return $file_name;
  242. }
  243. }
  244. // ------------------------------------------------------------------------
  245. /**
  246. * Caches file data about to be parsed by the channel module. Instead of querying
  247. * for individual files inside the entries loop, we'll query for everything we need
  248. * before the loop starts, significantly reducing queries.
  249. *
  250. * @param array $data Array of file field data we're going to query for
  251. * @return void
  252. */
  253. public function cache_data($data = array())
  254. {
  255. if (empty($data))
  256. {
  257. return FALSE;
  258. }
  259. $this->EE->load->model('file_model');
  260. // We'll keep track of file names and file IDs collected
  261. $file_names = array();
  262. $file_ids = array();
  263. $dir_ids = array();
  264. // Don't deal with duplicate data, files are the same from entry to entry
  265. $data = array_unique($data);
  266. foreach ($data as $field_data)
  267. {
  268. // If the file field is in the "{filedir_n}image.jpg" format
  269. if (preg_match('/^{filedir_(\d+)}/', $field_data, $matches))
  270. {
  271. $dir_ids[] = $matches[1];
  272. $file_names[] = str_replace($matches[0], '', $field_data);
  273. }
  274. // If file field is just a file ID, much simpler
  275. else if (! empty($field_data) && is_numeric($field_data))
  276. {
  277. $file_ids[] = $field_data;
  278. }
  279. }
  280. $dir_ids = array_unique($dir_ids);
  281. $file_names = array_diff($file_names, $this->_file_names);
  282. $this->_file_names = array_merge($this->_file_names, $file_names);
  283. // Query for files based on file names and directory ID
  284. if ( ! empty($file_names))
  285. {
  286. $file_names = $this->EE->file_model->get_files_by_name($file_names, $dir_ids)->result_array();
  287. }
  288. $file_ids = array_diff($file_ids, $this->_file_ids);
  289. $this->_file_ids = array_merge($this->_file_ids, $file_ids);
  290. // Query for files based on file ID
  291. if ( ! empty($file_ids))
  292. {
  293. $file_ids = $this->EE->file_model->get_files_by_id($data)->result_array();
  294. }
  295. // Merge our results into our cached array
  296. $this->_files = array_merge(
  297. $this->_files, // Merge itself in case more than one file field is processed
  298. $file_names,
  299. $file_ids
  300. );
  301. }
  302. // ------------------------------------------------------------------------
  303. /**
  304. * Searches the local _files array for a particular file based on a specific
  305. * key and value, and queries the database for the file if it doesn't exist.
  306. *
  307. * @param mixed $file_reference File ID or file name of file to return
  308. * @param string $dir_id Directory ID file is in, if searching by file name
  309. * @return array File information
  310. */
  311. public function get_file($file_reference = NULL, $dir_id = NULL)
  312. {
  313. // This is what we're returning, we'll be overwriting it by the end of
  314. // the function if all goes well
  315. $file = FALSE;
  316. if ($file_reference != NULL)
  317. {
  318. // Assign the key (field) and value we'll be searching by
  319. $key = (is_numeric($file_reference)) ? 'file_id' : 'file_name';
  320. // Loop through cached files
  321. foreach ($this->_files as $file)
  322. {
  323. // See if key exists
  324. if (isset($file[$key]))
  325. {
  326. // If value exists, return the file and stop the search
  327. if (($key == 'file_id' AND $file[$key] == $file_reference) OR
  328. // If we're searching by file name, make sure we're grabbing the
  329. // correct file in the case that we cached two files with the same
  330. // name but in different upload directories.
  331. ($key == 'file_name' AND $file[$key] == $file_reference
  332. AND $file['upload_location_id'] == $dir_id))
  333. {
  334. return $file;
  335. }
  336. }
  337. }
  338. // If we got here, we need to query for the file
  339. $this->EE->load->model('file_model');
  340. // Query based on file ID
  341. if (is_numeric($file_reference))
  342. {
  343. $file = $this->EE->file_model->get_files_by_id($file_reference)->row_array();
  344. }
  345. // Query based on file name and directory ID
  346. else
  347. {
  348. $file = $this->EE->file_model->get_files_by_name($file_reference, $dir_id)->row_array();
  349. }
  350. $this->_files[] = $file;
  351. }
  352. return $file;
  353. }
  354. // ------------------------------------------------------------------------
  355. /**
  356. * Parse field contents, which may be in the {filedir_n} format for may be
  357. * a file ID.
  358. *
  359. * @access public
  360. * @param string $data Field contents
  361. * @return array|boolean Information about file and upload directory, false
  362. * if there is no file
  363. */
  364. public function parse_field($data)
  365. {
  366. // If the file field is in the "{filedir_n}image.jpg" format
  367. if (preg_match('/^{filedir_(\d+)}/', $data, $matches))
  368. {
  369. // Set upload directory ID and file name
  370. $dir_id = $matches[1];
  371. $file_name = str_replace($matches[0], '', $data);
  372. $file = $this->get_file($file_name, $dir_id);
  373. }
  374. // If file field is just a file ID
  375. else if (! empty($data) && is_numeric($data))
  376. {
  377. $file = $this->get_file($data);
  378. }
  379. // If there is no file, but data was passed in, create a dummy file
  380. // array to pass back the data we were given. This is likely to
  381. // happen when the old style of category_image is passed in and we
  382. // don't recognize it because it's in a URL format. So, we'll return
  383. // the URL so that people's category images continue to work.
  384. if (empty($file) AND ! empty($data))
  385. {
  386. $file = array(
  387. 'url' => $data,
  388. 'file_name' => $data,
  389. 'extension' => '',
  390. 'path' => '',
  391. 'upload_location_id' => '',
  392. 'file_hw_original' => ''
  393. );
  394. return $file;
  395. }
  396. else if (empty($file) AND empty($data))
  397. {
  398. return FALSE;
  399. }
  400. // Get the cached upload preferences
  401. $upload_dir = $this->_get_upload_prefs();
  402. if ( ! isset($upload_dir[$file['upload_location_id']]))
  403. {
  404. return FALSE;
  405. }
  406. $upload_dir = $upload_dir[$file['upload_location_id']];
  407. $file['file_name'] = rawurlencode($file['file_name']);
  408. // Set additional data based on what we've gathered
  409. $file['raw_output'] = $data;
  410. $file['path'] = (isset($upload_dir['url'])) ? $upload_dir['url'] : '';
  411. $file['extension'] = substr(strrchr($file['file_name'], '.'), 1);
  412. $file['filename'] = basename($file['file_name'], '.'.$file['extension']); // backwards compatibility
  413. $file['url'] = $file['path'].$file['file_name'];
  414. $dimensions = explode(" ", $file['file_hw_original']);
  415. $file['width'] = isset($dimensions[1]) ? $dimensions[1] : '';
  416. $file['height'] = isset($dimensions[0]) ? $dimensions[0] : '';
  417. // Pre and post formatting
  418. $file['image_pre_format'] = $upload_dir['pre_format'];
  419. $file['image_post_format'] = $upload_dir['post_format'];
  420. $file['file_pre_format'] = $upload_dir['file_pre_format'];
  421. $file['file_post_format'] = $upload_dir['file_post_format'];
  422. // Image/file properties
  423. $file['image_properties'] = $upload_dir['properties'];
  424. $file['file_properties'] = $upload_dir['file_properties'];
  425. $manipulations = $this->_get_dimensions_by_dir_id($file['upload_location_id']);
  426. foreach($manipulations as $m)
  427. {
  428. $file['url:'.$m['short_name']] = $file['path'].'_'.$m['short_name'].'/'.$file['file_name'];
  429. }
  430. return $file;
  431. }
  432. // ------------------------------------------------------------------------
  433. /**
  434. * Unlike parse(), this parses all occurances of {filedir_n} from a given
  435. * string to their actual values and returns the processed string.
  436. *
  437. * @access public
  438. * @param string $data The string to parse {filedir_n} in
  439. * @return string The original string with all {filedir_n}'s parsed
  440. */
  441. public function parse_string($data)
  442. {
  443. // Find each instance of {filedir_n}
  444. if (preg_match_all('/{filedir_(\d+)}/', $data, $matches, PREG_SET_ORDER))
  445. {
  446. $file_dirs = $this->_file_dirs();
  447. // Replace each match
  448. foreach ($matches as $match)
  449. {
  450. if (isset($file_dirs[$match[1]]))
  451. {
  452. $data = str_replace('{filedir_'.$match[1].'}', $file_dirs[$match[1]], $data);
  453. }
  454. }
  455. }
  456. return $data;
  457. }
  458. // ------------------------------------------------------------------------
  459. /**
  460. * Get the file directory data and keep it stored in the cache
  461. *
  462. * @return array Array of file directories
  463. */
  464. private function _file_dirs()
  465. {
  466. if ( ! $this->EE->session->cache(__CLASS__, 'file_dirs'))
  467. {
  468. $this->EE->session->set_cache(
  469. __CLASS__,
  470. 'file_dirs',
  471. $this->EE->functions->fetch_file_paths()
  472. );
  473. }
  474. return $this->EE->session->cache(__CLASS__, 'file_dirs');
  475. }
  476. // ------------------------------------------------------------------------
  477. /**
  478. * Get upload preferences and keep it cached in the class
  479. *
  480. * @return array Array of upload preferences
  481. */
  482. private function _get_upload_prefs()
  483. {
  484. if (empty($this->_upload_prefs))
  485. {
  486. $this->EE->load->model('file_upload_preferences_model');
  487. $this->_upload_prefs = $this->EE->file_upload_preferences_model->get_file_upload_preferences(
  488. NULL,
  489. NULL,
  490. TRUE
  491. );
  492. }
  493. return $this->_upload_prefs;
  494. }
  495. // ------------------------------------------------------------------------
  496. /**
  497. * Gets dimensions for an upload directory and caches them
  498. *
  499. * @param int $dir_id ID of upload directory
  500. * @return array Array of image manipulation settings
  501. */
  502. private function _get_dimensions_by_dir_id($dir_id)
  503. {
  504. if ( ! isset($this->_manipulations[$dir_id]))
  505. {
  506. $this->EE->load->model('file_model');
  507. $this->_manipulations[$dir_id] = $this->EE->file_model->get_dimensions_by_dir_id($dir_id)->result_array();
  508. }
  509. return $this->_manipulations[$dir_id];
  510. }
  511. // ------------------------------------------------------------------------
  512. /**
  513. * Add the file browser CSS to the head
  514. */
  515. private function _browser_css()
  516. {
  517. $this->EE->cp->add_to_head($this->EE->view->head_link('css/file_browser.css'));
  518. }
  519. // ------------------------------------------------------------------------
  520. /**
  521. * Loads up javascript dependencies and global variables for the file
  522. * browser and file uploader
  523. */
  524. private function _browser_javascript($endpoint_url)
  525. {
  526. $this->EE->cp->add_js_script('plugin', array('tmpl', 'ee_table'));
  527. // Include dependencies
  528. $this->EE->cp->add_js_script(array(
  529. 'file' => array(
  530. 'underscore',
  531. 'files/publish_fields'
  532. ),
  533. 'plugin' => array(
  534. 'scrollable',
  535. 'scrollable.navigator',
  536. 'ee_filebrowser',
  537. 'ee_fileuploader',
  538. 'tmpl'
  539. )
  540. ));
  541. $this->EE->load->helper('html');
  542. $this->EE->javascript->set_global(array(
  543. 'lang' => array(
  544. 'resize_image' => lang('resize_image'),
  545. 'or' => lang('or'),
  546. 'return_to_publish' => lang('return_to_publish')
  547. ),
  548. 'filebrowser' => array(
  549. 'endpoint_url' => $endpoint_url,
  550. 'window_title' => lang('file_manager'),
  551. 'next' => anchor(
  552. '#',
  553. img(
  554. $this->EE->cp->cp_theme_url . 'images/pagination_next_button.gif',
  555. array(
  556. 'alt' => lang('next'),
  557. 'width' => 13,
  558. 'height' => 13
  559. )
  560. ),
  561. array(
  562. 'class' => 'next'
  563. )
  564. ),
  565. 'previous' => anchor(
  566. '#',
  567. img(
  568. $this->EE->cp->cp_theme_url . 'images/pagination_prev_button.gif',
  569. array(
  570. 'alt' => lang('previous'),
  571. 'width' => 13,
  572. 'height' => 13
  573. )
  574. ),
  575. array(
  576. 'class' => 'previous'
  577. )
  578. )
  579. ),
  580. 'fileuploader' => array(
  581. 'window_title' => lang('file_upload'),
  582. 'delete_url' => 'C=content_files&M=delete_files'
  583. )
  584. ));
  585. }
  586. }
  587. // END File_field class
  588. /* End of file File_field.php */
  589. /* Location: ./system/expressionengine/libraries/File_field.php */