Projet

Général

Profil

Paste
Télécharger (86,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / file_entity / file_entity.module @ be58a50c

1
<?php
2

    
3
/**
4
 * @file
5
 * Extends Drupal file entities to be fieldable and viewable.
6
 */
7

    
8
/**
9
 * Modules should return this value from hook_file_entity_access() to allow
10
 * access to a file.
11
 */
12
define('FILE_ENTITY_ACCESS_ALLOW', 'allow');
13

    
14
/**
15
 * Modules should return this value from hook_file_entity_access() to deny
16
 * access to a file.
17
 */
18
define('FILE_ENTITY_ACCESS_DENY', 'deny');
19

    
20
/**
21
 * Modules should return this value from hook_file_entity_access() to not affect
22
 * file access.
23
 */
24
define('FILE_ENTITY_ACCESS_IGNORE', NULL);
25

    
26
/**
27
 * As part of extending Drupal core's file entity API, this module adds some
28
 * functions to the 'file' namespace. For organization, those are kept in the
29
 * 'file_entity.file_api.inc' file.
30
 */
31
require_once dirname(__FILE__) . '/file_entity.file_api.inc';
32

    
33
// @todo Remove when http://drupal.org/node/977052 is fixed.
34
require_once dirname(__FILE__) . '/file_entity.field.inc';
35

    
36
/**
37
 * Implements hook_hook_info().
38
 */
39
function file_entity_hook_info() {
40
  $hooks = array(
41
    'file_operations',
42
    'file_type_info',
43
    'file_type_info_alter',
44
    'file_formatter_info',
45
    'file_formatter_info_alter',
46
    'file_view',
47
    'file_view_alter',
48
    'file_displays_alter',
49
    'file_type',
50
    'file_type_alter',
51
    'file_download_headers_alter',
52
    'file_entity_access',
53
  );
54

    
55
  return array_fill_keys($hooks, array('group' => 'file'));
56
}
57

    
58
/**
59
 * Implements hook_hook_info_alter().
60
 *
61
 * Add support for existing core hooks to be located in modulename.file.inc.
62
 */
63
function file_entity_hook_info_alter(&$info) {
64
  $hooks = array(
65
    // File API hooks
66
    'file_copy',
67
    'file_move',
68
    'file_validate',
69
    // File access
70
    'file_download',
71
    'file_download_access',
72
    'file_download_access_alter',
73
    // File entity hooks
74
    'file_load',
75
    'file_presave',
76
    'file_insert',
77
    'file_update',
78
    'file_delete',
79
    // Miscellaneous hooks
80
    'file_mimetype_mapping_alter',
81
    'file_url_alter',
82
    // Stream wrappers
83
    'stream_wrappers',
84
    'stream_wrappers_alter',
85
  );
86
  $info += array_fill_keys($hooks, array('group' => 'file'));
87
}
88

    
89
/**
90
 * Implements hook_module_implements_alter().
91
 */
92
function file_entity_module_implements_alter(&$implementations, $hook) {
93
  // nginx_accel_redirect_file_transfer() is an accidental hook implementation.
94
  // @see https://www.drupal.org/node/2278625
95
  if ($hook == 'file_transfer') {
96
    unset($implementations['nginx_accel_redirect']);
97
  }
98
}
99

    
100
/**
101
 * Implements hook_help().
102
 */
103
function file_entity_help($path, $arg) {
104
  switch ($path) {
105
    case 'admin/structure/file-types':
106
      $output = '<p>' . t('When a file is uploaded to this website, it is assigned one of the following types, based on what kind of file it is.') . '</p>';
107
      return $output;
108
    case 'admin/structure/file-types/manage/%/display/preview':
109
    case 'admin/structure/file-types/manage/%/file-display/preview':
110
      drupal_set_message(t('Some modules rely on the Preview view mode to function correctly. Changing these settings may break parts of your site.'), 'warning');
111
      break;
112
  }
113
}
114

    
115
/**
116
 * Implements hook_menu().
117
 */
118
function file_entity_menu() {
119
  // File Configuration
120
  // @todo Move this back to admin/config/media/file-types in Drupal 8 if
121
  // MENU_MAX_DEPTH is increased to a value higher than 9.
122
  $items['admin/structure/file-types'] = array(
123
    'title' => 'File types',
124
    'description' => 'Manage settings for the type of files used on your site.',
125
    'page callback' => 'file_entity_list_types_page',
126
    'access arguments' => array('administer file types'),
127
    'file' => 'file_entity.admin.inc',
128
  );
129
  $items['admin/structure/file-types/add'] = array(
130
    'title' => 'Add file type',
131
    'page callback' => 'drupal_get_form',
132
    'page arguments' => array('file_entity_file_type_form'),
133
    'access arguments' => array('administer file types'),
134
    'type' => MENU_LOCAL_ACTION,
135
    'file' => 'file_entity.admin.inc',
136
  );
137
  $items['admin/structure/file-types/manage/%file_type'] = array(
138
    'title' => 'Manage file types',
139
    'description' => 'Manage settings for the type of files used on your site.',
140
  );
141
  $items['admin/structure/file-types/manage/%file_type/enable'] = array(
142
    'title' => 'Enable',
143
    'page callback' => 'drupal_get_form',
144
    'page arguments' => array('file_entity_type_enable_confirm', 4),
145
    'access arguments' => array('administer file types'),
146
    'file' => 'file_entity.admin.inc',
147
    'type' => MENU_CALLBACK,
148
  );
149
  $items['admin/structure/file-types/manage/%file_type/disable'] = array(
150
    'title' => 'Disable',
151
    'page callback' => 'drupal_get_form',
152
    'page arguments' => array('file_entity_type_disable_confirm', 4),
153
    'access arguments' => array('administer file types'),
154
    'file' => 'file_entity.admin.inc',
155
    'type' => MENU_CALLBACK,
156
  );
157
  $items['admin/structure/file-types/manage/%file_type/revert'] = array(
158
    'title' => 'Revert',
159
    'page callback' => 'drupal_get_form',
160
    'page arguments' => array('file_entity_type_revert_confirm', 4),
161
    'access arguments' => array('administer file types'),
162
    'file' => 'file_entity.admin.inc',
163
    'type' => MENU_CALLBACK,
164
  );
165
  $items['admin/structure/file-types/manage/%file_type/delete'] = array(
166
    'title' => 'Delete',
167
    'page callback' => 'drupal_get_form',
168
    'page arguments' => array('file_entity_type_delete_confirm', 4),
169
    'access arguments' => array('administer file types'),
170
    'file' => 'file_entity.admin.inc',
171
    'type' => MENU_CALLBACK,
172
  );
173

    
174
  $items['admin/content/file'] = array(
175
    'title' => 'Files',
176
    'description' => 'Manage files used on your site.',
177
    'page callback' => 'drupal_get_form',
178
    'page arguments' => array('file_entity_admin_file'),
179
    'access arguments' => array('administer files'),
180
    'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
181
    'file' => 'file_entity.admin.inc',
182
  );
183
  $items['admin/content/file/list'] = array(
184
    'title' => 'List',
185
    'type' => MENU_DEFAULT_LOCAL_TASK,
186
  );
187

    
188
  // general view, edit, delete for files
189
  $items['file/add'] = array(
190
    'title' => 'Add file',
191
    'page callback' => 'drupal_get_form',
192
    'page arguments' => array('file_entity_add_upload', array()),
193
    'access callback' => 'file_entity_access',
194
    'access arguments' => array('create'),
195
    'file' => 'file_entity.pages.inc',
196
  );
197
  if (module_exists('plupload') && module_exists('multiform')) {
198
    $items['file/add']['page arguments'] = array('file_entity_add_upload_multiple');
199
  }
200
  $items['file/add/upload'] = array(
201
    'title' => 'Upload',
202
    'type' => MENU_DEFAULT_LOCAL_TASK,
203
    'weight' => -10,
204
  );
205
  $items['file/add/upload/file'] = array(
206
    'title' => 'File',
207
    'type' => MENU_DEFAULT_LOCAL_TASK,
208
    'weight' => -10,
209
  );
210
  $items['file/add/upload/archive'] = array(
211
    'title' => 'Archive',
212
    'page callback' => 'drupal_get_form',
213
    'page arguments' => array('file_entity_upload_archive_form'),
214
    'access arguments' => array('administer files'),
215
    'file' => 'file_entity.pages.inc',
216
    'type' => MENU_LOCAL_TASK,
217
    'weight' => -5,
218
  );
219
  $items['file/%file'] = array(
220
    'title callback' => 'entity_label',
221
    'title arguments' => array('file', 1),
222
    // The page callback also invokes drupal_set_title() in case
223
    // the menu router's title is overridden by a menu link.
224
    'page callback' => 'file_entity_view_page',
225
    'page arguments' => array(1),
226
    'access callback' => 'file_entity_access',
227
    'access arguments' => array('view', 1),
228
    'file' => 'file_entity.pages.inc',
229
  );
230
  $items['file/%file/view'] = array(
231
    'title' => 'View',
232
    'type' => MENU_DEFAULT_LOCAL_TASK,
233
    'weight' => -10,
234
  );
235
  $items['file/%file/usage'] = array(
236
    'title' => 'Usage',
237
    'page callback' => 'file_entity_usage_page',
238
    'page arguments' => array(1),
239
    'access callback' => 'file_entity_access',
240
    'access arguments' => array('update', 1),
241
    'type' => MENU_LOCAL_TASK,
242
    'context' => MENU_CONTEXT_PAGE,
243
    'file' => 'file_entity.pages.inc',
244
  );
245
  $items['file/%file/download'] = array(
246
    'title' => 'Download',
247
    'page callback' => 'file_entity_download_page',
248
    'page arguments' => array(1),
249
    'access callback' => 'file_entity_access',
250
    'access arguments' => array('download', 1),
251
    'file' => 'file_entity.pages.inc',
252
    'type' => MENU_CALLBACK,
253
  );
254
  $items['file/%file/edit'] = array(
255
    'title' => 'Edit',
256
    'page callback' => 'drupal_get_form',
257
    'page arguments' => array('file_entity_edit', 1),
258
    'access callback' => 'file_entity_access',
259
    'access arguments' => array('update', 1),
260
    'weight' => 0,
261
    'type' => MENU_LOCAL_TASK,
262
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
263
    'file' => 'file_entity.pages.inc',
264
  );
265
  $items['file/%file/delete'] = array(
266
    'title' => 'Delete',
267
    'page callback' => 'drupal_get_form',
268
    'page arguments'  => array('file_entity_delete_form', 1),
269
    'access callback' => 'file_entity_access',
270
    'access arguments' => array('delete', 1),
271
    'weight' => 1,
272
    'type' => MENU_LOCAL_TASK,
273
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
274
    'file' => 'file_entity.pages.inc',
275
  );
276

    
277
  // Attach a "Manage file display" tab to each file type in the same way that
278
  // Field UI attaches "Manage fields" and "Manage display" tabs. Note that
279
  // Field UI does not have to be enabled; we're just using the same IA pattern
280
  // here for attaching the "Manage file display" page.
281
  $entity_info = entity_get_info('file');
282
  foreach ($entity_info['bundles'] as $file_type => $bundle_info) {
283
    if (isset($bundle_info['admin'])) {
284
      // Get the base path and access.
285
      $path = $bundle_info['admin']['path'];
286
      $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
287
      $access += array(
288
        'access callback' => 'user_access',
289
        'access arguments' => array('administer file types'),
290
      );
291

    
292
      // The file type must be passed to the page callbacks. It might be
293
      // configured as a wildcard (multiple file types sharing the same menu
294
      // router path).
295
      $file_type_argument = isset($bundle_info['admin']['bundle argument']) ? $bundle_info['admin']['bundle argument'] : $file_type;
296

    
297
      $items[$path] = array(
298
        'title' => 'Edit file type',
299
        'title callback' => 'file_entity_type_get_name',
300
        'title arguments' => array(4),
301
        'page callback' => 'drupal_get_form',
302
        'page arguments' => array('file_entity_file_type_form', $file_type_argument),
303
        'file' => 'file_entity.admin.inc',
304
      ) + $access;
305

    
306
      // Add the 'File type settings' tab.
307
      $items["$path/edit"] = array(
308
        'title' => 'Edit',
309
        'type' => MENU_DEFAULT_LOCAL_TASK,
310
      );
311

    
312
      // Add the 'Manage file display' tab.
313
      $items["$path/file-display"] = array(
314
        'title' => 'Manage file display',
315
        'page callback' => 'drupal_get_form',
316
        'page arguments' => array('file_entity_file_display_form', $file_type_argument, 'default'),
317
        'type' => MENU_LOCAL_TASK,
318
        'weight' => 3,
319
        'file' => 'file_entity.admin.inc',
320
      ) + $access;
321

    
322
      // Add a secondary tab for each view mode.
323
      $weight = 0;
324
      $view_modes = array('default' => array('label' => t('Default'))) + $entity_info['view modes'];
325
      foreach ($view_modes as $view_mode => $view_mode_info) {
326
        $items["$path/file-display/$view_mode"] = array(
327
          'title' => $view_mode_info['label'],
328
          'page arguments' => array('file_entity_file_display_form', $file_type_argument, $view_mode),
329
          'type' => ($view_mode == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK),
330
          'weight' => ($view_mode == 'default' ? -10 : $weight++),
331
          'file' => 'file_entity.admin.inc',
332
          // View modes for which the 'custom settings' flag isn't TRUE are
333
          // disabled via this access callback. This needs to extend, rather
334
          // than override normal $access rules.
335
          'access callback' => '_file_entity_view_mode_menu_access',
336
          'access arguments' => array_merge(array($file_type_argument, $view_mode, $access['access callback']), $access['access arguments']),
337
        );
338
      }
339
    }
340
  }
341

    
342
  $items['admin/config/media/file-settings'] = array(
343
    'title' => 'File settings',
344
    'description' => 'Configure allowed file extensions, default alt and title sources, and the file upload wizard.',
345
    'page callback' => 'drupal_get_form',
346
    'page arguments' => array('file_entity_settings_form'),
347
    'access arguments' => array('administer site configuration'),
348
    'file' => 'file_entity.admin.inc',
349
  );
350

    
351
  // Optional devel module integration
352
  if (module_exists('devel')) {
353
    $items['file/%file/devel'] = array(
354
      'title' => 'Devel',
355
      'page callback' => 'devel_load_object',
356
      'page arguments' => array('file', 1),
357
      'access arguments' => array('access devel information'),
358
      'type' => MENU_LOCAL_TASK,
359
      'file' => 'devel.pages.inc',
360
      'file path' => drupal_get_path('module', 'devel'),
361
      'weight' => 100,
362
    );
363
    $items['file/%file/devel/load'] = array(
364
      'title' => 'Load',
365
      'type' => MENU_DEFAULT_LOCAL_TASK,
366
    );
367
    $items['file/%file/devel/render'] = array(
368
      'title' => 'Render',
369
      'page callback' => 'devel_render_object',
370
      'page arguments' => array('file', 1),
371
      'access arguments' => array('access devel information'),
372
      'file' => 'devel.pages.inc',
373
      'file path' => drupal_get_path('module', 'devel'),
374
      'type' => MENU_LOCAL_TASK,
375
      'weight' => 100,
376
    );
377
    if (module_exists('token')) {
378
      $items['file/%file/devel/token'] = array(
379
        'title' => 'Tokens',
380
        'page callback' => 'token_devel_token_object',
381
        'page arguments' => array('file', 1),
382
        'access arguments' => array('access devel information'),
383
        'type' => MENU_LOCAL_TASK,
384
        'file' => 'token.pages.inc',
385
        'file path' => drupal_get_path('module', 'token'),
386
        'weight' => 5,
387
      );
388
    }
389
  }
390

    
391
  // Devel generate integration.
392
  if (module_exists('devel_generate')) {
393
    $items['admin/config/development/generate/file'] = array(
394
      'title' => 'Generate files',
395
      'description' => 'Generate a given number of files. Optionally delete current files.',
396
      'page callback' => 'drupal_get_form',
397
      'page arguments' => array('file_entity_generate_file_form'),
398
      'access arguments' => array('administer files'),
399
      'file' => 'file_entity.devel_generate.inc',
400
    );
401
    $items['admin/content/file/generate'] = $items['admin/config/development/generate/file'];
402
    $items['admin/content/file/generate']['type'] = MENU_LOCAL_ACTION;
403
    $items['file/add/generate'] = $items['admin/config/development/generate/file'];
404
    $items['file/add/generate']['title'] = 'Generate';
405
    $items['file/add/generate']['type'] = MENU_LOCAL_TASK;
406
    $items['file/add/generate']['weight'] = 50;
407
  }
408

    
409
  return $items;
410
}
411

    
412
/**
413
 * Implements hook_menu_local_tasks_alter().
414
 */
415
function file_entity_menu_local_tasks_alter(&$data, $router_item, $root_path) {
416
  // Add action link to 'file/add' on 'admin/content/file' page.
417
  if ($root_path == 'admin/content/file') {
418
    $item = menu_get_item('file/add');
419
    if (!empty($item['access'])) {
420
      $data['actions']['output'][] = array(
421
        '#theme' => 'menu_local_action',
422
        '#link' => $item,
423
        '#weight' => $item['weight'],
424
      );
425
    }
426
  }
427
}
428

    
429
/**
430
 * Implement hook_permission().
431
 */
432
function file_entity_permission() {
433
  $permissions = array(
434
    'bypass file access' => array(
435
      'title' => t('Bypass file access control'),
436
      'description' => t('View, edit and delete all files regardless of permission restrictions.'),
437
      'restrict access' => TRUE,
438
    ),
439
    'administer file types' => array(
440
      'title' => t('Administer file types'),
441
      'restrict access' => TRUE,
442
    ),
443
    'administer files' => array(
444
      'title' => t('Administer files'),
445
      'restrict access' => TRUE,
446
    ),
447
    'create files' => array(
448
      'title' => t('Add and upload new files'),
449
    ),
450
    'view own private files' => array(
451
      'title' => t('View own private files'),
452
    ),
453
    'view own files' => array(
454
      'title' => t('View own files'),
455
    ),
456
    'view private files' => array(
457
      'title' => t('View private files'),
458
      'restrict access' => TRUE,
459
    ),
460
    'view files' => array(
461
      'title' => t('View files'),
462
    ),
463
  );
464

    
465
  // Add description for the 'View file details' and 'View own private file
466
  // details' permissions to show which stream wrappers they apply to.
467
  $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
468
  $wrappers += array('public' => array(t('None')), 'private' => array(t('None')));
469

    
470
  $permissions['view files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['public'])));
471
  $permissions['view own private files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['private'])));
472

    
473
  // Generate standard file permissions for all applicable file types.
474
  foreach (file_entity_permissions_get_configured_types() as $type) {
475
    $permissions += file_entity_list_permissions($type);
476
  }
477

    
478
  return $permissions;
479
}
480

    
481
/*
482
 * Implements hook_cron_queue_info().
483
 */
484
function file_entity_cron_queue_info() {
485
  $queues['file_entity_type_determine'] = array(
486
    'worker callback' => 'file_entity_type_determine',
487
  );
488
  return $queues;
489
}
490

    
491
/*
492
 * Determines file type for a given file ID and saves the file.
493
 *
494
 * @param $fid
495
 *   A file ID.
496
 */
497
function file_entity_type_determine($fid) {
498
  if ($file = file_load($fid)) {
499
    // The file type will be automatically determined when saving the file.
500
    file_save($file);
501
  }
502
}
503

    
504
/**
505
 * Gather the rankings from the the hook_ranking implementations.
506
 *
507
 * @param $query
508
 *   A query object that has been extended with the Search DB Extender.
509
 */
510
function _file_entity_rankings(SelectQueryExtender $query) {
511
  if ($ranking = module_invoke_all('file_ranking')) {
512
    $tables = &$query->getTables();
513
    foreach ($ranking as $rank => $values) {
514
      if ($file_rank = variable_get('file_entity_rank_' . $rank, 0)) {
515
        // If the table defined in the ranking isn't already joined, then add it.
516
        if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
517
          $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
518
        }
519
        $arguments = isset($values['arguments']) ? $values['arguments'] : array();
520
        $query->addScore($values['score'], $arguments, $file_rank);
521
      }
522
    }
523
  }
524
}
525

    
526
/**
527
 * Implements hook_search_info().
528
 */
529
function file_entity_search_info() {
530
  return array(
531
    'title' => 'Files',
532
    'path' => 'file',
533
  );
534
}
535

    
536
/**
537
 * Implements hook_search_access().
538
 */
539
function file_entity_search_access() {
540
  return user_access('view own private files') || user_access('view own files') || user_access('view private files') || user_access('view files');
541
}
542

    
543
/**
544
 * Implements hook_search_reset().
545
 */
546
function file_entity_search_reset() {
547
  db_update('search_dataset')
548
    ->fields(array('reindex' => REQUEST_TIME))
549
    ->condition('type', 'file')
550
    ->execute();
551
}
552

    
553
/**
554
 * Implements hook_search_status().
555
 */
556
function file_entity_search_status() {
557
  $total = db_query('SELECT COUNT(*) FROM {file_managed}')->fetchField();
558
  $remaining = db_query("SELECT COUNT(*) FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField();
559
  return array('remaining' => $remaining, 'total' => $total);
560
}
561

    
562
/**
563
 * Implements hook_search_admin().
564
 */
565
function file_entity_search_admin() {
566
  // Output form for defining rank factor weights.
567
  $form['file_ranking'] = array(
568
    '#type' => 'fieldset',
569
    '#title' => t('File ranking'),
570
  );
571
  $form['file_ranking']['#theme'] = 'file_entity_search_admin';
572
  $form['file_ranking']['info'] = array(
573
    '#value' => '<em>' . t('The following numbers control which properties the file search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
574
  );
575

    
576
  // Note: reversed to reflect that higher number = higher ranking.
577
  $options = drupal_map_assoc(range(0, 10));
578
  foreach (module_invoke_all('file_ranking') as $var => $values) {
579
    $form['file_ranking']['factors']['file_entity_rank_' . $var] = array(
580
      '#title' => $values['title'],
581
      '#type' => 'select',
582
      '#options' => $options,
583
      '#default_value' => variable_get('file_entity_rank_' . $var, 0),
584
    );
585
  }
586
  return $form;
587
}
588

    
589
/**
590
 * Implements hook_search_execute().
591
 */
592
function file_entity_search_execute($keys = NULL, $conditions = NULL) {
593
  global $user;
594

    
595
  // Build matching conditions
596
  $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
597
  $query->join('file_managed', 'fm', 'fm.fid = i.sid');
598
  $query->searchExpression($keys, 'file');
599

    
600
  // Insert special keywords.
601
  $query->setOption('type', 'fm.type');
602
  if ($query->setOption('term', 'ti.tid')) {
603
    $query->join('taxonomy_index', 'ti', 'fm.fid = ti.fid');
604
  }
605
  // Only continue if the first pass query matches.
606
  if (!$query->executeFirstPass()) {
607
    return array();
608
  }
609

    
610
  // Add the ranking expressions.
611
  _file_entity_rankings($query);
612

    
613
  // Load results.
614
  $find = $query
615
    ->limit(10)
616
    ->addTag('file_access')
617
    ->execute();
618
  $results = array();
619
  foreach ($find as $item) {
620
    // Render the file.
621
    $file = file_load($item->sid);
622
    $build = file_view($file, 'search_result');
623
    unset($build['#theme']);
624
    $file->rendered = drupal_render($build);
625

    
626
    $extra = module_invoke_all('file_entity_search_result', $file);
627

    
628
    $types = file_entity_type_get_names();
629

    
630
    $uri = entity_uri('file', $file);
631
    $results[] = array(
632
      'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
633
      'type' => check_plain($types[$file->type]),
634
      'title' => $file->filename,
635
      'user' => theme('username', array('account' => user_load($file->uid))),
636
      'date' => $file->timestamp,
637
      'file' => $file,
638
      'extra' => $extra,
639
      'score' => $item->calculated_score,
640
      'snippet' => search_excerpt($keys, $file->rendered),
641
      'language' => function_exists('entity_language') ? entity_language('file', $file) : NULL,
642
    );
643
  }
644
  return $results;
645
}
646

    
647
/**
648
 * Implements hook_file_ranking().
649
 */
650
function file_entity_file_ranking() {
651
  // Create the ranking array and add the basic ranking options.
652
  $ranking = array(
653
    'relevance' => array(
654
      'title' => t('Keyword relevance'),
655
      // Average relevance values hover around 0.15
656
      'score' => 'i.relevance',
657
    ),
658
  );
659

    
660
  // Add relevance based on creation date.
661
  if ($file_cron_last = variable_get('file_entity_cron_last', 0)) {
662
    $ranking['timestamp'] = array(
663
      'title' => t('Recently posted'),
664
      // Exponential decay with half-life of 6 months, starting at last indexed file
665
      'score' => 'POW(2.0, (fm.timestamp - :file_cron_last) * 6.43e-8)',
666
      'arguments' => array(':file_cron_last' => $file_cron_last),
667
    );
668
  }
669
  return $ranking;
670
}
671

    
672
/**
673
 * Returns HTML for the file ranking part of the search settings admin page.
674
 *
675
 * @param $variables
676
 *   An associative array containing:
677
 *   - form: A render element representing the form.
678
 *
679
 * @ingroup themeable
680
 */
681
function theme_file_entity_search_admin($variables) {
682
  $form = $variables['form'];
683

    
684
  $output = drupal_render($form['info']);
685

    
686
  $header = array(t('Factor'), t('Weight'));
687
  foreach (element_children($form['factors']) as $key) {
688
    $row = array();
689
    $row[] = $form['factors'][$key]['#title'];
690
    $form['factors'][$key]['#title_display'] = 'invisible';
691
    $row[] = drupal_render($form['factors'][$key]);
692
    $rows[] = $row;
693
  }
694
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
695

    
696
  $output .= drupal_render_children($form);
697
  return $output;
698
}
699

    
700
/**
701
 * Implements hook_update_index().
702
 */
703
function file_entity_update_index() {
704
  $limit = (int)variable_get('search_cron_limit', 100);
705

    
706
  $result = db_query_range("SELECT fm.fid FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, fm.fid ASC", 0, $limit, array(), array('target' => 'slave'));
707

    
708
  foreach ($result as $file) {
709
    _file_entity_index_file($file);
710
  }
711
}
712

    
713
/**
714
 * Index a single file.
715
 *
716
 * @param $file
717
 *   The file to index.
718
 */
719
function _file_entity_index_file($file) {
720
  $file = file_load($file->fid);
721

    
722
  // Save the creation time of the most recent indexed file, for the search
723
  // results half-life calculation.
724
  variable_set('file_entity_cron_last', $file->timestamp);
725

    
726
  // Render the file.
727
  $build = file_view($file, 'search_index');
728
  unset($build['#theme']);
729
  $file->rendered = drupal_render($build);
730

    
731
  $text = '<h1>' . check_plain($file->filename) . '</h1>' . $file->rendered;
732

    
733
  // Fetch extra data normally not visible
734
  $extra = module_invoke_all('file_entity_update_index', $file);
735
  foreach ($extra as $t) {
736
    $text .= $t;
737
  }
738

    
739
  // Update index
740
  search_index($file->fid, 'file', $text);
741
}
742

    
743
/**
744
 * Implements hook_form_FORM_ID_alter().
745
 */
746
function file_entity_form_search_form_alter(&$form, $form_state) {
747
  if (isset($form['module']) && $form['module']['#value'] == 'file_entity' && user_access('use advanced search')) {
748
    // Keyword boxes:
749
    $form['advanced'] = array(
750
      '#type' => 'fieldset',
751
      '#title' => t('Advanced search'),
752
      '#collapsible' => TRUE,
753
      '#collapsed' => TRUE,
754
      '#attributes' => array('class' => array('search-advanced')),
755
    );
756
    $form['advanced']['keywords'] = array(
757
      '#prefix' => '<div class="criterion">',
758
      '#suffix' => '</div>',
759
    );
760
    $form['advanced']['keywords']['or'] = array(
761
      '#type' => 'textfield',
762
      '#title' => t('Containing any of the words'),
763
      '#size' => 30,
764
      '#maxlength' => 255,
765
    );
766
    $form['advanced']['keywords']['phrase'] = array(
767
      '#type' => 'textfield',
768
      '#title' => t('Containing the phrase'),
769
      '#size' => 30,
770
      '#maxlength' => 255,
771
    );
772
    $form['advanced']['keywords']['negative'] = array(
773
      '#type' => 'textfield',
774
      '#title' => t('Containing none of the words'),
775
      '#size' => 30,
776
      '#maxlength' => 255,
777
    );
778

    
779
    // File types:
780
    $types = array_map('check_plain', file_entity_type_get_names());
781
    $form['advanced']['type'] = array(
782
      '#type' => 'checkboxes',
783
      '#title' => t('Only of the type(s)'),
784
      '#prefix' => '<div class="criterion">',
785
      '#suffix' => '</div>',
786
      '#options' => $types,
787
    );
788
    $form['advanced']['submit'] = array(
789
      '#type' => 'submit',
790
      '#value' => t('Advanced search'),
791
      '#prefix' => '<div class="action">',
792
      '#suffix' => '</div>',
793
      '#weight' => 100,
794
    );
795

    
796
    $form['#validate'][] = 'file_entity_search_validate';
797
  }
798
}
799

    
800
/**
801
 * Form API callback for the search form. Registered in file_entity_form_alter().
802
 */
803
function file_entity_search_validate($form, &$form_state) {
804
  // Initialize using any existing basic search keywords.
805
  $keys = $form_state['values']['processed_keys'];
806

    
807
  // Insert extra restrictions into the search keywords string.
808
  if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
809
    // Retrieve selected types - Form API sets the value of unselected
810
    // checkboxes to 0.
811
    $form_state['values']['type'] = array_filter($form_state['values']['type']);
812
    if (count($form_state['values']['type'])) {
813
      $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
814
    }
815
  }
816

    
817
  if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
818
    $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
819
  }
820
  if ($form_state['values']['or'] != '') {
821
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
822
      $keys .= ' ' . implode(' OR ', $matches[1]);
823
    }
824
  }
825
  if ($form_state['values']['negative'] != '') {
826
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
827
      $keys .= ' -' . implode(' -', $matches[1]);
828
    }
829
  }
830
  if ($form_state['values']['phrase'] != '') {
831
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
832
  }
833
  if (!empty($keys)) {
834
    form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
835
  }
836
}
837

    
838
/**
839
 * Implements hook_admin_paths().
840
 */
