file_entity.file_api.inc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. <?php
  2. /**
  3. * @file
  4. * API extensions of Drupal core's file.inc.
  5. */
  6. /**
  7. * Returns information about file types from hook_file_type_info().
  8. *
  9. * @param $file_type
  10. * (optional) A file type name. If ommitted, all file types will be returned.
  11. *
  12. * @return
  13. * Either a file type description, as provided by hook_file_type_info(), or an
  14. * array of all existing file types, keyed by file type name.
  15. */
  16. function file_info_file_types($file_type = NULL) {
  17. $info = &drupal_static(__FUNCTION__);
  18. if (!isset($info)) {
  19. $info = module_invoke_all('file_type_info');
  20. // Add support for the standard file types until this can be fully
  21. // abstracted out of Media module.
  22. $info += array(
  23. 'application' => array('label' => t('Application (multipurpose)')),
  24. 'audio' => array('label' => t('Audio')),
  25. 'image' => array('label' => t('Image')),
  26. 'text' => array('label' => t('Text')),
  27. 'video' => array('label' => t('Video')),
  28. );
  29. drupal_alter('file_type_info', $info);
  30. uasort($info, '_file_entity_sort_weight_label');
  31. }
  32. if ($file_type) {
  33. if (isset($info[$file_type])) {
  34. return $info[$file_type];
  35. }
  36. }
  37. else {
  38. return $info;
  39. }
  40. }
  41. /**
  42. * Determines the file type of a passed in file object.
  43. *
  44. * The file type is determined by extracting the 'first' part of the file's
  45. * MIME type. For example, a PNG image with a MIME type of 'image/png' will
  46. * have a file type of 'image'.
  47. *
  48. * @link http://www.iana.org/assignments/media-types/index.html IANA list of official MIME media types @endlink
  49. */
  50. function file_get_type($file) {
  51. // Ensure that a MIME type has been determined first.
  52. if (empty($file->filemime)) {
  53. $file->filemime = file_get_mimetype($file->uri);
  54. }
  55. return substr($file->filemime, 0, strpos($file->filemime, '/'));
  56. }
  57. /**
  58. * Returns information about file formatters from hook_file_formatter_info().
  59. *
  60. * @param $formatter_type
  61. * (optional) A file formatter type name. If ommitted, all file formatter
  62. * will be returned.
  63. *
  64. * @return
  65. * Either a file formatter description, as provided by
  66. * hook_file_formatter_info(), or an array of all existing file formatters,
  67. * keyed by formatter type name.
  68. */
  69. function file_info_formatter_types($formatter_type = NULL) {
  70. $info = &drupal_static(__FUNCTION__);
  71. if (!isset($info)) {
  72. $info = module_invoke_all('file_formatter_info');
  73. drupal_alter('file_formatter_info', $info);
  74. uasort($info, '_file_entity_sort_weight_label');
  75. }
  76. if ($formatter_type) {
  77. if (isset($info[$formatter_type])) {
  78. return $info[$formatter_type];
  79. }
  80. }
  81. else {
  82. return $info;
  83. }
  84. }
  85. /**
  86. * Clears the file info cache.
  87. */
  88. function file_info_cache_clear() {
  89. drupal_static_reset('file_info_file_types');
  90. drupal_static_reset('file_info_formatter_types');
  91. }
  92. /**
  93. * Construct a drupal_render() style array from an array of loaded files.
  94. *
  95. * @param $files
  96. * An array of files as returned by file_load_multiple().
  97. * @param $view_mode
  98. * View mode.
  99. * @param $weight
  100. * An integer representing the weight of the first file in the list.
  101. * @param $langcode
  102. * A string indicating the language field values are to be shown in. If no
  103. * language is provided the current content language is used.
  104. *
  105. * @return
  106. * An array in the format expected by drupal_render().
  107. */
  108. function file_view_multiple($files, $view_mode = 'full', $weight = 0, $langcode = NULL) {
  109. if (empty($files)) {
  110. return array();
  111. }
  112. field_attach_prepare_view('file', $files, $view_mode, $langcode);
  113. entity_prepare_view('file', $files, $langcode);
  114. $build = array();
  115. foreach ($files as $file) {
  116. $build[$file->fid] = file_view($file, $view_mode, $langcode);
  117. $build[$file->fid]['#weight'] = $weight;
  118. $weight++;
  119. }
  120. $build['#sorted'] = TRUE;
  121. return $build;
  122. }
  123. /**
  124. * Generate an array for rendering the given file.
  125. *
  126. * @param $file
  127. * A file object.
  128. * @param $view_mode
  129. * View mode.
  130. * @param $langcode
  131. * (optional) A language code to use for rendering. Defaults to the global
  132. * content language of the current request.
  133. *
  134. * @return
  135. * An array as expected by drupal_render().
  136. */
  137. function file_view($file, $view_mode = 'full', $langcode = NULL) {
  138. if (!isset($langcode)) {
  139. $langcode = $GLOBALS['language_content']->language;
  140. }
  141. // Populate $file->content with a render() array.
  142. file_build_content($file, $view_mode, $langcode);
  143. $build = $file->content;
  144. // We don't need duplicate rendering info in $file->content.
  145. unset($file->content);
  146. $build += array(
  147. '#theme' => 'file_entity',
  148. '#file' => $file,
  149. '#view_mode' => $view_mode,
  150. '#language' => $langcode,
  151. );
  152. // Add contextual links for this file, except when the file is already being
  153. // displayed on its own page. Modules may alter this behavior (for example,
  154. // to restrict contextual links to certain view modes) by implementing
  155. // hook_file_view_alter().
  156. if (!empty($file->fid) && !($view_mode == 'full' && file_is_page($file))) {
  157. $build['#contextual_links']['file'] = array('file', array($file->fid));
  158. }
  159. // Allow modules to modify the structured file.
  160. $type = 'file';
  161. drupal_alter(array('file_view', 'entity_view'), $build, $type);
  162. return $build;
  163. }
  164. /**
  165. * Builds a structured array representing the file's content.
  166. *
  167. * @param $file
  168. * A file object.
  169. * @param $view_mode
  170. * View mode, e.g. 'default', 'full', etc.
  171. * @param $langcode
  172. * (optional) A language code to use for rendering. Defaults to the global
  173. * content language of the current request.
  174. */
  175. function file_build_content($file, $view_mode = 'full', $langcode = NULL) {
  176. if (!isset($langcode)) {
  177. $langcode = $GLOBALS['language_content']->language;
  178. }
  179. // Remove previously built content, if exists.
  180. $file->content = array();
  181. // Build the actual file display.
  182. // @todo Figure out how to clean this crap up.
  183. $file->content['file'] = file_view_file($file, $view_mode, $langcode);
  184. if (isset($file->content['file'])) {
  185. if (isset($file->content['file']['#theme']) && $file->content['file']['#theme'] != 'file_link') {
  186. unset($file->content['file']['#file']);
  187. }
  188. unset($file->content['file']['#view_mode']);
  189. unset($file->content['file']['#language']);
  190. }
  191. else {
  192. unset($file->content['file']);
  193. }
  194. // Build fields content.
  195. // In case of a multiple view, file_view_multiple() already ran the
  196. // 'prepare_view' step. An internal flag prevents the operation from running
  197. // twice.
  198. field_attach_prepare_view('file', array($file->fid => $file), $view_mode, $langcode);
  199. entity_prepare_view('file', array($file->fid => $file), $langcode);
  200. $file->content += field_attach_view('file', $file, $view_mode, $langcode);
  201. $links = array();
  202. $file->content['links'] = array(
  203. '#theme' => 'links__file',
  204. '#pre_render' => array('drupal_pre_render_links'),
  205. '#attributes' => array('class' => array('links', 'inline')),
  206. );
  207. $file->content['links']['file'] = array(
  208. '#theme' => 'links__file__file',
  209. '#links' => $links,
  210. '#attributes' => array('class' => array('links', 'inline')),
  211. );
  212. // Allow modules to make their own additions to the file.
  213. module_invoke_all('file_view', $file, $view_mode, $langcode);
  214. module_invoke_all('entity_view', $file, 'file', $view_mode, $langcode);
  215. }
  216. /**
  217. * Generate an array for rendering just the file portion of a file entity.
  218. *
  219. * @param $file
  220. * A file object.
  221. * @param $displays
  222. * Can be either:
  223. * - the name of a view mode;
  224. * - or an array of custom display settings, as returned by file_displays().
  225. * @param $langcode
  226. * (optional) A language code to use for rendering. Defaults to the global
  227. * content language of the current request.
  228. *
  229. * @return
  230. * An array as expected by drupal_render().
  231. */
  232. function file_view_file($file, $displays = 'full', $langcode = NULL) {
  233. if (!isset($langcode)) {
  234. $langcode = $GLOBALS['language_content']->language;
  235. }
  236. // Prepare incoming display specifications.
  237. if (is_string($displays)) {
  238. $view_mode = $displays;
  239. $displays = file_displays($file->type, $view_mode);
  240. }
  241. else {
  242. $view_mode = '_custom_display';
  243. }
  244. drupal_alter('file_displays', $displays, $file, $view_mode);
  245. _file_sort_array_by_weight($displays);
  246. // Attempt to display the file with each of the possible displays. Stop after
  247. // the first successful one. See file_displays() for details.
  248. $element = NULL;
  249. foreach ($displays as $formatter_type => $display) {
  250. if (!empty($display['status'])) {
  251. $formatter_info = file_info_formatter_types($formatter_type);
  252. // Under normal circumstances, the UI prevents enabling formatters for
  253. // incompatible file types. In case this was somehow circumvented (for
  254. // example, a module updated its formatter definition without updating
  255. // existing display settings), perform an extra check here.
  256. if (isset($formatter_info['file types']) && !in_array($file->type, $formatter_info['file types'])) {
  257. continue;
  258. }
  259. if (isset($formatter_info['view callback']) && ($function = $formatter_info['view callback']) && function_exists($function)) {
  260. $display['type'] = $formatter_type;
  261. if (!empty($formatter_info['default settings'])) {
  262. if (empty($display['settings'])) {
  263. $display['settings'] = array();
  264. }
  265. $display['settings'] += $formatter_info['default settings'];
  266. }
  267. $element = $function($file, $display, $langcode);
  268. if (isset($element)) {
  269. break;
  270. }
  271. }
  272. }
  273. }
  274. // If none of the configured formatters were able to display the file, attempt
  275. // to display the file using the file type's default view callback.
  276. if (!isset($element)) {
  277. $file_type_info = file_info_file_types($file->type);
  278. if (isset($file_type_info['default view callback']) && ($function = $file_type_info['default view callback']) && function_exists($function)) {
  279. $element = $function($file, $view_mode, $langcode);
  280. }
  281. }
  282. // If a render element was returned by a formatter or the file type's default
  283. // view callback, add some defaults to it and return it.
  284. if (isset($element)) {
  285. $element += array(
  286. '#file' => $file,
  287. '#view_mode' => $view_mode,
  288. '#language' => $langcode,
  289. );
  290. return $element;
  291. }
  292. }
  293. /**
  294. * Returns an array of possible displays to use for a file type in a given view mode.
  295. *
  296. * It is common for a site to be configured with broadly defined file types
  297. * (e.g., 'video'), and to have different files of this type require different
  298. * displays (for example, the code required to display a YouTube video is
  299. * different than the code required to display a local QuickTime video).
  300. * Therefore, the site administrator can configure multiple displays for a given
  301. * file type. This function returns all of the displays that the administrator
  302. * enabled for the given file type in the given view mode. file_view_file() then
  303. * invokes each of these, and passes the specific file to display. Each display
  304. * implementation can inspect the file, and either return a render array (if it
  305. * is capable of displaying the file), or return nothing (if it is incapable of
  306. * displaying the file). The first render array returned is the one used.
  307. *
  308. * @param $file_type
  309. * The type of file.
  310. * @param $view_mode
  311. * The view mode.
  312. *
  313. * @return
  314. * An array keyed by the formatter type name. Each item in the array contains
  315. * the following key/value pairs:
  316. * - status: Whether this display is enabled. If not TRUE, file_view_file()
  317. * skips over it.
  318. * - weight: An integer that determines the order of precedence within the
  319. * returned array. The lowest weight display capable of displaying the file
  320. * is used.
  321. * - settings: An array of key/value pairs specific to the formatter type. See
  322. * hook_file_formatter_info() for details.
  323. *
  324. * @see hook_file_formatter_info()
  325. * @see file_view_file()
  326. */
  327. function file_displays($file_type, $view_mode) {
  328. $cache = &drupal_static(__FUNCTION__, array());
  329. // If the requested view mode isn't configured to use a custom display for its
  330. // fields, then don't use a custom display for its file either.
  331. if ($view_mode != 'default') {
  332. $view_mode_settings = field_view_mode_settings('file', $file_type);
  333. $view_mode = !empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default';
  334. }
  335. if (!isset($cache[$file_type][$view_mode])) {
  336. // Load the display configurations for the file type and view mode. If none
  337. // exist for the view mode, use the default view mode.
  338. $displays = file_displays_load($file_type, $view_mode, TRUE);
  339. if (empty($displays) && $view_mode != 'default') {
  340. $cache[$file_type][$view_mode] = file_displays($file_type, 'default');
  341. }
  342. else {
  343. // Convert the display objects to arrays and remove unnecessary keys.
  344. foreach ($displays as $formatter_name => $display) {
  345. $displays[$formatter_name] = array_intersect_key((array) $display, drupal_map_assoc(array('status', 'weight', 'settings')));
  346. }
  347. $cache[$file_type][$view_mode] = $displays;
  348. }
  349. }
  350. return $cache[$file_type][$view_mode];
  351. }
  352. /**
  353. * Returns an array of {file_display} objects for the file type and view mode.
  354. */
  355. function file_displays_load($file_type, $view_mode, $key_by_formatter_name = FALSE) {
  356. ctools_include('export');
  357. $display_names = array();
  358. $prefix = $file_type . '__' . $view_mode . '__';
  359. foreach (array_keys(file_info_formatter_types()) as $formatter_name) {
  360. $display_names[] = $prefix . $formatter_name;
  361. }
  362. $displays = ctools_export_load_object('file_display', 'names', $display_names);
  363. if ($key_by_formatter_name) {
  364. $prefix_length = strlen($prefix);
  365. $rekeyed_displays = array();
  366. foreach ($displays as $name => $display) {
  367. $rekeyed_displays[substr($name, $prefix_length)] = $display;
  368. }
  369. $displays = $rekeyed_displays;
  370. }
  371. return $displays;
  372. }
  373. /**
  374. * Saves a {file_display} object to the database.
  375. */
  376. function file_display_save($display) {
  377. ctools_include('export');
  378. ctools_export_crud_save('file_display', $display);
  379. }
  380. /**
  381. * Creates a new {file_display} object.
  382. */
  383. function file_display_new($file_type, $view_mode, $formatter_name) {
  384. ctools_include('export');
  385. $display = ctools_export_crud_new('file_display');
  386. $display->name = implode('__', array($file_type, $view_mode, $formatter_name));
  387. return $display;
  388. }
  389. /**
  390. * Helper function to sort an array by the value of each item's 'weight' key, while preserving relative order of items that have equal weight.
  391. */
  392. function _file_sort_array_by_weight(&$a) {
  393. $i=0;
  394. foreach ($a as $key => $item) {
  395. if (!isset($a[$key]['weight'])) {
  396. $a[$key]['weight'] = 0;
  397. }
  398. $original_weight[$key] = $a[$key]['weight'];
  399. $a[$key]['weight'] += $i/1000;
  400. $i++;
  401. }
  402. uasort($a, 'drupal_sort_weight');
  403. foreach ($a as $key => $item) {
  404. $a[$key]['weight'] = $original_weight[$key];
  405. }
  406. }
  407. /**
  408. * User sort function to sort by weight, then label/name.
  409. */
  410. function _file_entity_sort_weight_label($a, $b) {
  411. $a_weight = isset($a['weight']) ? $a['weight'] : 0;
  412. $b_weight = isset($b['weight']) ? $b['weight'] : 0;
  413. if ($a_weight == $b_weight) {
  414. $a_label = isset($a['label']) ? $a['label'] : '';
  415. $b_label = isset($b['label']) ? $b['label'] : '';
  416. return strcasecmp($a_label, $b_label);
  417. }
  418. else {
  419. return $a_weight < $b_weight ? -1 : 1;
  420. }
  421. }
  422. /**
  423. * Returns a file object which can be passed to file_save().
  424. *
  425. * @param $uri
  426. * A string containing the URI, path, or filename.
  427. * @param $use_existing
  428. * (Optional) If TRUE and there's an existing file in the {file_managed}
  429. * table with the passed in URI, then that file object is returned.
  430. * Otherwise, a new file object is returned. Default is TRUE.
  431. *
  432. * @return
  433. * A file object, or FALSE on error.
  434. *
  435. * @todo This should probably be named file_load_by_uri($uri, $create_if_not_exists).
  436. * @todo Remove this function when http://drupal.org/node/685818 is fixed.
  437. */
  438. function file_uri_to_object($uri, $use_existing = TRUE) {
  439. $file = FALSE;
  440. $uri = file_stream_wrapper_uri_normalize($uri);
  441. if ($use_existing) {
  442. // We should always attempt to re-use a file if possible.
  443. $files = entity_load('file', FALSE, array('uri' => $uri));
  444. $file = !empty($files) ? reset($files) : FALSE;
  445. }
  446. if (empty($file)) {
  447. $file = new stdClass();
  448. $file->uid = $GLOBALS['user']->uid;
  449. $file->filename = basename($uri);
  450. $file->uri = $uri;
  451. $file->filemime = file_get_mimetype($uri);
  452. // This is gagged because some uris will not support it.
  453. $file->filesize = @filesize($uri);
  454. $file->timestamp = REQUEST_TIME;
  455. $file->status = FILE_STATUS_PERMANENT;
  456. }
  457. return $file;
  458. }
  459. /**
  460. * A list of operations which can be called on files.
  461. *
  462. * @return
  463. * An associave array of operations keyed by a system name.
  464. * - label: A string to show in the operations dropdown.
  465. * - callback (string): A callback function to call for the operation. This
  466. * function will be passed an array of file_ids which were selected.
  467. * - confirm (boolean): Whether or not this operation requires a confirm form
  468. * In the case where confirm is set to true, callback should be a function
  469. * which can return a confirm form.
  470. */
  471. function hook_file_operations_info() {
  472. $operations = array(
  473. 'archive_and_email' => array(
  474. 'label' => t('Archive the selected files and email them'),
  475. 'callback' => 'file_archiver_confirm_form',
  476. 'confirm' => TRUE,
  477. ),
  478. );
  479. return $operations;
  480. }