file_entity.file_api.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <?php
  2. /**
  3. * @file
  4. * API extensions of Drupal core's file.inc.
  5. */
  6. /**
  7. * The {file_managed}.type value when the file type has not yet been determined.
  8. */
  9. define('FILE_TYPE_NONE', 'undefined');
  10. /**
  11. * Returns information about file types from hook_file_type_info().
  12. *
  13. * @param $file_type
  14. * (optional) A file type name. If ommitted, all file types will be returned.
  15. *
  16. * @return
  17. * Either a file type description, as provided by hook_file_type_info(), or an
  18. * array of all existing file types, keyed by file type name.
  19. */
  20. function file_info_file_types($file_type = NULL) {
  21. $info = &drupal_static(__FUNCTION__);
  22. if (!isset($info)) {
  23. $info = module_invoke_all('file_type_info');
  24. drupal_alter('file_type_info', $info);
  25. _file_sort_array_by_weight($info);
  26. }
  27. if (isset($file_type)) {
  28. if (isset($info[$file_type])) {
  29. return $info[$file_type];
  30. }
  31. }
  32. else {
  33. return $info;
  34. }
  35. }
  36. /**
  37. * Returns an object with file type information, so that %file_type can be used in hook_menu() paths.
  38. *
  39. * In addition to the information returned by file_info_file_types(), the 'type'
  40. * property is set for use by field_extract_bundle().
  41. */
  42. function file_type_load($type) {
  43. $info = file_info_file_types($type);
  44. if (isset($info)) {
  45. $info = (object) $info;
  46. $info->type = $type;
  47. return $info;
  48. }
  49. else {
  50. return FALSE;
  51. }
  52. }
  53. /**
  54. * Determines the file type of a passed in file object.
  55. */
  56. function file_get_type($file) {
  57. foreach (file_info_file_types() as $type => $info) {
  58. if (isset($info['claim callback']) && ($function = $info['claim callback']) && function_exists($function) && $function($file, $type)) {
  59. return $type;
  60. }
  61. }
  62. }
  63. /**
  64. * Returns information about file formatters from hook_file_formatter_info().
  65. *
  66. * @param $formatter_type
  67. * (optional) A file formatter type name. If ommitted, all file formatter
  68. * will be returned.
  69. *
  70. * @return
  71. * Either a file formatter description, as provided by
  72. * hook_file_formatter_info(), or an array of all existing file formatters,
  73. * keyed by formatter type name.
  74. */
  75. function file_info_formatter_types($formatter_type = NULL) {
  76. $info = &drupal_static(__FUNCTION__);
  77. if (!isset($info)) {
  78. $info = module_invoke_all('file_formatter_info');
  79. drupal_alter('file_formatter_info', $info);
  80. _file_sort_array_by_weight($info);
  81. }
  82. if ($formatter_type) {
  83. if (isset($info[$formatter_type])) {
  84. return $info[$formatter_type];
  85. }
  86. }
  87. else {
  88. return $info;
  89. }
  90. }
  91. /**
  92. * Clears the file info cache.
  93. */
  94. function file_info_cache_clear() {
  95. drupal_static_reset('file_info_file_types');
  96. drupal_static_reset('file_info_formatter_types');
  97. }
  98. /**
  99. * Construct a drupal_render() style array from an array of loaded files.
  100. *
  101. * @param $files
  102. * An array of files as returned by file_load_multiple().
  103. * @param $view_mode
  104. * View mode.
  105. * @param $weight
  106. * An integer representing the weight of the first file in the list.
  107. * @param $langcode
  108. * A string indicating the language field values are to be shown in. If no
  109. * language is provided the current content language is used.
  110. *
  111. * @return
  112. * An array in the format expected by drupal_render().
  113. */
  114. function file_view_multiple($files, $view_mode = 'default', $weight = 0, $langcode = NULL) {
  115. if (empty($files)) {
  116. return array();
  117. }
  118. field_attach_prepare_view('file', $files, $view_mode);
  119. entity_prepare_view('file', $files);
  120. $build = array();
  121. foreach ($files as $file) {
  122. $build[$file->fid] = file_view($file, $view_mode, $langcode);
  123. $build[$file->fid]['#weight'] = $weight;
  124. $weight++;
  125. }
  126. $build['#sorted'] = TRUE;
  127. return $build;
  128. }
  129. /**
  130. * Generate an array for rendering the given file.
  131. *
  132. * @param $file
  133. * A file object.
  134. * @param $view_mode
  135. * View mode.
  136. * @param $langcode
  137. * (optional) A language code to use for rendering. Defaults to the global
  138. * content language of the current request.
  139. *
  140. * @return
  141. * An array as expected by drupal_render().
  142. */
  143. function file_view($file, $view_mode = 'default', $langcode = NULL) {
  144. if (!isset($langcode)) {
  145. $langcode = $GLOBALS['language_content']->language;
  146. }
  147. // Prepare the file object for viewing, in case file_view() was called by
  148. // something other than file_view_multiple(). These functions exit quickly if
  149. // they've already run, so it's okay to call them even if they've already been
  150. // called by file_view_multiple().
  151. field_attach_prepare_view('file', array($file->fid => $file), $view_mode);
  152. entity_prepare_view('file', array($file->fid => $file));
  153. // Create the render array with the file itself and with fields.
  154. $build = array(
  155. '#file' => $file,
  156. '#view_mode' => $view_mode,
  157. '#language' => $langcode,
  158. );
  159. $build += field_attach_view('file', $file, $view_mode, $langcode);
  160. $build['file'] = file_view_file($file, $view_mode, $langcode);
  161. // Allow modules to add and alter.
  162. module_invoke_all('file_view', $file, $view_mode, $langcode);
  163. module_invoke_all('entity_view', $file, 'file', $view_mode, $langcode);
  164. $type = 'file';
  165. drupal_alter(array('file_view', 'entity_view'), $build, $type);
  166. return $build;
  167. }
  168. /**
  169. * Generate an array for rendering just the file portion of a file entity.
  170. *
  171. * @param $file
  172. * A file object.
  173. * @param $displays
  174. * Can be either:
  175. * - the name of a view mode;
  176. * - or an array of custom display settings, as returned by file_displays().
  177. * @param $langcode
  178. * (optional) A language code to use for rendering. Defaults to the global
  179. * content language of the current request.
  180. *
  181. * @return
  182. * An array as expected by drupal_render().
  183. */
  184. function file_view_file($file, $displays = 'default', $langcode = NULL) {
  185. if (!isset($langcode)) {
  186. $langcode = $GLOBALS['language_content']->language;
  187. }
  188. // Prepare incoming display specifications.
  189. if (is_string($displays)) {
  190. $view_mode = $displays;
  191. $displays = file_displays($file->type, $view_mode);
  192. }
  193. else {
  194. $view_mode = '_custom_display';
  195. }
  196. drupal_alter('file_displays', $displays, $file, $view_mode);
  197. _file_sort_array_by_weight($displays);
  198. // Attempt to display the file with each of the possible displays. Stop after
  199. // the first successful one. See file_displays() for details.
  200. $element = NULL;
  201. foreach ($displays as $formatter_type => $display) {
  202. if (!empty($display['status'])) {
  203. $formatter_info = file_info_formatter_types($formatter_type);
  204. // Under normal circumstances, the UI prevents enabling formatters for
  205. // incompatible file types. In case this was somehow circumvented (for
  206. // example, a module updated its formatter definition without updating
  207. // existing display settings), perform an extra check here.
  208. if (isset($formatter_info['file types']) && !in_array($file->type, $formatter_info['file types'])) {
  209. continue;
  210. }
  211. if (isset($formatter_info['view callback']) && ($function = $formatter_info['view callback']) && function_exists($function)) {
  212. $display['type'] = $formatter_type;
  213. if (!empty($formatter_info['default settings'])) {
  214. if (empty($display['settings'])) {
  215. $display['settings'] = array();
  216. }
  217. $display['settings'] += $formatter_info['default settings'];
  218. }
  219. $element = $function($file, $display, $langcode);
  220. if (isset($element)) {
  221. break;
  222. }
  223. }
  224. }
  225. }
  226. // If none of the configured formatters were able to display the file, attempt
  227. // to display the file using the file type's default view callback.
  228. if (!isset($element)) {
  229. $file_type_info = file_info_file_types($file->type);
  230. if (isset($file_type_info['default view callback']) && ($function = $file_type_info['default view callback']) && function_exists($function)) {
  231. $element = $function($file, $view_mode, $langcode);
  232. }
  233. }
  234. // If a render element was returned by a formatter or the file type's default
  235. // view callback, add some defaults to it and return it.
  236. if (isset($element)) {
  237. $element += array(
  238. '#file' => $file,
  239. '#view_mode' => $view_mode,
  240. '#language' => $langcode,
  241. );
  242. return $element;
  243. }
  244. }
  245. /**
  246. * Returns an array of possible displays to use for a file type in a given view mode.
  247. *
  248. * It is common for a site to be configured with broadly defined file types
  249. * (e.g., 'video'), and to have different files of this type require different
  250. * displays (for example, the code required to display a YouTube video is
  251. * different than the code required to display a local QuickTime video).
  252. * Therefore, the site administrator can configure multiple displays for a given
  253. * file type. This function returns all of the displays that the administrator
  254. * enabled for the given file type in the given view mode. file_view_file() then
  255. * invokes each of these, and passes the specific file to display. Each display
  256. * implementation can inspect the file, and either return a render array (if it
  257. * is capable of displaying the file), or return nothing (if it is incapable of
  258. * displaying the file). The first render array returned is the one used.
  259. *
  260. * @param $file_type
  261. * The type of file.
  262. * @param $view_mode
  263. * The view mode.
  264. *
  265. * @return
  266. * An array keyed by the formatter type name. Each item in the array contains
  267. * the following key/value pairs:
  268. * - status: Whether this display is enabled. If not TRUE, file_view_file()
  269. * skips over it.
  270. * - weight: An integer that determines the order of precedence within the
  271. * returned array. The lowest weight display capable of displaying the file
  272. * is used.
  273. * - settings: An array of key/value pairs specific to the formatter type. See
  274. * hook_file_formatter_info() for details.
  275. *
  276. * @see hook_file_formatter_info()
  277. * @see file_view_file()
  278. */
  279. function file_displays($file_type, $view_mode = 'default') {
  280. $cache = &drupal_static(__FUNCTION__, array());
  281. // If the requested view mode isn't configured to use a custom display for its
  282. // fields, then don't use a custom display for its file either.
  283. if ($view_mode != 'default') {
  284. $view_mode_settings = field_view_mode_settings('file', $file_type);
  285. $view_mode = !empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default';
  286. }
  287. if (!isset($cache[$file_type][$view_mode])) {
  288. // Load the display configurations for the file type and view mode. If none
  289. // exist for the view mode, use the default view mode.
  290. $displays = file_displays_load($file_type, $view_mode, TRUE);
  291. if (empty($displays) && $view_mode != 'default') {
  292. $cache[$file_type][$view_mode] = file_displays($file_type, 'default');
  293. }
  294. else {
  295. // Convert the display objects to arrays and remove unnecessary keys.
  296. foreach ($displays as $formatter_name => $display) {
  297. $displays[$formatter_name] = array_intersect_key((array) $display, drupal_map_assoc(array('status', 'weight', 'settings')));
  298. }
  299. $cache[$file_type][$view_mode] = $displays;
  300. }
  301. }
  302. return $cache[$file_type][$view_mode];
  303. }
  304. /**
  305. * Returns an array of {file_display} objects for the file type and view mode.
  306. */
  307. function file_displays_load($file_type, $view_mode, $key_by_formatter_name = FALSE) {
  308. ctools_include('export');
  309. $display_names = array();
  310. $prefix = $file_type . '__' . $view_mode . '__';
  311. foreach (array_keys(file_info_formatter_types()) as $formatter_name) {
  312. $display_names[] = $prefix . $formatter_name;
  313. }
  314. $displays = ctools_export_load_object('file_display', 'names', $display_names);
  315. if ($key_by_formatter_name) {
  316. $prefix_length = strlen($prefix);
  317. $rekeyed_displays = array();
  318. foreach ($displays as $name => $display) {
  319. $rekeyed_displays[substr($name, $prefix_length)] = $display;
  320. }
  321. $displays = $rekeyed_displays;
  322. }
  323. return $displays;
  324. }
  325. /**
  326. * Saves a {file_display} object to the database.
  327. */
  328. function file_display_save($display) {
  329. ctools_include('export');
  330. ctools_export_crud_save('file_display', $display);
  331. }
  332. /**
  333. * Creates a new {file_display} object.
  334. */
  335. function file_display_new($file_type, $view_mode, $formatter_name) {
  336. ctools_include('export');
  337. $display = ctools_export_crud_new('file_display');
  338. $display->name = implode('__', array($file_type, $view_mode, $formatter_name));
  339. return $display;
  340. }
  341. /**
  342. * 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.
  343. */
  344. function _file_sort_array_by_weight(&$a) {
  345. $i=0;
  346. foreach ($a as $key => $item) {
  347. if (!isset($a[$key]['weight'])) {
  348. $a[$key]['weight'] = 0;
  349. }
  350. $original_weight[$key] = $a[$key]['weight'];
  351. $a[$key]['weight'] += $i/1000;
  352. $i++;
  353. }
  354. uasort($a, 'drupal_sort_weight');
  355. foreach ($a as $key => $item) {
  356. $a[$key]['weight'] = $original_weight[$key];
  357. }
  358. }