841
function file_entity_admin_paths() {
842
  $paths = array(
843
    'file/add' => TRUE,
844
    'file/add/*' => TRUE,
845
    'file/*/edit' => TRUE,
846
    'file/*/usage' => TRUE,
847
    'file/*/delete' => TRUE,
848
  );
849
  return $paths;
850
}
851

    
852
/**
853
 * Implements hook_action_info_alter().
854
 */
855
function file_entity_action_info_alter(&$actions) {
856
  if (module_exists('pathauto')) {
857
    $actions['pathauto_file_update_action'] = array(
858
      'type' => 'file',
859
      'label' => t('Update file alias'),
860
      'configurable' => FALSE,
861
    );
862
  }
863
}
864

    
865
/**
866
 * Implements hook_theme().
867
 */
868
function file_entity_theme() {
869
  return array(
870
    'file_entity' => array(
871
      'render element' => 'elements',
872
      'template' => 'file_entity',
873
    ),
874
    'file_entity_search_admin' => array(
875
      'render element' => 'form',
876
    ),
877
    'file_entity_file_type_overview' => array(
878
      'variables' => array('label' => NULL, 'description' => NULL),
879
      'file' => 'file_entity.admin.inc',
880
    ),
881
    'file_entity_file_display_order' => array(
882
      'render element' => 'element',
883
      'file' => 'file_entity.admin.inc',
884
    ),
885
    'file_entity_file_link' => array(
886
      'variables' => array('file' => NULL, 'icon_directory' => NULL),
887
      'file' => 'file_entity.theme.inc',
888
    ),
889
    'file_entity_download_link' => array(
890
      'variables' => array('file' => NULL, 'icon_directory' => NULL, 'text' => NULL),
891
      'file' => 'file_entity.theme.inc',
892
    ),
893
    'file_entity_file_audio' => array(
894
      'variables' => array(
895
        'files' => array(),
896
        'controls' => TRUE,
897
        'autoplay' => FALSE,
898
        'loop' => FALSE,
899
        'preload' => NULL,
900
      ),
901
      'file' => 'file_entity.theme.inc',
902
    ),
903
    'file_entity_file_video' => array(
904
      'variables' => array(
905
        'files' => array(),
906
        'controls' => TRUE,
907
        'autoplay' => FALSE,
908
        'loop' => FALSE,
909
        'muted' => FALSE,
910
        'width' => NULL,
911
        'height' => NULL,
912
        'preload' => NULL,
913
      ),
914
      'file' => 'file_entity.theme.inc',
915
    ),
916
  );
917
}
918

    
919
/**
920
 * Implements hook_entity_info_alter().
921
 *
922
 * Extends the core file entity to be fieldable. The file type is used as the
923
 * bundle key. File types are implemented as CTools exportables, so modules can
924
 * define default file types via hook_file_default_types(), and the
925
 * administrator can override the default types or add custom ones via
926
 * admin/structure/file-types.
927
 */
928
function file_entity_entity_info_alter(&$entity_info) {
929
  $entity_info['file']['fieldable'] = TRUE;
930
  $entity_info['file']['entity keys']['bundle'] = 'type';
931
  $entity_info['file']['bundle keys']['bundle'] = 'type';
932
  $entity_info['file']['bundles'] = array();
933
  $entity_info['file']['uri callback'] = 'file_entity_uri';
934
  $entity_info['file']['view modes']['teaser'] = array(
935
    'label' => t('Teaser'),
936
    'custom settings' => TRUE,
937
  );
938
  $entity_info['file']['view modes']['full'] = array(
939
    'label' => t('Full content'),
940
    'custom settings' => FALSE,
941
  );
942
  $entity_info['file']['view modes']['preview'] = array(
943
    'label' => t('Preview'),
944
    'custom settings' => TRUE,
945
  );
946
  $entity_info['file']['view modes']['rss'] = array(
947
    'label' => t('RSS'),
948
    'custom settings' => FALSE,
949
  );
950

    
951
  // Search integration is provided by file_entity.module, so search-related
952
  // view modes for files are defined here and not in search.module.
953
  if (module_exists('search')) {
954
    $entity_info['file']['view modes']['search_index'] = array(
955
      'label' => t('Search index'),
956
      'custom settings' => FALSE,
957
    );
958
    $entity_info['file']['view modes']['search_result'] = array(
959
      'label' => t('Search result'),
960
      'custom settings' => FALSE,
961
    );
962
  }
963

    
964
  foreach (file_type_get_enabled_types() as $type) {
965
    $entity_info['file']['bundles'][$type->type] = array(
966
      'label' => $type->label,
967
      'admin' => array(
968
        'path' => 'admin/structure/file-types/manage/%file_type',
969
        'real path' => 'admin/structure/file-types/manage/' . $type->type,
970
        'bundle argument' => 4,
971
        'access arguments' => array('administer file types'),
972
      ),
973
    );
974
  }
975

    
976
  // Enable Metatag support.
977
  $entity_info['file']['metatags'] = TRUE;
978

    
979
  // Ensure some of the Entity API callbacks are supported.
980
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
981
  $entity_info['file']['view callback'] = 'file_entity_metadata_view_file';
982
  $entity_info['file']['form callback'] = 'file_entity_metadata_form_file';
983
  $entity_info['file']['access callback'] = 'file_entity_access';
984

    
985
  // Add integration with the Title module for file name replacement support.
986
  $entity_info['file']['field replacement'] = array(
987
    'filename' => array(
988
      'field' => array(
989
        'type' => 'text',
990
        'cardinality' => 1,
991
        'translatable' => TRUE,
992
      ),
993
      'instance' => array(
994
        'label' => t('File name'),
995
        'description' => t('A field replacing file name.'),
996
        'required' => TRUE,
997
        'settings' => array(
998
          'text_processing' => 0,
999
        ),
1000
        'widget' => array(
1001
          'weight' => -5,
1002
        ),
1003
        'display' => array(
1004
          'default' => array(
1005
            'type' => 'hidden',
1006
          ),
1007
        ),
1008
      ),
1009
      'preprocess_key' => 'filename',
1010
    ),
1011
  );
1012
}
1013

    
1014
/**
1015
 * Implements hook_entity_property_info().
1016
 */
1017
function file_entity_entity_property_info() {
1018
  $info['file']['properties']['type'] = array(
1019
    'label' => t('File type'),
1020
    'type' => 'token',
1021
    'description' => t('The type of the file.'),
1022
    'setter callback' => 'entity_property_verbatim_set',
1023
    'setter permission' => 'administer files',
1024
    'options list' => 'file_entity_type_get_names',
1025
    'required' => TRUE,
1026
    'schema field' => 'type',
1027
  );
1028

    
1029
  return $info;
1030
}
1031

    
1032
/**
1033
 * Implements hook_field_display_ENTITY_TYPE_alter().
1034
 */
1035
function file_entity_field_display_file_alter(&$display, $context) {
1036
  // Hide field labels in search index.
1037
  if ($context['view_mode'] == 'search_index') {
1038
    $display['label'] = 'hidden';
1039
  }
1040
}
1041

    
1042
/**
1043
 * URI callback for file entities.
1044
 */
1045
function file_entity_uri($file) {
1046
  $uri['path'] = 'file/' . $file->fid;
1047
  return $uri;
1048
}
1049

    
1050
/**
1051
 * Entity API callback to view files.
1052
 */
1053
function file_entity_metadata_view_file($entities, $view_mode = 'full', $langcode = NULL) {
1054
  $result = file_view_multiple($entities, $view_mode, 0, $langcode);
1055
  // Make sure to key the result with 'file' instead of 'files'.
1056
  return array('file' => reset($result));
1057
}
1058

    
1059
/**
1060
 * Entity API callback to get the form of a file entity.
1061
 */
1062
function file_entity_metadata_form_file($file) {
1063
  // Pre-populate the form-state with the right form include.
1064
  $form_state['build_info']['args'] = array($file);
1065
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
1066
  return drupal_build_form('file_entity_edit', $form_state);
1067
}
1068

    
1069
/**
1070
 * Implements hook_ctools_plugin_directory().
1071
 */
1072
function file_entity_ctools_plugin_directory($module, $plugin) {
1073
  if (in_array($module, array('panelizer', 'ctools', 'page_manager'))) {
1074
    return 'plugins/' . $plugin;
1075
  }
1076
}
1077

    
1078
/**
1079
 * Implements hook_field_extra_fields().
1080
 *
1081
 * Adds 'file' as an extra field, so that its display and form component can be
1082
 * weighted relative to the fields that are added to file entity bundles.
1083
 */
1084
function file_entity_field_extra_fields() {
1085
  $info = array();
1086

    
1087
  if ($file_type_names = file_entity_type_get_names()) {
1088
    foreach ($file_type_names as $type => $name) {
1089
      $info['file'][$type]['form']['filename'] = array(
1090
        'label' => t('File name'),
1091
        'description' => t('File name'),
1092
        'weight' => -10,
1093
      );
1094
      $info['file'][$type]['form']['preview'] = array(
1095
        'label' => t('File'),
1096
        'description' => t('File preview'),
1097
        'weight' => -5,
1098
      );
1099
      $info['file'][$type]['display']['file'] = array(
1100
        'label' => t('File'),
1101
        'description' => t('File display'),
1102
        'weight' => 0,
1103
      );
1104
    }
1105
  }
1106

    
1107
  return $info;
1108
}
1109

    
1110
/**
1111
 * Implements hook_file_formatter_info().
1112
 */
1113
function file_entity_file_formatter_info() {
1114
  $formatters = array();
1115

    
1116
  // Allow file field formatters to be reused for displaying the file entity's
1117
  // file pseudo-field.
1118
  foreach (field_info_formatter_types() as $key => $formatter) {
1119
    if (array_intersect($formatter['field types'], array('file', 'image'))) {
1120
      $key = 'file_field_' . $key;
1121
      $formatters[$key] = array(
1122
        'label' => $formatter['label'],
1123
        'description' => !empty($formatter['description']) ? $formatter['description'] : '',
1124
        'view callback' => 'file_entity_file_formatter_file_field_view',
1125
      );
1126
      if (!empty($formatter['settings'])) {
1127
        $formatters[$key] += array(
1128
          'default settings' => $formatter['settings'],
1129
          'settings callback' => 'file_entity_file_formatter_file_field_settings',
1130
        );
1131
      }
1132
      if (!empty($formatter['file formatter'])) {
1133
        $formatters[$key] += $formatter['file formatter'];
1134
      }
1135
    }
1136
  }
1137

    
1138
  // Add a simple file formatter for displaying an image in a chosen style.
1139
  if (module_exists('image')) {
1140
    $formatters['file_image'] = array(
1141
      'label' => t('Image'),
1142
      'default settings' => array(
1143
        'image_style' => '',
1144
        'alt' => '[file:field_file_image_alt_text]',
1145
        'title' => '[file:field_file_image_title_text]'
1146
      ),
1147
      'view callback' => 'file_entity_file_formatter_file_image_view',
1148
      'settings callback' => 'file_entity_file_formatter_file_image_settings',
1149
      'hidden' => TRUE,
1150
      'mime types' => array('image/*'),
1151
    );
1152
  }
1153

    
1154
  return $formatters;
1155
}
1156

    
1157
/**
1158
 * Implements hook_file_formatter_FORMATTER_view().
1159
 *
1160
 * This function provides a bridge to the field formatter API, so that file
1161
 * field formatters can be reused for displaying the file entity's file
1162
 * pseudo-field.
1163
 */
1164
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
1165
  if (strpos($display['type'], 'file_field_') === 0) {
1166
    $field_formatter_type = substr($display['type'], strlen('file_field_'));
1167
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1168
    if (isset($field_formatter_info['module'])) {
1169
      // Set $display['type'] to what hook_field_formatter_*() expects.
1170
      $display['type'] = $field_formatter_type;
1171

    
1172
      // Allow any attribute overrides (e.g. from the Media module) to be
1173
      // respected.
1174
      $item = (array) $file;
1175
      if (!empty($file->override['attributes'])) {
1176
        $item = array_merge($item, $file->override['attributes']);
1177
      }
1178

    
1179
      // Set $items to what file field formatters expect. See file_field_load(),
1180
      // and note that, here, $file is already a fully loaded entity.
1181
      $items = array($item);
1182

    
1183
      // Invoke hook_field_formatter_prepare_view() and
1184
      // hook_field_formatter_view(). Note that we are reusing field formatter
1185
      // functions, but we are not displaying a Field API field, so we set
1186
      // $field and $instance accordingly, and do not invoke
1187
      // hook_field_prepare_view(). This assumes that the formatter functions do
1188
      // not rely on $field or $instance. A module that implements formatter
1189
      // functions that rely on $field or $instance (and therefore, can only be
1190
      // used for real fields) can prevent this formatter from being used on the
1191
      // pseudo-field by removing it within hook_file_formatter_info_alter().
1192
      $field = $instance = NULL;
1193
      if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
1194
        $fid = $file->fid;
1195
        // hook_field_formatter_prepare_view() alters $items by reference.
1196
        $grouped_items = array($fid => &$items);
1197
        $function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
1198
      }
1199
      if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
1200
        $element = $function('file', $file, $field, $instance, $langcode, $items, $display);
1201
        // We passed the file as $items[0], so return the corresponding element.
1202
        if (isset($element[0])) {
1203
          return $element[0];
1204
        }
1205
      }
1206
    }
1207
  }
1208
}
1209

    
1210
/**
1211
 * Implements hook_file_formatter_FORMATTER_settings().
1212
 *
1213
 * This function provides a bridge to the field formatter API, so that file
1214
 * field formatters can be reused for displaying the file entity's file
1215
 * pseudo-field.
1216
 */
1217
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
1218
  if (strpos($formatter_type, 'file_field_') === 0) {
1219
    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
1220
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1221

    
1222
    // Invoke hook_field_formatter_settings_form(). We are reusing field
1223
    // formatter functions, but we are not working with a Field API field, so
1224
    // set $field accordingly. Unfortunately, the API is for $settings to be
1225
    // transferred via the $instance parameter, so we must mock it.
1226
    if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
1227
      $field = NULL;
1228
      $mock_instance = array(
1229
        'display' => array(
1230
          $view_mode => array(
1231
            'type' => $field_formatter_type,
1232
            'settings' => $settings,
1233
          ),
1234
        ),
1235
        'entity_type' => 'file',
1236
        'bundle' => $file_type,
1237
      );
1238
      return $function($field, $mock_instance, $view_mode, $form, $form_state);
1239
    }
1240
  }
1241
}
1242

    
1243
/**
1244
 * Implements hook_file_formatter_FORMATTER_view().
1245
 *
1246
 * Returns a drupal_render() array to display an image of the chosen style.
1247
 *
1248
 * This formatter is only capable of displaying local images. If the passed in
1249
 * file is either not local or not an image, nothing is returned, so that
1250
 * file_view_file() can try another formatter.
1251
 */
1252
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
1253
  // Prevent PHP notices when trying to read empty files.
1254
  // @see http://drupal.org/node/681042
1255
  if (!$file->filesize) {
1256
    return;
1257
  }
1258

    
1259
  // Do not bother proceeding if this file does not have an image mime type.
1260
  if (file_entity_file_get_mimetype_type($file) != 'image') {
1261
    return;
1262
  }
1263

    
1264
  if (file_entity_file_is_readable($file)) {
1265
    // We don't sanitize here.
1266
    // @see http://drupal.org/node/1553094#comment-6257382
1267
    // Theme function will take care of escaping.
1268
    if (!isset($file->metadata)) {
1269
      $file->metadata = array();
1270
    }
1271
    $file->metadata += array('width' => NULL, 'height' => NULL);
1272
    $replace_options = array(
1273
      'clear' => TRUE,
1274
      'sanitize' => FALSE,
1275
    );
1276
    if (!empty($display['settings']['image_style'])) {
1277
      $element = array(
1278
        '#theme' => 'image_style',
1279
        '#style_name' => $display['settings']['image_style'],
1280
        '#path' => $file->uri,
1281
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1282
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1283
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1284
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1285
      );
1286
    }
1287
    else {
1288
      $element = array(
1289
        '#theme' => 'image',
1290
        '#path' => $file->uri,
1291
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1292
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1293
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1294
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1295
      );
1296
    }
1297
    return $element;
1298
  }
1299
}
1300

    
1301
/**
1302
 * Check if a file entity is readable or not.
1303
 *
1304
 * @param object $file
1305
 *   A file entity object from file_load().
1306
 *
1307
 * @return boolean
1308
 *   TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
1309
 */
1310
function file_entity_file_is_readable($file) {
1311
  $scheme = file_uri_scheme($file->uri);
1312
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
1313
  return !empty($wrappers[$scheme]);
1314
}
1315

    
1316
/**
1317
 * Implements hook_file_formatter_FORMATTER_settings().
1318
 *
1319
 * Returns form elements for configuring the 'file_image' formatter.
1320
 */
1321
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
1322
  $element = array();
1323
  $element['image_style'] = array(
1324
    '#title' => t('Image style'),
1325
    '#type' => 'select',
1326
    '#options' => image_style_options(FALSE),
1327
    '#default_value' => $settings['image_style'],
1328
    '#empty_option' => t('None (original image)'),
1329
  );
1330

    
1331
  // For image files we allow the alt attribute (required in HTML).
1332
  $element['alt'] = array(
1333
    '#title' => t('Alt attribute'),
1334
    '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
1335
    '#type' => 'textfield',
1336
    '#default_value' => $settings['alt'],
1337
  );
1338

    
1339
  // Allow the setting of the title attribute.
1340
  $element['title'] = array(
1341
    '#title' => t('Title attribute'),
1342
    '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
1343
    '#type' => 'textfield',
1344
    '#default_value' => $settings['title'],
1345
  );
1346

    
1347
  if (module_exists('token')) {
1348
    $element['alt']['#description'] .= t('This field supports tokens.');
1349
    $element['title']['#description'] .= t('This field supports tokens.');
1350
    $element['tokens'] = array(
1351
      '#theme' => 'token_tree',
1352
      '#token_types' => array('file'),
1353
      '#dialog' => TRUE,
1354
    );
1355
  }
1356

    
1357
  return $element;
1358
}
1359

    
1360
/**
1361
 * Menu access callback for the 'view mode file display settings' pages.
1362
 *
1363
 * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
1364
 * be enabled.
1365
 */
1366
function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
1367
  // Deny access if the view mode isn't configured to use custom display
1368
  // settings.
1369
  $view_mode_settings = field_view_mode_settings('file', $file_type->type);
1370
  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
1371
  if (!$visibility) {
1372
    return FALSE;
1373
  }
1374

    
1375
  // Otherwise, continue to an $access_callback check.
1376
  $args = array_slice(func_get_args(), 3);
1377
  $callback = empty($access_callback) ? 0 : trim($access_callback);
1378
  if (is_numeric($callback)) {
1379
    return (bool) $callback;
1380
  }
1381
  elseif (function_exists($access_callback)) {
1382
    return call_user_func_array($access_callback, $args);
1383
  }
1384
}
1385

    
1386
/**
1387
 * Implements hook_modules_enabled().
1388
 */
1389
function file_entity_modules_enabled($modules) {
1390
  file_info_cache_clear();
1391
}
1392

    
1393
/**
1394
 * Implements hook_modules_disabled().
1395
 */
1396
function file_entity_modules_disabled($modules) {
1397
  file_info_cache_clear();
1398
}
1399

    
1400
/**
1401
 * Implements hook_views_api().
1402
 */
1403
function file_entity_views_api() {
1404
  return array(
1405
    'api' => 3,
1406
  );
1407
}
1408

    
1409
/**
1410
 * Returns whether the current page is the full page view of the passed-in file.
1411
 *
1412
 * @param $file
1413
 *   A file object.
1414
 */
1415
function file_entity_is_page($file) {
1416
  $page_file = menu_get_object('file', 1);
1417
  return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
1418
}
1419

    
1420
/**
1421
 * Process variables for file_entity.tpl.php
1422
 *
1423
 * The $variables array contains the following arguments:
1424
 * - $file
1425
 * - $view_mode
1426
 *
1427
 * @see file_entity.tpl.php
1428
 */
1429
function template_preprocess_file_entity(&$variables) {
1430
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
1431
  $variables['file'] = $variables['elements']['#file'];
1432
  $file = $variables['file'];
1433

    
1434
  $variables['id'] = drupal_html_id('file-'. $file->fid);
1435
  $variables['date']      = format_date($file->timestamp);
1436
  $account = user_load($file->uid);
1437
  $variables['name']      = theme('username', array('account' => $account));
1438

    
1439
  $uri = entity_uri('file', $file);
1440
  $variables['file_url']  = url($uri['path'], $uri['options']);
1441
  $label = entity_label('file', $file);
1442
  $variables['label']     = check_plain($label);
1443
  $variables['page']      = $view_mode == 'full' && file_entity_is_page($file);
1444

    
1445
  // Hide the file name from being displayed until we can figure out a better
1446
  // way to control this. We cannot simply not output the title since
1447
  // contextual links require $title_suffix to be output in the template.
1448
  // @see http://drupal.org/node/1245266
1449
  if (!$variables['page']) {
1450
    $variables['title_attributes_array']['class'][] = 'element-invisible';
1451
  }
1452

    
1453
  // Flatten the file object's member fields.
1454
  $variables = array_merge((array) $file, $variables);
1455

    
1456
  // Helpful $content variable for templates.
1457
  $variables += array('content' => array());
1458
  foreach (element_children($variables['elements']) as $key) {
1459
    $variables['content'][$key] = $variables['elements'][$key];
1460
  }
1461

    
1462
  // Make the field variables available with the appropriate language.
1463
  field_attach_preprocess('file', $file, $variables['content'], $variables);
1464

    
1465
  // Attach the file object to the content element.
1466
  $variables['content']['file']['#file'] = $file;
1467

    
1468
  // Display post information only on certain file types.
1469
  if (variable_get('file_submitted_' . $file->type, FALSE)) {
1470
    $variables['display_submitted'] = TRUE;
1471
    $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1472
    $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
1473
  }
1474
  else {
1475
    $variables['display_submitted'] = FALSE;
1476
    $variables['submitted'] = '';
1477
    $variables['user_picture'] = '';
1478
  }
1479

    
1480
  // Gather file classes.
1481
  $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
1482
  $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
1483
  if ($file->status != FILE_STATUS_PERMANENT) {
1484
    $variables['classes_array'][] = 'file-temporary';
1485
  }
1486

    
1487
  // Change the 'file-entity' class into 'file'
1488
  if ($variables['classes_array'][0] == 'file-entity') {
1489
    $variables['classes_array'][0] = 'file';
1490
  }
1491

    
1492
  // Clean up name so there are no underscores.
1493
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
1494
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
1495
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
1496
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
1497
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
1498
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
1499
}
1500

    
1501
/**
1502
 * Returns the file type name of the passed file or file type string.
1503
 *
1504
 * @param $file
1505
 *   A file object or string that indicates the file type to return.
1506
 *
1507
 * @return
1508
 *   The file type name or FALSE if the file type is not found.
1509
 */
1510
function file_entity_type_get_name($file) {
1511
  $type = is_object($file) ? $file->type : $file;
1512
  $info = entity_get_info('file');
1513
  return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
1514
}
1515

    
1516
/**
1517
 * Returns a list of available file type names.
1518
 *
1519
 * @return
1520
 *   An array of file type names, keyed by the type.
1521
 */
1522
function file_entity_type_get_names() {
1523
  $names = &drupal_static(__FUNCTION__);
1524

    
1525
  if (!isset($names)) {
1526
    $names = array();
1527
    $info = entity_get_info('file');
1528
    foreach ($info['bundles'] as $bundle => $bundle_info) {
1529
      $names[$bundle] = $bundle_info['label'];
1530
    }
1531
  }
1532

    
1533
  return $names;
1534
}
1535

    
1536
/**
1537
 * Return an array of available view modes for file entities.
1538
 */
1539
function file_entity_view_mode_labels() {
1540
  $labels = &drupal_static(__FUNCTION__);
1541

    
1542
  if (!isset($options)) {
1543
    $entity_info = entity_get_info('file');
1544
    $labels = array('default' => t('Default'));
1545
    foreach ($entity_info['view modes'] as $machine_name => $mode) {
1546
      $labels[$machine_name] = $mode['label'];
1547
    }
1548
  }
1549

    
1550
  return $labels;
1551
}
1552

    
1553
/**
1554
 * Return the label for a specific file entity view mode.
1555
 */
1556
function file_entity_view_mode_label($view_mode, $default = FALSE) {
1557
  $labels = file_entity_view_mode_labels();
1558
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
1559
}
1560

    
1561
/**
1562
 * Helper function to get a list of hidden stream wrappers.
1563
 *
1564
 * This is used in several places to filter queries for media so that files in
1565
 * temporary:// don't show up.
1566
 */
1567
function file_entity_get_hidden_stream_wrappers() {
1568
  return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
1569
}
1570

    
1571
/**
1572
 * Return a specific stream wrapper's registry information.
1573
 *
1574
 * @param $scheme
1575
 *   A URI scheme, a stream is referenced as "scheme://target".
1576
 *
1577
 * @see file_get_stream_wrappers()
1578
 */
1579
function file_entity_get_stream_wrapper($scheme) {
1580
  $wrappers = file_get_stream_wrappers();
1581
  return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
1582
}
1583

    
1584
/**
1585
 * Implements hook_stream_wrappers_alter().
1586
 */
1587
function file_entity_stream_wrappers_alter(&$wrappers) {
1588
  if (isset($wrappers['private'])) {
1589
    $wrappers['private']['private'] = TRUE;
1590
  }
1591
  if (isset($wrappers['temporary'])) {
1592
    $wrappers['temporary']['private'] = TRUE;
1593
  }
1594
}
1595

    
1596
/**
1597
 * Implements hook_ctools_plugin_api().
1598
 */
1599
function file_entity_ctools_plugin_api($module, $api) {
1600
  if (($module == 'file_entity' && $api == 'file_type') || ($module == 'page_manager' && $api == 'pages_default') || $module == 'panelizer') {
1601
    return array('version' => 1);
1602
  }
1603
  if ($module == 'file_entity' && $api == 'file_default_displays') {
1604
    return array('version' => 1);
1605
  }
1606
}
1607

    
1608
/**
1609
 * @defgroup file_entity_access File access rights
1610
 * @{
1611
 * The file access system determines who can do what to which files.
1612
 *
1613
 * In determining access rights for a file, file_entity_access() first checks
1614
 * whether the user has the "bypass file access" permission. Such users have
1615
 * unrestricted access to all files. user 1 will always pass this check.
1616
 *
1617
 * Next, all implementations of hook_file_entity_access() will be called. Each
1618
 * implementation may explicitly allow, explicitly deny, or ignore the access
1619
 * request. If at least one module says to deny the request, it will be rejected.
1620
 * If no modules deny the request and at least one says to allow it, the request
1621
 * will be permitted.
1622
 *
1623
 * There is no access grant system for files.
1624
 *
1625
 * In file listings, the process above is followed except that
1626
 * hook_file_entity_access() is not called on each file for performance reasons
1627
 * and for proper functioning of the pager system. When adding a filelisting to
1628
 * your module, be sure to use a dynamic query created by db_select()
1629
 * and add a tag of "file_entity_access". This will allow modules dealing
1630
 * with file access to ensure only files to which the user has access
1631
 * are retrieved, through the use of hook_query_TAG_alter().
1632
 *
1633
 * Note: Even a single module returning FILE_ENTITY_ACCESS_DENY from
1634
 * hook_file_entity_access() will block access to the file. Therefore,
1635
 * implementers should take care to not deny access unless they really intend to.
1636
 * Unless a module wishes to actively deny access it should return
1637
 * FILE_ENTITY_ACCESS_IGNORE (or simply return nothing)
1638
 * to allow other modules to control access.
1639
 *
1640
 * Stream wrappers that are considered private should implement a 'private'
1641
 * flag equal to TRUE in hook_stream_wrappers().
1642
 */
1643

    
1644
/**
1645
 * Determine if a user may perform the given operation on the specified file.
1646
 *
1647
 * @param $op
1648
 *   The operation to be performed on the file. Possible values are:
1649
 *   - "view"
1650
 *   - "download"
1651
 *   - "update"
1652
 *   - "delete"
1653
 *   - "create"
1654
 * @param $file
1655
 *   The file object on which the operation is to be performed, or file type
1656
 *   (e.g. 'image') for "create" operation.
1657
 * @param $account
1658
 *   Optional, a user object representing the user for whom the operation is to
1659
 *   be performed. Determines access for a user other than the current user.
1660
 *
1661
 * @return
1662
 *   TRUE if the operation may be performed, FALSE otherwise.
1663
 */
1664
function file_entity_access($op, $file = NULL, $account = NULL) {
1665
  $rights = &drupal_static(__FUNCTION__, array());
1666

    
1667
  if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
1668
    // If there was no file to check against, and the $op was not one of the
1669
    // supported ones, we return access denied.
1670
    return FALSE;
1671
  }
1672

    
1673
  // If no user object is supplied, the access check is for the current user.
1674
  if (empty($account)) {
1675
    $account = $GLOBALS['user'];
1676
  }
1677

    
1678
  // $file may be either an object or a file type. Since file types cannot be
1679
  // an integer, use either fid or type as the static cache id.
1680
  $cache_id = is_object($file) ? $file->fid : $file;
1681

    
1682
  // If we've already checked access for this file, user and op, return from
1683
  // cache.
1684
  if (isset($rights[$account->uid][$cache_id][$op])) {
1685
    return $rights[$account->uid][$cache_id][$op];
1686
  }
1687

    
1688
  if (user_access('bypass file access', $account)) {
1689
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1690
  }
1691

    
1692
  // We grant access to the file if both of the following conditions are met:
1693
  // - No modules say to deny access.
1694
  // - At least one module says to grant access.
1695
  $access = module_invoke_all('file_entity_access', $op, $file, $account);
1696
  if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
1697
    return $rights[$account->uid][$cache_id][$op] = FALSE;
1698
  }
1699
  elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
1700
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1701
  }
1702

    
1703

    
1704
  // Fall back to default behaviors on view.
1705
  if ($op == 'view' && is_object($file)) {
1706
    $scheme = file_uri_scheme($file->uri);
1707
    $wrapper = file_entity_get_stream_wrapper($scheme);
1708

    
1709
    if (!empty($wrapper['private'])) {
1710
      // For private files, users can view private files if the
1711
      // user has the 'view private files' permission.
1712
      if (user_access('view private files', $account)) {
1713
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1714
      }
1715

    
1716
      // For private files, users can view their own private files if the
1717
      // user is not anonymous, and has the 'view own private files' permission.
1718
      if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
1719
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1720
      }
1721
    }
1722
    elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
1723
      // For non-private files, allow to see if user owns the file.
1724
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1725
    }
1726
    elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
1727
      // For non-private files, users can view if they have the 'view files'
1728
      // permission.
1729
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1730
    }
1731
  }
1732

    
1733
  return FALSE;
1734
}
1735

    
1736
/**
1737
 * Implements hook_file_entity_access().
1738
 */
1739
function file_entity_file_entity_access($op, $file, $account) {
1740
  // If the file URI is invalid, deny access.
1741
  if (is_object($file) && !file_valid_uri($file->uri)) {
1742
    return FILE_ENTITY_ACCESS_DENY;
1743
  }
1744

    
1745
  if ($op == 'create') {
1746
    if (user_access('create files')) {
1747
      return FILE_ENTITY_ACCESS_ALLOW;
1748
    }
1749
  }
1750

    
1751
  if (!empty($file)) {
1752
    $type = is_string($file) ? $file : $file->type;
1753

    
1754
    if (in_array($type, file_entity_permissions_get_configured_types())) {
1755
      if ($op == 'download') {
1756
        if (user_access('download any ' . $type . ' files', $account) || is_object($file) && user_access('download own ' . $type . ' files', $account) && ($account->uid == $file->uid)) {
1757
          return FILE_ENTITY_ACCESS_ALLOW;
1758
        }
1759
      }
1760

    
1761
      if ($op == 'update') {
1762
        if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1763
          return FILE_ENTITY_ACCESS_ALLOW;
1764
        }
1765
      }
1766

    
1767
      if ($op == 'delete') {
1768
        if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1769
          return FILE_ENTITY_ACCESS_ALLOW;
1770
        }
1771
      }
1772
    }
1773
  }
1774

    
1775
  return FILE_ENTITY_ACCESS_IGNORE;
1776
}
1777

    
1778
/**
1779
 * Implements hook_query_TAG_alter().
1780
 *
1781
 * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
1782
 * file access checks for the user account given by the 'account' meta-data (or
1783
 * global $user if not provided).
1784
 */
1785
function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
1786
  _file_entity_query_file_entity_access_alter($query, 'file');
1787
}
1788

    
1789
/**
1790
 * Implements hook_query_TAG_alter().
1791
 *
1792
 * This function implements the same functionality as
1793
 * file_entity_query_file_access_alter() for the SQL field storage engine. File
1794
 * access conditions are added for field values belonging to files only.
1795
 */
1796
function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
1797
  //_file_entity_query_file_entity_access_alter($query, 'entity');
1798
}
1799

    
1800
/**
1801
 * Helper for file entity access functions.
1802
 *
1803
 * @param $query
1804
 *   The query to add conditions to.
1805
 * @param $type
1806
 *   Either 'file' or 'entity' depending on what sort of query it is. See
1807
 *   file_entity_query_file_entity_access_alter() and
1808
 *   file_entity_query_entity_field_access_alter() for more.
1809
 */
1810
function _file_entity_query_file_entity_access_alter($query, $type) {
1811
  global $user;
1812

    
1813
  // Read meta-data from query, if provided.
1814
  if (!$account = $query->getMetaData('account')) {
1815
    $account = $user;
1816
  }
1817

    
1818
  // If $account can bypass file access, we don't need to alter the query.
1819
  if (user_access('bypass file access', $account)) {
1820
    return;
1821
  }
1822

    
1823
  $tables = $query->getTables();
1824
  $base_table = $query->getMetaData('base_table');
1825
  // If no base table is specified explicitly, search for one.
1826
  if (!$base_table) {
1827
    $fallback = '';
1828
    foreach ($tables as $alias => $table_info) {
1829
      if (!($table_info instanceof SelectQueryInterface)) {
1830
        $table = $table_info['table'];
1831
        // If the file_managed table is in the query, it wins immediately.
1832
        if ($table == 'file_managed') {
1833
          $base_table = $table;
1834
          break;
1835
        }
1836
        // Check whether the table has a foreign key to file_managed.fid. If it
1837
        // does, do not run this check again as we found a base table and only
1838
        // file_managed can triumph that.
1839
        if (!$base_table) {
1840
          // The schema is cached.
1841
          $schema = drupal_get_schema($table);
1842
          if (isset($schema['fields']['fid'])) {
1843
            if (isset($schema['foreign keys'])) {
1844
              foreach ($schema['foreign keys'] as $relation) {
1845
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
1846
                  $base_table = $table;
1847
                }
1848
              }
1849
            }
1850
            else {
1851
              // At least it's a fid. A table with a field called fid is very
1852
              // very likely to be a file_managed.fid in a file access query.
1853
              $fallback = $table;
1854
            }
1855
          }
1856
        }
1857
      }
1858
    }
1859
    // If there is nothing else, use the fallback.
1860
    if (!$base_table) {
1861
      if ($fallback) {
1862
        watchdog('security', 'Your file listing query is using @fallback as a base table in a query tagged for file access. This might not be secure and might not even work. Specify foreign keys in your schema to file_managed.fid ', array('@fallback' => $fallback), WATCHDOG_WARNING);
1863
        $base_table = $fallback;
1864
      }
1865
      else {
1866
        throw new Exception(t('Query tagged for file access but there is no fid. Add foreign keys to file_managed.fid in schema to fix.'));
1867
      }
1868
    }
1869
  }
1870

    
1871
  if ($type == 'entity') {
1872
    // The original query looked something like:
1873
    // @code
1874
    //  SELECT fid FROM sometable s
1875
    //  WHERE ($file_access_conditions)
1876
    // @endcode
1877
    //
1878
    // Our query will look like:
1879
    // @code
1880
    //  SELECT entity_type, entity_id
1881
    //  FROM field_data_something s
1882
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1883
    // @endcode
1884
    //
1885
    // So instead of directly adding to the query object, we need to collect
1886
    // all of the file access conditions in a separate db_and() object and
1887
    // then add it to the query at the end.
1888
    $file_conditions = db_and();
1889
  }
1890
  foreach ($tables as $falias => $tableinfo) {
1891
    $table = $tableinfo['table'];
1892
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
1893
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
1894
      $subquery_conditions = db_or();
1895

    
1896
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
1897
      if (!empty($wrappers['public'])) {
1898
        if (user_access('view files', $account)) {
1899
          foreach (array_keys($wrappers['public']) as $wrapper) {
1900
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1901
          }
1902
        }
1903
        elseif (user_access('view own files', $account)) {
1904
          foreach (array_keys($wrappers['public']) as $wrapper) {
1905
            $subquery_conditions->condition(db_and()
1906
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1907
              ->condition('fm_access.uid', $account->uid)
1908
            );
1909
          }
1910
        }
1911
      }
1912
      if (!empty($wrappers['private'])) {
1913
        if (user_access('view private files', $account)) {
1914
          foreach (array_keys($wrappers['private']) as $wrapper) {
1915
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1916
          }
1917
        }
1918
        elseif (user_access('view own private files', $account)) {
1919
          foreach (array_keys($wrappers['private']) as $wrapper) {
1920
            $subquery_conditions->condition(db_and()
1921
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1922
              ->condition('fm_access.uid', $account->uid)
1923
            );
1924
          }
1925
        }
1926
      }
1927

    
1928
      if ($subquery_conditions->count()) {
1929
        $subquery->condition($subquery_conditions);
1930

    
1931
        $field = 'fid';
1932
        // Now handle entities.
1933
        if ($type == 'entity') {
1934
          // Set a common alias for entities.
1935
          $base_alias = $falias;
1936
          $field = 'entity_id';
1937
        }
1938
        $subquery->where("$falias.$field = fm_access.fid");
1939

    
1940
        // For an entity query, attach the subquery to entity conditions.
1941
        if ($type == 'entity') {
1942
          $file_conditions->exists($subquery);
1943
        }
1944
        // Otherwise attach it to the node query itself.
1945
        elseif ($table == 'file_managed') {
1946
          // Fix for https://drupal.org/node/2073085
1947
          $db_or = db_or();
1948
          $db_or->exists($subquery);
1949
          $db_or->isNull($falias . '.' . $field);
1950
          $query->condition($db_or);
1951
        }
1952
        else {
1953
          $query->exists($subquery);
1954
        }
1955
      }
1956
    }
1957
  }
1958

    
1959
  if ($type == 'entity' && $file_conditions->count()) {
1960
    // All the file access conditions are only for field values belonging to
1961
    // files.
1962
    $file_conditions->condition("$base_alias.entity_type", 'file');
1963
    $or = db_or();
1964
    $or->condition($file_conditions);
1965
    // If the field value belongs to a non-file entity type then this function
1966
    // does not do anything with it.
1967
    $or->condition("$base_alias.entity_type", 'file', '<>');
1968
    // Add the compiled set of rules to the query.
1969
    $query->condition($or);
1970
  }
1971
}
1972

    
1973
/**
1974
 * Implements hook_file_download().
1975
 */
1976
function file_entity_file_download($uri) {
1977
  // Load the file from the URI.
1978
  $file = file_uri_to_object($uri);
1979

    
1980
  // An existing file wasn't found, so we don't control access.
1981
  // E.g. image derivatives will fall here.
1982
  if (empty($file->fid)) {
1983
    return NULL;
1984
  }
1985

    
1986
  // Allow the user to download the file if they have appropriate permissions.
1987
  if (file_entity_access('view', $file)) {
1988
    return file_get_content_headers($file);
1989
  }
1990

    
1991
  return NULL;
1992
}
1993

    
1994
/**
1995
 * Helper function to generate standard file permission list for a given type.
1996
 *
1997
 * @param $type
1998
 *   The machine-readable name of the file type.
1999
 * @return array
2000
 *   An array of permission names and descriptions.
2001
 */
2002
function file_entity_list_permissions($type) {
2003
  $info = file_type_load($type);
2004

    
2005
  // Build standard list of file permissions for this type.
2006
  $permissions = array(
2007
    "edit own $type files" => array(
2008
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
2009
    ),
2010
    "edit any $type files" => array(
2011
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
2012
    ),
2013
    "delete own $type files" => array(
2014
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
2015
    ),
2016
    "delete any $type files" => array(
2017
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
2018
    ),
2019
    "download own $type files" => array(
2020
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
2021
    ),
2022
    "download any $type files" => array(
2023
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
2024
    ),
2025
  );
2026

    
2027
  return $permissions;
2028
}
2029

    
2030
/**
2031
 * Returns an array of file types that should be managed by permissions.
2032
 *
2033
 * By default, this will include all file types in the system. To exclude a
2034
 * specific file from getting permissions defined for it, set the
2035
 * file_entity_permissions_$type variable to 0. File entity does not provide an
2036
 * interface for doing so, however, contrib modules may exclude their own files
2037
 * in hook_install(). Alternatively, contrib modules may configure all file
2038
 * types at once, or decide to apply some other hook_file_entity_access()
2039
 * implementation to some or all file types.
2040
 *
2041
 * @return
2042
 *   An array of file types managed by this module.
2043
 */
2044
function file_entity_permissions_get_configured_types() {
2045

    
2046
  $configured_types = array();
2047

    
2048
  foreach (file_type_get_enabled_types() as $type => $info) {
2049
    if (variable_get('file_entity_permissions_' . $type, 1)) {
2050
      $configured_types[] = $type;
2051
    }
2052
  }
2053

    
2054
  return $configured_types;
2055
}
2056

    
2057
/**
2058
 * @} End of "defgroup file_entity_access".
2059
 *
2060
 * Implements hook_file_default_types().
2061
 */
2062
function file_entity_file_default_types() {
2063
  $types = array();
2064

    
2065
  // Image.
2066
  $types['image'] = (object) array(
2067
    'api_version' => 1,
2068
    'type' => 'image',
2069
    'label' => t('Image'),
2070
    'description' => t('An <em>Image</em> file is a still visual.'),
2071
    'mimetypes' => array(
2072
      'image/*',
2073
    ),
2074
  );
2075

    
2076
  // Video.
2077
  $types['video'] = (object) array(
2078
    'api_version' => 1,
2079
    'type' => 'video',
2080
    'label' => t('Video'),
2081
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2082
    'mimetypes' => array(
2083
      'video/*',
2084
    ),
2085
  );
2086

    
2087
  // Audio.
2088
  $types['audio'] = (object) array(
2089
    'api_version' => 1,
2090
    'type' => 'audio',
2091
    'label' => t('Audio'),
2092
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2093
    'mimetypes' => array(
2094
      'audio/*',
2095
    ),
2096
  );
2097

    
2098
  // Document.
2099
  $types['document'] = (object) array(
2100
    'api_version' => 1,
2101
    'type' => 'document',
2102
    'label' => t('Document'),
2103
    'description' => t('A <em>Document</em> file is written information.'),
2104
    'mimetypes' => array(
2105
      'text/plain',
2106
      'application/msword',
2107
      'application/vnd.ms-excel',
2108
      'application/pdf',
2109
      'application/vnd.ms-powerpoint',
2110
      'application/vnd.oasis.opendocument.text',
2111
      'application/vnd.oasis.opendocument.spreadsheet',
2112
      'application/vnd.oasis.opendocument.presentation',
2113
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2114
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2115
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2116
    ),
2117
  );
2118

    
2119
  return $types;
2120
}
2121

    
2122
/**
2123
 * Implements hook_file_operations().
2124
 */
2125
function file_entity_file_operations() {
2126
  $operations = array(
2127
    'permanent' => array(
2128
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2129
      'callback' => 'file_entity_mass_update',
2130
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2131
    ),
2132
    'temporary' => array(
2133
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2134
      'callback' => 'file_entity_mass_update',
2135
      'callback arguments' => array('updates' => array('status' => 0)),
2136
    ),
2137
    'delete' => array(
2138
      'label' => t('Delete selected files'),
2139
      'callback' => NULL,
2140
    ),
2141
  );
2142
  return $operations;
2143
}
2144

    
2145
/**
2146
 * Clear the field cache for any entities referencing a specific file.
2147
 *
2148
 * @param object $file
2149
 *   A file object.
2150
 */
2151
function file_entity_invalidate_field_caches($file) {
2152
  $entity_types = &drupal_static(__FUNCTION__);
2153

    
2154
  // Gather the list of entity types which support field caching.
2155
  if (!isset($entity_types)) {
2156
    $entity_types = array();
2157
    foreach (entity_get_info() as $entity_type => $entity_info) {
2158
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2159
        $entity_types[] = $entity_type;
2160
      }
2161
    }
2162
  }
2163

    
2164
  // If no entity types support field caching, then there is no work to be done.
2165
  if (empty($entity_types)) {
2166
    return;
2167
  }
2168

    
2169
  $records = db_query("SELECT DISTINCT type, id FROM {file_usage} WHERE fid = :fid AND type IN (:types) AND id > 0", array(':fid' => $file->fid, ':types' => $entity_types))->fetchAll();
2170
  if (!empty($records)) {
2171
    $cids = array();
2172
    foreach ($records as $record) {
2173
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2174
    }
2175
    cache_clear_all($cids, 'cache_field');
2176
  }
2177
}
2178

    
2179
/**
2180
 * Check if a file entity is considered local or not.
2181
 *
2182
 * @param object $file
2183
 *   A file entity object from file_load().
2184
 *
2185
 * @return
2186
 *   TRUE if the file is using a local stream wrapper, or FALSE otherwise.
2187
 */
2188
function file_entity_file_is_local($file) {
2189
  $scheme = file_uri_scheme($file->uri);
2190
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
2191
  return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
2192
}
2193

    
2194
/**
2195
 * Check if a file entity is considered writeable or not.
2196
 *
2197
 * @param object $file
2198
 *   A file entity object from file_load().
2199
 *
2200
 * @return
2201
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2202
 *   or FALSE otherwise.
2203
 */
2204
function file_entity_file_is_writeable($file) {
2205
  $scheme = file_uri_scheme($file->uri);
2206
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2207
  return !empty($wrappers[$scheme]);
2208
}
2209

    
2210
/**
2211
 * Pre-render callback for adding validation descriptions to file upload fields.
2212
 */
2213
function file_entity_upload_validators_pre_render($element) {
2214
  if (!empty($element['#upload_validators'])) {
2215
    if (!isset($element['#description'])) {
2216
      $element['#description'] = '';
2217
    }
2218
    if ($element['#description'] !== FALSE) {
2219
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2220
    }
2221
  }
2222
  return $element;
2223
}
2224

    
2225
/**
2226
 * @name pathauto_file Pathauto integration for the core file module.
2227
 * @{
2228
 */
2229

    
2230
/**
2231
 * Implements hook_file_insert() on behalf of pathauto.module.
2232
 */
2233
function pathauto_file_insert($file) {
2234
  pathauto_file_update_alias($file, 'insert');
2235
}
2236

    
2237
/**
2238
 * Implements hook_file_update() on behalf of pathauto.module.
2239
 */
2240
function pathauto_file_update($file) {
2241
  pathauto_file_update_alias($file, 'update');
2242
}
2243

    
2244
/**
2245
 * Implements hook_file_delete() on behalf of pathauto.module.
2246
 */
2247
function pathauto_file_delete($file) {
2248
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2249
}
2250

    
2251
/**
2252
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2253
 *
2254
 * Add the Pathauto settings to the file form.
2255
 */
2256
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2257
  $file = $form_state['file'];
2258
  $langcode = pathauto_entity_language('file', $file);
2259
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2260
}
2261

    
2262
/**
2263
 * Implements hook_file_operations() on behalf of pathauto.module.
2264
 */
2265
function pathauto_file_operations() {
2266
  $operations['pathauto_update_alias'] = array(
2267
    'label' => t('Update URL alias'),
2268
    'callback' => 'pathauto_file_update_alias_multiple',
2269
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2270
  );
2271
  return $operations;
2272
}
2273

    
2274
/**
2275
 * Update the URL aliases for an individual file.
2276
 *
2277
 * @param $file
2278
 *   A file object.
2279
 * @param $op
2280
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2281
 * @param $options
2282
 *   An optional array of additional options.
2283
 */
2284
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2285
  // Skip processing if the user has disabled pathauto for the file.
2286
  if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
2287
    return;
2288
  }
2289

    
2290
  $options += array('language' => pathauto_entity_language('file', $file));
2291

    
2292
  // Skip processing if the file has no pattern.
2293
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2294
    return;
2295
  }
2296

    
2297
  module_load_include('inc', 'pathauto');
2298
  $uri = entity_uri('file', $file);
2299
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2300
}
2301

    
2302
/**
2303
 * Update the URL aliases for multiple files.
2304
 *
2305
 * @param $fids
2306
 *   An array of file IDs.
2307
 * @param $op
2308
 *   Operation being performed on the files ('insert', 'update' or
2309
 *   'bulkupdate').
2310
 * @param $options
2311
 *   An optional array of additional options.
2312
 */
2313
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2314
  $options += array('message' => FALSE);
2315

    
2316
  $files = file_load_multiple($fids);
2317
  foreach ($files as $file) {
2318
    pathauto_file_update_alias($file, $op, $options);
2319
  }
2320

    
2321
  if (!empty($options['message'])) {
2322
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2323
  }
2324
}
2325

    
2326
/**
2327
 * Update action wrapper for pathauto_file_update_alias().
2328
 */
2329
function pathauto_file_update_action($file, $context = array()) {
2330
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2331
}
2332

    
2333
/**
2334
 * @} End of "name pathauto_file".
2335
 */
2336

    
2337
/**
2338
 * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
2339
 */
2340
function path_form_file_entity_edit_alter(&$form, $form_state) {
2341
  // Make sure this does not show up on the delete confirmation form.
2342
  if (empty($form_state['confirm_delete'])) {
2343
    $file = $form_state['file'];
2344
    $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
2345
    $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2346
    $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
2347
    $path = (isset($file->fid) ? path_load($conditions) : array());
2348
    if ($path === FALSE) {
2349
      $path = array();
2350
    }
2351
    $path += array(
2352
      'pid' => NULL,
2353
      'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
2354
      'alias' => '',
2355
      'language' => $langcode,
2356
    );
2357
    $form['path'] = array(
2358
      '#type' => 'fieldset',
2359
      '#title' => t('URL path settings'),
2360
      '#collapsible' => TRUE,
2361
      '#collapsed' => empty($path['alias']),
2362
      '#group' => 'additional_settings',
2363
      '#attributes' => array(
2364
        'class' => array('path-form'),
2365
      ),
2366
      '#attached' => array(
2367
        'js' => array(drupal_get_path('module', 'path') . '/path.js'),
2368
      ),
2369
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
2370
      '#weight' => 30,
2371
      '#tree' => TRUE,
2372
      '#element_validate' => array('path_form_element_validate'),
2373
    );
2374
    $form['path']['alias'] = array(
2375
      '#type' => 'textfield',
2376
      '#title' => t('URL alias'),
2377
      '#default_value' => $path['alias'],
2378
      '#maxlength' => 255,
2379
      '#description' => t('Optionally specify an alternative URL by which this file can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'),
2380
    );
2381
    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
2382
    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
2383
    $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
2384
  }
2385
}
2386

    
2387
/**
2388
 * Implements hook_file_insert() on behalf of path.module.
2389
 */
2390
function path_file_insert($file) {
2391
  if (isset($file->path)) {
2392
    $path = $file->path;
2393
    $path['alias'] = trim($path['alias']);
2394
    // Only save a non-empty alias.
2395
    if (!empty($path['alias'])) {
2396
      // Ensure fields for programmatic executions.
2397
      $path['source'] = 'file/' . $file->fid;
2398
      // Core does not provide a way to store the file language but contrib
2399
      // modules can do it so we need to take this into account.
2400
      $langcode = entity_language('file', $file);
2401
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2402
      path_save($path);
2403
    }
2404
  }
2405
}
2406

    
2407
/**
2408
 * Implements hook_file_update() on behalf of path.module.
2409
 */
2410
function path_file_update($file) {
2411
  if (isset($file->path)) {
2412
    $path = $file->path;
2413
    $path['alias'] = trim($path['alias']);
2414
    // Delete old alias if user erased it.
2415
    if (!empty($path['fid']) && empty($path['alias'])) {
2416
      path_delete($path['fid']);
2417
    }
2418
    // Only save a non-empty alias.
2419
    if (!empty($path['alias'])) {
2420
      // Ensure fields for programmatic executions.
2421
      $path['source'] = 'file/' . $file->fid;
2422
      // Core does not provide a way to store the file language but contrib
2423
      // modules can do it so we need to take this into account.
2424
      $langcode = entity_language('file', $file);
2425
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2426
      path_save($path);
2427
    }
2428
  }
2429
}
2430

    
2431
/**
2432
 * Implements hook_file_delete() on behalf of path.module.
2433
 */
2434
function path_file_delete($file) {
2435
  // Delete all aliases associated with this file.
2436
  path_delete(array('source' => 'file/' . $file->fid));
2437
}
2438

    
2439
/**
2440
 * Checks if pattern(s) match mimetype(s).
2441
 */
2442
function file_entity_match_mimetypes($needle, $haystack) {
2443
  $needle = is_array($needle) ? $needle : array($needle);
2444
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2445

    
2446
  foreach ($haystack as $mimetype) {
2447
    foreach ($needle as $search) {
2448
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2449
        return TRUE;
2450
      }
2451
    }
2452
  }
2453

    
2454
  return FALSE;
2455
}
2456

    
2457
/**
2458
 * A wrapper function for the PHP function fnmatch().
2459
 *
2460
 * We include this, because Windows servers do not implement fnmatch() until
2461
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2462
 */
2463
function file_entity_fnmatch($pattern, $string) {
2464
  if (!function_exists('fnmatch')) {
2465
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2466
  }
2467
  return fnmatch($pattern, $string);
2468
}
2469

    
2470
/**
2471
 * Return an URI for a file download.
2472
 */
2473
function file_entity_download_uri($file) {
2474
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2475
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2476
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2477
  }
2478
  return $uri;
2479
}
2480

    
2481
function file_entity_file_get_mimetype_type($file) {
2482
  list($type, $subtype) = explode('/', $file->filemime, 2);
2483
  return $type;
2484
}
2485

    
2486
/**
2487
 * Implements hook_admin_menu_map().
2488
 */
2489
function file_entity_admin_menu_map() {
2490
  if (!user_access('administer file types')) {
2491
    return;
2492
  }
2493
  $map['admin/structure/file-types/manage/%file_type'] = array(
2494
    'parent' => 'admin/structure/file-types',
2495
    'arguments' => array(
2496
      array('%file_type' => array_keys(file_entity_type_get_names())),
2497
    ),
2498
  );
2499
  return $map;
2500
}
2501

    
2502
/*
2503
 * Generates a token to protect a file download URL.
2504
 *
2505
 * This prevents unauthorized crawling of all file download URLs since the
2506
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
2507
 * to guess or attempt many at once. This can be costly both in CPU time
2508
 * and bandwidth.
2509
 *
2510
 * @see image_style_path_token()
2511
 *
2512
 * @param object $file
2513
 *   A file entity object.
2514
 *
2515
 * @return string
2516
 *   An eight-character token which can be used to protect file downloads
2517
 *   against denial-of-service attacks.
2518
 */
2519
function file_entity_get_download_token($file) {
2520
  // Return the first eight characters.
2521
  return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
2522
}
2523

    
2524
/**
2525
 * Find all fields that are of a certain field type.
2526
 *
2527
 * @param string $field_type
2528
 *   A field type.
2529
 *
2530
 * @return array
2531
 *   An array of field names that match the type $field_type.
2532
 */
2533
function _file_entity_get_fields_by_type($field_type) {
2534
  $return = array();
2535
  if (function_exists('field_info_field_map')) {
2536
    foreach (field_info_field_map() as $field_name => $field) {
2537
      if ($field['type'] == $field_type) {
2538
        $return[$field_name] = $field_name;
2539
      }
2540
    }
2541
  }
2542
  else {
2543
    foreach (field_info_fields() as $field_name => $field) {
2544
      if ($field['type'] == $field_type) {
2545
        $return[$field_name] = $field_name;
2546
      }
2547
    }
2548
  }
2549
  return $return;
2550
}
2551

    
2552
/**
2553
 * Implements hook_field_attach_load().
2554
 */
2555
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2556
  // Loop over all the entities looking for entities with attached images.
2557
  foreach ($entities as $entity) {
2558
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2559
    // Examine every image field instance attached to this entity's bundle.
2560
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2561
    foreach ($instances as $field_name => $instance) {
2562
      if (!empty($entity->{$field_name})) {
2563
        foreach ($entity->{$field_name} as $langcode => $items) {
2564
          foreach ($items as $delta => $item) {
2565
            // If alt and title text is not specified, fall back to alt and
2566
            // title text on the file.
2567
            if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
2568
              $file = file_load($item['fid']);
2569
              foreach (array('alt', 'title') as $key) {
2570
                if (empty($item[$key]) && !empty($file->{$key})) {
2571
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2572
                }
2573
              }
2574
            }
2575
          }
2576
        }
2577
      }
2578
    }
2579
  }
2580
}
2581

    
2582
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2583
  $wrappers = array();
2584
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2585
    if (empty($wrapper['private'])) {
2586
      $wrappers['public'][$key] = $wrapper['name'];
2587
    }
2588
    else {
2589
      $wrappers['private'][$key] = $wrapper['name'];
2590
    }
2591
  }
2592
  return $wrappers;
2593
}
2594

    
2595
/**
2596
 * Implements hook_features_pipe_alter() for the file type component.
2597
 */
2598
function file_entity_features_pipe_file_type_alter(&$pipe, $data, $export) {
2599
  foreach ($data as $file_type) {
2600
    $pipe['variable'][] = "pathauto_file_{$file_type}_pattern";
2601
  }
2602
}