Projet

Général

Profil

Paste
Télécharger (84,8 ko) Statistiques
| Branche: | Révision:

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

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
  );
83
  $info += array_fill_keys($hooks, array('group' => 'file'));
84
}
85

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

    
97
/**
98
 * Implements hook_help().
99
 */
100
function file_entity_help($path, $arg) {
101
  switch ($path) {
102
    case 'admin/structure/file-types':
103
      $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>';
104
      return $output;
105
    case 'admin/structure/file-types/manage/%/display/preview':
106
    case 'admin/structure/file-types/manage/%/file-display/preview':
107
      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');
108
      break;
109
  }
110
}
111

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

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

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

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

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

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

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

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

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

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

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

    
388
  return $items;
389
}
390

    
391
/**
392
 * Implements hook_menu_local_tasks_alter().
393
 */
394
function file_entity_menu_local_tasks_alter(&$data, $router_item, $root_path) {
395
  // Add action link to 'file/add' on 'admin/content/file' page.
396
  if ($root_path == 'admin/content/file') {
397
    $item = menu_get_item('file/add');
398
    if (!empty($item['access'])) {
399
      $data['actions']['output'][] = array(
400
        '#theme' => 'menu_local_action',
401
        '#link' => $item,
402
        '#weight' => $item['weight'],
403
      );
404
    }
405
  }
406
}
407

    
408
/**
409
 * Implement hook_permission().
410
 */
411
function file_entity_permission() {
412
  $permissions = array(
413
    'bypass file access' => array(
414
      'title' => t('Bypass file access control'),
415
      'description' => t('View, edit and delete all files regardless of permission restrictions.'),
416
      'restrict access' => TRUE,
417
    ),
418
    'administer file types' => array(
419
      'title' => t('Administer file types'),
420
      'restrict access' => TRUE,
421
    ),
422
    'administer files' => array(
423
      'title' => t('Administer files'),
424
      'restrict access' => TRUE,
425
    ),
426
    'create files' => array(
427
      'title' => t('Add and upload new files'),
428
    ),
429
    'view own private files' => array(
430
      'title' => t('View own private files'),
431
    ),
432
    'view own files' => array(
433
      'title' => t('View own files'),
434
    ),
435
    'view private files' => array(
436
      'title' => t('View private files'),
437
      'restrict access' => TRUE,
438
    ),
439
    'view files' => array(
440
      'title' => t('View files'),
441
    ),
442
  );
443

    
444
  // Add description for the 'View file details' and 'View own private file
445
  // details' permissions to show which stream wrappers they apply to.
446
  $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
447
  $wrappers += array('public' => array(t('None')), 'private' => array(t('None')));
448

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

    
452
  // Generate standard file permissions for all applicable file types.
453
  foreach (file_entity_permissions_get_configured_types() as $type) {
454
    $permissions += file_entity_list_permissions($type);
455
  }
456

    
457
  return $permissions;
458
}
459

    
460
/*
461
 * Implements hook_cron_queue_info().
462
 */
463
function file_entity_cron_queue_info() {
464
  $queues['file_entity_type_determine'] = array(
465
    'worker callback' => 'file_entity_type_determine',
466
  );
467
  return $queues;
468
}
469

    
470
/*
471
 * Determines file type for a given file ID and saves the file.
472
 *
473
 * @param $fid
474
 *   A file ID.
475
 */
476
function file_entity_type_determine($fid) {
477
  if ($file = file_load($fid)) {
478
    // The file type will be automatically determined when saving the file.
479
    file_save($file);
480
  }
481
}
482

    
483
/**
484
 * Gather the rankings from the the hook_ranking implementations.
485
 *
486
 * @param $query
487
 *   A query object that has been extended with the Search DB Extender.
488
 */
489
function _file_entity_rankings(SelectQueryExtender $query) {
490
  if ($ranking = module_invoke_all('file_ranking')) {
491
    $tables = &$query->getTables();
492
    foreach ($ranking as $rank => $values) {
493
      if ($file_rank = variable_get('file_entity_rank_' . $rank, 0)) {
494
        // If the table defined in the ranking isn't already joined, then add it.
495
        if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
496
          $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
497
        }
498
        $arguments = isset($values['arguments']) ? $values['arguments'] : array();
499
        $query->addScore($values['score'], $arguments, $file_rank);
500
      }
501
    }
502
  }
503
}
504

    
505
/**
506
 * Implements hook_search_info().
507
 */
508
function file_entity_search_info() {
509
  return array(
510
    'title' => 'Files',
511
    'path' => 'file',
512
  );
513
}
514

    
515
/**
516
 * Implements hook_search_access().
517
 */
518
function file_entity_search_access() {
519
  return user_access('view own private files') || user_access('view own files') || user_access('view private files') || user_access('view files');
520
}
521

    
522
/**
523
 * Implements hook_search_reset().
524
 */
525
function file_entity_search_reset() {
526
  db_update('search_dataset')
527
    ->fields(array('reindex' => REQUEST_TIME))
528
    ->condition('type', 'file')
529
    ->execute();
530
}
531

    
532
/**
533
 * Implements hook_search_status().
534
 */
535
function file_entity_search_status() {
536
  $total = db_query('SELECT COUNT(*) FROM {file_managed}')->fetchField();
537
  $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();
538
  return array('remaining' => $remaining, 'total' => $total);
539
}
540

    
541
/**
542
 * Implements hook_search_admin().
543
 */
544
function file_entity_search_admin() {
545
  // Output form for defining rank factor weights.
546
  $form['file_ranking'] = array(
547
    '#type' => 'fieldset',
548
    '#title' => t('File ranking'),
549
  );
550
  $form['file_ranking']['#theme'] = 'file_entity_search_admin';
551
  $form['file_ranking']['info'] = array(
552
    '#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>'
553
  );
554

    
555
  // Note: reversed to reflect that higher number = higher ranking.
556
  $options = drupal_map_assoc(range(0, 10));
557
  foreach (module_invoke_all('file_ranking') as $var => $values) {
558
    $form['file_ranking']['factors']['file_entity_rank_' . $var] = array(
559
      '#title' => $values['title'],
560
      '#type' => 'select',
561
      '#options' => $options,
562
      '#default_value' => variable_get('file_entity_rank_' . $var, 0),
563
    );
564
  }
565
  return $form;
566
}
567

    
568
/**
569
 * Implements hook_search_execute().
570
 */
571
function file_entity_search_execute($keys = NULL, $conditions = NULL) {
572
  global $user;
573

    
574
  // Build matching conditions
575
  $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
576
  $query->join('file_managed', 'fm', 'fm.fid = i.sid');
577
  $query->searchExpression($keys, 'file');
578

    
579
  // Insert special keywords.
580
  $query->setOption('type', 'fm.type');
581
  if ($query->setOption('term', 'ti.tid')) {
582
    $query->join('taxonomy_index', 'ti', 'fm.fid = ti.fid');
583
  }
584
  // Only continue if the first pass query matches.
585
  if (!$query->executeFirstPass()) {
586
    return array();
587
  }
588

    
589
  // Add the ranking expressions.
590
  _file_entity_rankings($query);
591

    
592
  // Load results.
593
  $find = $query
594
    ->limit(10)
595
    ->addTag('file_access')
596
    ->execute();
597
  $results = array();
598
  foreach ($find as $item) {
599
    // Render the file.
600
    $file = file_load($item->sid);
601
    $build = file_view($file, 'search_result');
602
    unset($build['#theme']);
603
    $file->rendered = drupal_render($build);
604

    
605
    $extra = module_invoke_all('file_entity_search_result', $file);
606

    
607
    $types = file_entity_type_get_names();
608

    
609
    $uri = entity_uri('file', $file);
610
    $results[] = array(
611
      'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
612
      'type' => check_plain($types[$file->type]),
613
      'title' => $file->filename,
614
      'user' => theme('username', array('account' => user_load($file->uid))),
615
      'date' => $file->timestamp,
616
      'file' => $file,
617
      'extra' => $extra,
618
      'score' => $item->calculated_score,
619
      'snippet' => search_excerpt($keys, $file->rendered),
620
      'language' => function_exists('entity_language') ? entity_language('file', $file) : NULL,
621
    );
622
  }
623
  return $results;
624
}
625

    
626
/**
627
 * Implements hook_file_ranking().
628
 */
629
function file_entity_file_ranking() {
630
  // Create the ranking array and add the basic ranking options.
631
  $ranking = array(
632
    'relevance' => array(
633
      'title' => t('Keyword relevance'),
634
      // Average relevance values hover around 0.15
635
      'score' => 'i.relevance',
636
    ),
637
  );
638

    
639
  // Add relevance based on creation date.
640
  if ($file_cron_last = variable_get('file_entity_cron_last', 0)) {
641
    $ranking['timestamp'] = array(
642
      'title' => t('Recently posted'),
643
      // Exponential decay with half-life of 6 months, starting at last indexed file
644
      'score' => 'POW(2.0, (fm.timestamp - :file_cron_last) * 6.43e-8)',
645
      'arguments' => array(':file_cron_last' => $file_cron_last),
646
    );
647
  }
648
  return $ranking;
649
}
650

    
651
/**
652
 * Returns HTML for the file ranking part of the search settings admin page.
653
 *
654
 * @param $variables
655
 *   An associative array containing:
656
 *   - form: A render element representing the form.
657
 *
658
 * @ingroup themeable
659
 */
660
function theme_file_entity_search_admin($variables) {
661
  $form = $variables['form'];
662

    
663
  $output = drupal_render($form['info']);
664

    
665
  $header = array(t('Factor'), t('Weight'));
666
  foreach (element_children($form['factors']) as $key) {
667
    $row = array();
668
    $row[] = $form['factors'][$key]['#title'];
669
    $form['factors'][$key]['#title_display'] = 'invisible';
670
    $row[] = drupal_render($form['factors'][$key]);
671
    $rows[] = $row;
672
  }
673
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
674

    
675
  $output .= drupal_render_children($form);
676
  return $output;
677
}
678

    
679
/**
680
 * Implements hook_update_index().
681
 */
682
function file_entity_update_index() {
683
  $limit = (int)variable_get('search_cron_limit', 100);
684

    
685
  $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'));
686

    
687
  foreach ($result as $file) {
688
    _file_entity_index_file($file);
689
  }
690
}
691

    
692
/**
693
 * Index a single file.
694
 *
695
 * @param $file
696
 *   The file to index.
697
 */
698
function _file_entity_index_file($file) {
699
  $file = file_load($file->fid);
700

    
701
  // Save the creation time of the most recent indexed file, for the search
702
  // results half-life calculation.
703
  variable_set('file_entity_cron_last', $file->timestamp);
704

    
705
  // Render the file.
706
  $build = file_view($file, 'search_index');
707
  unset($build['#theme']);
708
  $file->rendered = drupal_render($build);
709

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

    
712
  // Fetch extra data normally not visible
713
  $extra = module_invoke_all('file_entity_update_index', $file);
714
  foreach ($extra as $t) {
715
    $text .= $t;
716
  }
717

    
718
  // Update index
719
  search_index($file->fid, 'file', $text);
720
}
721

    
722
/**
723
 * Implements hook_form_FORM_ID_alter().
724
 */
725
function file_entity_form_search_form_alter(&$form, $form_state) {
726
  if (isset($form['module']) && $form['module']['#value'] == 'file_entity' && user_access('use advanced search')) {
727
    // Keyword boxes:
728
    $form['advanced'] = array(
729
      '#type' => 'fieldset',
730
      '#title' => t('Advanced search'),
731
      '#collapsible' => TRUE,
732
      '#collapsed' => TRUE,
733
      '#attributes' => array('class' => array('search-advanced')),
734
    );
735
    $form['advanced']['keywords'] = array(
736
      '#prefix' => '<div class="criterion">',
737
      '#suffix' => '</div>',
738
    );
739
    $form['advanced']['keywords']['or'] = array(
740
      '#type' => 'textfield',
741
      '#title' => t('Containing any of the words'),
742
      '#size' => 30,
743
      '#maxlength' => 255,
744
    );
745
    $form['advanced']['keywords']['phrase'] = array(
746
      '#type' => 'textfield',
747
      '#title' => t('Containing the phrase'),
748
      '#size' => 30,
749
      '#maxlength' => 255,
750
    );
751
    $form['advanced']['keywords']['negative'] = array(
752
      '#type' => 'textfield',
753
      '#title' => t('Containing none of the words'),
754
      '#size' => 30,
755
      '#maxlength' => 255,
756
    );
757

    
758
    // File types:
759
    $types = array_map('check_plain', file_entity_type_get_names());
760
    $form['advanced']['type'] = array(
761
      '#type' => 'checkboxes',
762
      '#title' => t('Only of the type(s)'),
763
      '#prefix' => '<div class="criterion">',
764
      '#suffix' => '</div>',
765
      '#options' => $types,
766
    );
767
    $form['advanced']['submit'] = array(
768
      '#type' => 'submit',
769
      '#value' => t('Advanced search'),
770
      '#prefix' => '<div class="action">',
771
      '#suffix' => '</div>',
772
      '#weight' => 100,
773
    );
774

    
775
    $form['#validate'][] = 'file_entity_search_validate';
776
  }
777
}
778

    
779
/**
780
 * Form API callback for the search form. Registered in file_entity_form_alter().
781
 */
782
function file_entity_search_validate($form, &$form_state) {
783
  // Initialize using any existing basic search keywords.
784
  $keys = $form_state['values']['processed_keys'];
785

    
786
  // Insert extra restrictions into the search keywords string.
787
  if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
788
    // Retrieve selected types - Form API sets the value of unselected
789
    // checkboxes to 0.
790
    $form_state['values']['type'] = array_filter($form_state['values']['type']);
791
    if (count($form_state['values']['type'])) {
792
      $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
793
    }
794
  }
795

    
796
  if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
797
    $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
798
  }
799
  if ($form_state['values']['or'] != '') {
800
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
801
      $keys .= ' ' . implode(' OR ', $matches[1]);
802
    }
803
  }
804
  if ($form_state['values']['negative'] != '') {
805
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
806
      $keys .= ' -' . implode(' -', $matches[1]);
807
    }
808
  }
809
  if ($form_state['values']['phrase'] != '') {
810
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
811
  }
812
  if (!empty($keys)) {
813
    form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
814
  }
815
}
816

    
817
/**
818
 * Implements hook_admin_paths().
819
 */
820
function file_entity_admin_paths() {
821
  $paths = array(
822
    'file/add' => TRUE,
823
    'file/add/*' => TRUE,
824
    'file/*/edit' => TRUE,
825
    'file/*/usage' => TRUE,
826
    'file/*/delete' => TRUE,
827
  );
828
  return $paths;
829
}
830

    
831
/**
832
 * Implements hook_action_info_alter().
833
 */
834
function file_entity_action_info_alter(&$actions) {
835
  if (module_exists('pathauto')) {
836
    $actions['pathauto_file_update_action'] = array(
837
      'type' => 'file',
838
      'label' => t('Update file alias'),
839
      'configurable' => FALSE,
840
    );
841
  }
842
}
843

    
844
/**
845
 * Implements hook_theme().
846
 */
847
function file_entity_theme() {
848
  return array(
849
    'file_entity' => array(
850
      'render element' => 'elements',
851
      'template' => 'file_entity',
852
    ),
853
    'file_entity_search_admin' => array(
854
      'render element' => 'form',
855
    ),
856
    'file_entity_file_type_overview' => array(
857
      'variables' => array('label' => NULL, 'description' => NULL),
858
      'file' => 'file_entity.admin.inc',
859
    ),
860
    'file_entity_file_display_order' => array(
861
      'render element' => 'element',
862
      'file' => 'file_entity.admin.inc',
863
    ),
864
    'file_entity_file_link' => array(
865
      'variables' => array('file' => NULL, 'icon_directory' => NULL),
866
      'file' => 'file_entity.theme.inc',
867
    ),
868
    'file_entity_download_link' => array(
869
      'variables' => array('file' => NULL, 'icon_directory' => NULL, 'text' => NULL),
870
      'file' => 'file_entity.theme.inc',
871
    ),
872
    'file_entity_file_audio' => array(
873
      'variables' => array(
874
        'files' => array(),
875
        'controls' => TRUE,
876
        'autoplay' => FALSE,
877
        'loop' => FALSE,
878
      ),
879
      'file' => 'file_entity.theme.inc',
880
    ),
881
    'file_entity_file_video' => array(
882
      'variables' => array(
883
        'files' => array(),
884
        'controls' => TRUE,
885
        'autoplay' => FALSE,
886
        'loop' => FALSE,
887
        'muted' => FALSE,
888
        'width' => NULL,
889
        'height' => NULL,
890
      ),
891
      'file' => 'file_entity.theme.inc',
892
    ),
893
  );
894
}
895

    
896
/**
897
 * Implements hook_entity_info_alter().
898
 *
899
 * Extends the core file entity to be fieldable. The file type is used as the
900
 * bundle key. File types are implemented as CTools exportables, so modules can
901
 * define default file types via hook_file_default_types(), and the
902
 * administrator can override the default types or add custom ones via
903
 * admin/structure/file-types.
904
 */
905
function file_entity_entity_info_alter(&$entity_info) {
906
  $entity_info['file']['fieldable'] = TRUE;
907
  $entity_info['file']['entity keys']['bundle'] = 'type';
908
  $entity_info['file']['bundle keys']['bundle'] = 'type';
909
  $entity_info['file']['bundles'] = array();
910
  $entity_info['file']['uri callback'] = 'file_entity_uri';
911
  $entity_info['file']['view modes']['teaser'] = array(
912
    'label' => t('Teaser'),
913
    'custom settings' => TRUE,
914
  );
915
  $entity_info['file']['view modes']['full'] = array(
916
    'label' => t('Full content'),
917
    'custom settings' => FALSE,
918
  );
919
  $entity_info['file']['view modes']['preview'] = array(
920
    'label' => t('Preview'),
921
    'custom settings' => TRUE,
922
  );
923
  $entity_info['file']['view modes']['rss'] = array(
924
    'label' => t('RSS'),
925
    'custom settings' => FALSE,
926
  );
927

    
928
  // Search integration is provided by file_entity.module, so search-related
929
  // view modes for files are defined here and not in search.module.
930
  if (module_exists('search')) {
931
    $entity_info['file']['view modes']['search_index'] = array(
932
      'label' => t('Search index'),
933
      'custom settings' => FALSE,
934
    );
935
    $entity_info['file']['view modes']['search_result'] = array(
936
      'label' => t('Search result'),
937
      'custom settings' => FALSE,
938
    );
939
  }
940

    
941
  foreach (file_type_get_enabled_types() as $type) {
942
    $entity_info['file']['bundles'][$type->type] = array(
943
      'label' => $type->label,
944
      'admin' => array(
945
        'path' => 'admin/structure/file-types/manage/%file_type',
946
        'real path' => 'admin/structure/file-types/manage/' . $type->type,
947
        'bundle argument' => 4,
948
      ),
949
    );
950
  }
951

    
952
  // Enable Metatag support.
953
  $entity_info['file']['metatags'] = TRUE;
954

    
955
  // Ensure some of the Entity API callbacks are supported.
956
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
957
  $entity_info['file']['view callback'] = 'file_view_multiple';
958
  $entity_info['file']['edit callback'] = 'file_entity_metadata_form_file';
959
  $entity_info['file']['access callback'] = 'file_entity_access';
960

    
961
  // Add integration with the Title module for file name replacement support.
962
  $entity_info['file']['field replacement'] = array(
963
    'filename' => array(
964
      'field' => array(
965
        'type' => 'text',
966
        'cardinality' => 1,
967
        'translatable' => TRUE,
968
      ),
969
      'instance' => array(
970
        'label' => t('File name'),
971
        'description' => t('A field replacing file name.'),
972
        'required' => TRUE,
973
        'settings' => array(
974
          'text_processing' => 0,
975
        ),
976
        'widget' => array(
977
          'weight' => -5,
978
        ),
979
        'display' => array(
980
          'default' => array(
981
            'type' => 'hidden',
982
          ),
983
        ),
984
      ),
985
      'preprocess_key' => 'filename',
986
    ),
987
  );
988
}
989

    
990
/**
991
 * Implements hook_entity_property_info().
992
 */
993
function file_entity_entity_property_info() {
994
  $info['file']['properties']['type'] = array(
995
    'label' => t('File type'),
996
    'type' => 'token',
997
    'description' => t('The type of the file.'),
998
    'setter callback' => 'entity_property_verbatim_set',
999
    'setter permission' => 'administer files',
1000
    'options list' => 'file_entity_type_get_names',
1001
    'required' => TRUE,
1002
    'schema field' => 'type',
1003
  );
1004

    
1005
  return $info;
1006
}
1007

    
1008
/**
1009
 * Implements hook_field_display_ENTITY_TYPE_alter().
1010
 */
1011
function file_entity_field_display_file_alter(&$display, $context) {
1012
  // Hide field labels in search index.
1013
  if ($context['view_mode'] == 'search_index') {
1014
    $display['label'] = 'hidden';
1015
  }
1016
}
1017

    
1018
/**
1019
 * URI callback for file entities.
1020
 */
1021
function file_entity_uri($file) {
1022
  $uri['path'] = 'file/' . $file->fid;
1023
  return $uri;
1024
}
1025

    
1026
/**
1027
 * Entity API callback to get the form of a file entity.
1028
 */
1029
function file_entity_metadata_form_file($file) {
1030
  // Pre-populate the form-state with the right form include.
1031
  $form_state['build_info']['args'] = array($file);
1032
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
1033
  return drupal_build_form('file_entity_edit', $form_state);
1034
}
1035

    
1036
/**
1037
 * Implements hook_ctools_plugin_directory().
1038
 */
1039

    
1040
function file_entity_ctools_plugin_directory($module, $type) {
1041
  if ($module == 'ctools' && $type == 'content_types') {
1042
    return 'plugins/' . $type;
1043
  }
1044
}
1045

    
1046
/**
1047
 * Implements hook_field_extra_fields().
1048
 *
1049
 * Adds 'file' as an extra field, so that its display and form component can be
1050
 * weighted relative to the fields that are added to file entity bundles.
1051
 */
1052
function file_entity_field_extra_fields() {
1053
  $info = array();
1054

    
1055
  if ($file_type_names = file_entity_type_get_names()) {
1056
    foreach ($file_type_names as $type => $name) {
1057
      $info['file'][$type]['form']['filename'] = array(
1058
        'label' => t('File name'),
1059
        'description' => t('File name'),
1060
        'weight' => -10,
1061
      );
1062
      $info['file'][$type]['form']['preview'] = array(
1063
        'label' => t('File'),
1064
        'description' => t('File preview'),
1065
        'weight' => -5,
1066
      );
1067
      $info['file'][$type]['display']['file'] = array(
1068
        'label' => t('File'),
1069
        'description' => t('File display'),
1070
        'weight' => 0,
1071
      );
1072
    }
1073
  }
1074

    
1075
  return $info;
1076
}
1077

    
1078
/**
1079
 * Implements hook_file_formatter_info().
1080
 */
1081
function file_entity_file_formatter_info() {
1082
  $formatters = array();
1083

    
1084
  // Allow file field formatters to be reused for displaying the file entity's
1085
  // file pseudo-field.
1086
  foreach (field_info_formatter_types() as $key => $formatter) {
1087
    if (array_intersect($formatter['field types'], array('file', 'image'))) {
1088
      $key = 'file_field_' . $key;
1089
      $formatters[$key] = array(
1090
        'label' => $formatter['label'],
1091
        'description' => !empty($formatter['description']) ? $formatter['description'] : '',
1092
        'view callback' => 'file_entity_file_formatter_file_field_view',
1093
      );
1094
      if (!empty($formatter['settings'])) {
1095
        $formatters[$key] += array(
1096
          'default settings' => $formatter['settings'],
1097
          'settings callback' => 'file_entity_file_formatter_file_field_settings',
1098
        );
1099
      }
1100
      if (!empty($formatter['file formatter'])) {
1101
        $formatters[$key] += $formatter['file formatter'];
1102
      }
1103
    }
1104
  }
1105

    
1106
  // Add a simple file formatter for displaying an image in a chosen style.
1107
  if (module_exists('image')) {
1108
    $formatters['file_image'] = array(
1109
      'label' => t('Image'),
1110
      'default settings' => array(
1111
        'image_style' => '',
1112
        'alt' => '[file:field_file_image_alt_text]',
1113
        'title' => '[file:field_file_image_title_text]'
1114
      ),
1115
      'view callback' => 'file_entity_file_formatter_file_image_view',
1116
      'settings callback' => 'file_entity_file_formatter_file_image_settings',
1117
      'hidden' => TRUE,
1118
      'mime types' => array('image/*'),
1119
    );
1120
  }
1121

    
1122
  return $formatters;
1123
}
1124

    
1125
/**
1126
 * Implements hook_file_formatter_FORMATTER_view().
1127
 *
1128
 * This function provides a bridge to the field formatter API, so that file
1129
 * field formatters can be reused for displaying the file entity's file
1130
 * pseudo-field.
1131
 */
1132
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
1133
  if (strpos($display['type'], 'file_field_') === 0) {
1134
    $field_formatter_type = substr($display['type'], strlen('file_field_'));
1135
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1136
    if (isset($field_formatter_info['module'])) {
1137
      // Set $display['type'] to what hook_field_formatter_*() expects.
1138
      $display['type'] = $field_formatter_type;
1139

    
1140
      // Allow any attribute overrides (e.g. from the Media module) to be
1141
      // respected.
1142
      $item = (array) $file;
1143
      if (!empty($file->override['attributes'])) {
1144
        $item = array_merge($item, $file->override['attributes']);
1145
      }
1146

    
1147
      // Set $items to what file field formatters expect. See file_field_load(),
1148
      // and note that, here, $file is already a fully loaded entity.
1149
      $items = array($item);
1150

    
1151
      // Invoke hook_field_formatter_prepare_view() and
1152
      // hook_field_formatter_view(). Note that we are reusing field formatter
1153
      // functions, but we are not displaying a Field API field, so we set
1154
      // $field and $instance accordingly, and do not invoke
1155
      // hook_field_prepare_view(). This assumes that the formatter functions do
1156
      // not rely on $field or $instance. A module that implements formatter
1157
      // functions that rely on $field or $instance (and therefore, can only be
1158
      // used for real fields) can prevent this formatter from being used on the
1159
      // pseudo-field by removing it within hook_file_formatter_info_alter().
1160
      $field = $instance = NULL;
1161
      if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
1162
        $fid = $file->fid;
1163
        // hook_field_formatter_prepare_view() alters $items by reference.
1164
        $grouped_items = array($fid => &$items);
1165
        $function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
1166
      }
1167
      if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
1168
        $element = $function('file', $file, $field, $instance, $langcode, $items, $display);
1169
        // We passed the file as $items[0], so return the corresponding element.
1170
        if (isset($element[0])) {
1171
          return $element[0];
1172
        }
1173
      }
1174
    }
1175
  }
1176
}
1177

    
1178
/**
1179
 * Implements hook_file_formatter_FORMATTER_settings().
1180
 *
1181
 * This function provides a bridge to the field formatter API, so that file
1182
 * field formatters can be reused for displaying the file entity's file
1183
 * pseudo-field.
1184
 */
1185
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
1186
  if (strpos($formatter_type, 'file_field_') === 0) {
1187
    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
1188
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1189

    
1190
    // Invoke hook_field_formatter_settings_form(). We are reusing field
1191
    // formatter functions, but we are not working with a Field API field, so
1192
    // set $field accordingly. Unfortunately, the API is for $settings to be
1193
    // transfered via the $instance parameter, so we must mock it.
1194
    if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
1195
      $field = NULL;
1196
      $mock_instance = array(
1197
        'display' => array(
1198
          $view_mode => array(
1199
            'type' => $field_formatter_type,
1200
            'settings' => $settings,
1201
          ),
1202
        ),
1203
        'entity_type' => 'file',
1204
        'bundle' => $file_type,
1205
      );
1206
      return $function($field, $mock_instance, $view_mode, $form, $form_state);
1207
    }
1208
  }
1209
}
1210

    
1211
/**
1212
 * Implements hook_file_formatter_FORMATTER_view().
1213
 *
1214
 * Returns a drupal_render() array to display an image of the chosen style.
1215
 *
1216
 * This formatter is only capable of displaying local images. If the passed in
1217
 * file is either not local or not an image, nothing is returned, so that
1218
 * file_view_file() can try another formatter.
1219
 */
1220
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
1221
  // Prevent PHP notices when trying to read empty files.
1222
  // @see http://drupal.org/node/681042
1223
  if (!$file->filesize) {
1224
    return;
1225
  }
1226

    
1227
  // Do not bother proceeding if this file does not have an image mime type.
1228
  if (file_entity_file_get_mimetype_type($file) != 'image') {
1229
    return;
1230
  }
1231

    
1232
  if (file_entity_file_is_readable($file)) {
1233
    // We don't sanitize here.
1234
    // @see http://drupal.org/node/1553094#comment-6257382
1235
    // Theme function will take care of escaping.
1236
    if (!isset($file->metadata)) {
1237
      $file->metadata = array();
1238
    }
1239
    $file->metadata += array('width' => NULL, 'height' => NULL);
1240
    $replace_options = array(
1241
      'clear' => TRUE,
1242
      'sanitize' => FALSE,
1243
    );
1244
    if (!empty($display['settings']['image_style'])) {
1245
      $element = array(
1246
        '#theme' => 'image_style',
1247
        '#style_name' => $display['settings']['image_style'],
1248
        '#path' => $file->uri,
1249
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1250
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1251
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1252
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1253
      );
1254
    }
1255
    else {
1256
      $element = array(
1257
        '#theme' => 'image',
1258
        '#path' => $file->uri,
1259
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1260
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1261
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1262
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1263
      );
1264
    }
1265
    return $element;
1266
  }
1267
}
1268

    
1269
/**
1270
 * Check if a file entity is readable or not.
1271
 *
1272
 * @param object $file
1273
 *   A file entity object from file_load().
1274
 *
1275
 * @return boolean
1276
 *   TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
1277
 */
1278
function file_entity_file_is_readable($file) {
1279
  $scheme = file_uri_scheme($file->uri);
1280
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
1281
  return !empty($wrappers[$scheme]);
1282
}
1283

    
1284
/**
1285
 * Implements hook_file_formatter_FORMATTER_settings().
1286
 *
1287
 * Returns form elements for configuring the 'file_image' formatter.
1288
 */
1289
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
1290
  $element = array();
1291
  $element['image_style'] = array(
1292
    '#title' => t('Image style'),
1293
    '#type' => 'select',
1294
    '#options' => image_style_options(FALSE),
1295
    '#default_value' => $settings['image_style'],
1296
    '#empty_option' => t('None (original image)'),
1297
  );
1298

    
1299
  // For image files we allow the alt attribute (required in HTML).
1300
  $element['alt'] = array(
1301
    '#title' => t('Alt attribute'),
1302
    '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
1303
    '#type' => 'textfield',
1304
    '#default_value' => $settings['alt'],
1305
  );
1306

    
1307
  // Allow the setting of the title attribute.
1308
  $element['title'] = array(
1309
    '#title' => t('Title attribute'),
1310
    '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
1311
    '#type' => 'textfield',
1312
    '#default_value' => $settings['title'],
1313
  );
1314

    
1315
  if (module_exists('token')) {
1316
    $element['alt']['#description'] .= t('This field supports tokens.');
1317
    $element['title']['#description'] .= t('This field supports tokens.');
1318
    $element['tokens'] = array(
1319
      '#theme' => 'token_tree',
1320
      '#token_types' => array('file'),
1321
      '#dialog' => TRUE,
1322
    );
1323
  }
1324

    
1325
  return $element;
1326
}
1327

    
1328
/**
1329
 * Menu access callback for the 'view mode file display settings' pages.
1330
 *
1331
 * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
1332
 * be enabled.
1333
 */
1334
function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
1335
  // Deny access if the view mode isn't configured to use custom display
1336
  // settings.
1337
  $view_mode_settings = field_view_mode_settings('file', $file_type->type);
1338
  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
1339
  if (!$visibility) {
1340
    return FALSE;
1341
  }
1342

    
1343
  // Otherwise, continue to an $access_callback check.
1344
  $args = array_slice(func_get_args(), 3);
1345
  $callback = empty($access_callback) ? 0 : trim($access_callback);
1346
  if (is_numeric($callback)) {
1347
    return (bool) $callback;
1348
  }
1349
  elseif (function_exists($access_callback)) {
1350
    return call_user_func_array($access_callback, $args);
1351
  }
1352
}
1353

    
1354
/**
1355
 * Implements hook_modules_enabled().
1356
 */
1357
function file_entity_modules_enabled($modules) {
1358
  file_info_cache_clear();
1359
}
1360

    
1361
/**
1362
 * Implements hook_modules_disabled().
1363
 */
1364
function file_entity_modules_disabled($modules) {
1365
  file_info_cache_clear();
1366
}
1367

    
1368
/**
1369
 * Implements hook_views_api().
1370
 */
1371
function file_entity_views_api() {
1372
  return array(
1373
    'api' => 3,
1374
  );
1375
}
1376

    
1377
/**
1378
 * Returns whether the current page is the full page view of the passed-in file.
1379
 *
1380
 * @param $file
1381
 *   A file object.
1382
 */
1383
function file_entity_is_page($file) {
1384
  $page_file = menu_get_object('file', 1);
1385
  return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
1386
}
1387

    
1388
/**
1389
 * Process variables for file_entity.tpl.php
1390
 *
1391
 * The $variables array contains the following arguments:
1392
 * - $file
1393
 * - $view_mode
1394
 *
1395
 * @see file_entity.tpl.php
1396
 */
1397
function template_preprocess_file_entity(&$variables) {
1398
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
1399
  $variables['file'] = $variables['elements']['#file'];
1400
  $file = $variables['file'];
1401

    
1402
  $variables['id'] = drupal_html_id('file-'. $file->fid);
1403
  $variables['date']      = format_date($file->timestamp);
1404
  $account = user_load($file->uid);
1405
  $variables['name']      = theme('username', array('account' => $account));
1406

    
1407
  $uri = entity_uri('file', $file);
1408
  $variables['file_url']  = url($uri['path'], $uri['options']);
1409
  $label = entity_label('file', $file);
1410
  $variables['label']     = check_plain($label);
1411
  $variables['page']      = $view_mode == 'full' && file_entity_is_page($file);
1412

    
1413
  // Hide the file name from being displayed until we can figure out a better
1414
  // way to control this. We cannot simply not output the title since
1415
  // contextual links require $title_suffix to be output in the template.
1416
  // @see http://drupal.org/node/1245266
1417
  if (!$variables['page']) {
1418
    $variables['title_attributes_array']['class'][] = 'element-invisible';
1419
  }
1420

    
1421
  // Flatten the file object's member fields.
1422
  $variables = array_merge((array) $file, $variables);
1423

    
1424
  // Helpful $content variable for templates.
1425
  $variables += array('content' => array());
1426
  foreach (element_children($variables['elements']) as $key) {
1427
    $variables['content'][$key] = $variables['elements'][$key];
1428
  }
1429

    
1430
  // Make the field variables available with the appropriate language.
1431
  field_attach_preprocess('file', $file, $variables['content'], $variables);
1432

    
1433
  // Attach the file object to the content element.
1434
  $variables['content']['file']['#file'] = $file;
1435

    
1436
  // Display post information only on certain file types.
1437
  if (variable_get('file_submitted_' . $file->type, FALSE)) {
1438
    $variables['display_submitted'] = TRUE;
1439
    $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1440
    $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
1441
  }
1442
  else {
1443
    $variables['display_submitted'] = FALSE;
1444
    $variables['submitted'] = '';
1445
    $variables['user_picture'] = '';
1446
  }
1447

    
1448
  // Gather file classes.
1449
  $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
1450
  $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
1451
  if ($file->status != FILE_STATUS_PERMANENT) {
1452
    $variables['classes_array'][] = 'file-temporary';
1453
  }
1454

    
1455
  // Change the 'file-entity' class into 'file'
1456
  if ($variables['classes_array'][0] == 'file-entity') {
1457
    $variables['classes_array'][0] = 'file';
1458
  }
1459

    
1460
  // Clean up name so there are no underscores.
1461
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
1462
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
1463
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
1464
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
1465
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
1466
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
1467
}
1468

    
1469
/**
1470
 * Returns the file type name of the passed file or file type string.
1471
 *
1472
 * @param $file
1473
 *   A file object or string that indicates the file type to return.
1474
 *
1475
 * @return
1476
 *   The file type name or FALSE if the file type is not found.
1477
 */
1478
function file_entity_type_get_name($file) {
1479
  $type = is_object($file) ? $file->type : $file;
1480
  $info = entity_get_info('file');
1481
  return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
1482
}
1483

    
1484
/**
1485
 * Returns a list of available file type names.
1486
 *
1487
 * @return
1488
 *   An array of file type names, keyed by the type.
1489
 */
1490
function file_entity_type_get_names() {
1491
  $names = &drupal_static(__FUNCTION__);
1492

    
1493
  if (!isset($names)) {
1494
    $info = entity_get_info('file');
1495
    foreach ($info['bundles'] as $bundle => $bundle_info) {
1496
      $names[$bundle] = $bundle_info['label'];
1497
    }
1498
  }
1499

    
1500
  return $names;
1501
}
1502

    
1503
/**
1504
 * Return an array of available view modes for file entities.
1505
 */
1506
function file_entity_view_mode_labels() {
1507
  $labels = &drupal_static(__FUNCTION__);
1508

    
1509
  if (!isset($options)) {
1510
    $entity_info = entity_get_info('file');
1511
    $labels = array('default' => t('Default'));
1512
    foreach ($entity_info['view modes'] as $machine_name => $mode) {
1513
      $labels[$machine_name] = $mode['label'];
1514
    }
1515
  }
1516

    
1517
  return $labels;
1518
}
1519

    
1520
/**
1521
 * Return the label for a specific file entity view mode.
1522
 */
1523
function file_entity_view_mode_label($view_mode, $default = FALSE) {
1524
  $labels = file_entity_view_mode_labels();
1525
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
1526
}
1527

    
1528
/**
1529
 * Helper function to get a list of hidden stream wrappers.
1530
 *
1531
 * This is used in several places to filter queries for media so that files in
1532
 * temporary:// don't show up.
1533
 */
1534
function file_entity_get_hidden_stream_wrappers() {
1535
  return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
1536
}
1537

    
1538
/**
1539
 * Return a specific stream wrapper's registry information.
1540
 *
1541
 * @param $scheme
1542
 *   A URI scheme, a stream is referenced as "scheme://target".
1543
 *
1544
 * @see file_get_stream_wrappers()
1545
 */
1546
function file_entity_get_stream_wrapper($scheme) {
1547
  $wrappers = file_get_stream_wrappers();
1548
  return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
1549
}
1550

    
1551
/**
1552
 * Implements hook_stream_wrappers_alter().
1553
 */
1554
function file_entity_stream_wrappers_alter(&$wrappers) {
1555
  if (isset($wrappers['private'])) {
1556
    $wrappers['private']['private'] = TRUE;
1557
  }
1558
  if (isset($wrappers['temporary'])) {
1559
    $wrappers['temporary']['private'] = TRUE;
1560
  }
1561
}
1562

    
1563
/**
1564
 * Implements hook_ctools_plugin_api().
1565
 */
1566
function file_entity_ctools_plugin_api($owner, $api) {
1567
  if ($owner == 'file_entity' && $api == 'file_type') {
1568
    return array('version' => 1);
1569
  }
1570
  if ($owner == 'file_entity' && $api == 'file_default_displays') {
1571
    return array('version' => 1);
1572
  }
1573
}
1574

    
1575
/**
1576
 * @defgroup file_entity_access File access rights
1577
 * @{
1578
 * The file access system determines who can do what to which files.
1579
 *
1580
 * In determining access rights for a file, file_entity_access() first checks
1581
 * whether the user has the "bypass file access" permission. Such users have
1582
 * unrestricted access to all files. user 1 will always pass this check.
1583
 *
1584
 * Next, all implementations of hook_file_entity_access() will be called. Each
1585
 * implementation may explicitly allow, explicitly deny, or ignore the access
1586
 * request. If at least one module says to deny the request, it will be rejected.
1587
 * If no modules deny the request and at least one says to allow it, the request
1588
 * will be permitted.
1589
 *
1590
 * There is no access grant system for files.
1591
 *
1592
 * In file listings, the process above is followed except that
1593
 * hook_file_entity_access() is not called on each file for performance reasons
1594
 * and for proper functioning of the pager system. When adding a filelisting to
1595
 * your module, be sure to use a dynamic query created by db_select()
1596
 * and add a tag of "file_entity_access". This will allow modules dealing
1597
 * with file access to ensure only files to which the user has access
1598
 * are retrieved, through the use of hook_query_TAG_alter().
1599
 *
1600
 * Note: Even a single module returning FILE_ENTITY_ACCESS_DENY from
1601
 * hook_file_entity_access() will block access to the file. Therefore,
1602
 * implementers should take care to not deny access unless they really intend to.
1603
 * Unless a module wishes to actively deny access it should return
1604
 * FILE_ENTITY_ACCESS_IGNORE (or simply return nothing)
1605
 * to allow other modules to control access.
1606
 *
1607
 * Stream wrappers that are considered private should implement a 'private'
1608
 * flag equal to TRUE in hook_stream_wrappers().
1609
 */
1610

    
1611
/**
1612
 * Determine if a user may perform the given operation on the specified file.
1613
 *
1614
 * @param $op
1615
 *   The operation to be performed on the file. Possible values are:
1616
 *   - "view"
1617
 *   - "download"
1618
 *   - "update"
1619
 *   - "delete"
1620
 *   - "create"
1621
 * @param $file
1622
 *   The file object on which the operation is to be performed, or file type
1623
 *   (e.g. 'image') for "create" operation.
1624
 * @param $account
1625
 *   Optional, a user object representing the user for whom the operation is to
1626
 *   be performed. Determines access for a user other than the current user.
1627
 *
1628
 * @return
1629
 *   TRUE if the operation may be performed, FALSE otherwise.
1630
 */
1631
function file_entity_access($op, $file = NULL, $account = NULL) {
1632
  $rights = &drupal_static(__FUNCTION__, array());
1633

    
1634
  if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
1635
    // If there was no file to check against, and the $op was not one of the
1636
    // supported ones, we return access denied.
1637
    return FALSE;
1638
  }
1639

    
1640
  // If no user object is supplied, the access check is for the current user.
1641
  if (empty($account)) {
1642
    $account = $GLOBALS['user'];
1643
  }
1644

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

    
1649
  // If we've already checked access for this file, user and op, return from
1650
  // cache.
1651
  if (isset($rights[$account->uid][$cache_id][$op])) {
1652
    return $rights[$account->uid][$cache_id][$op];
1653
  }
1654

    
1655
  if (user_access('bypass file access', $account)) {
1656
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1657
  }
1658

    
1659
  // We grant access to the file if both of the following conditions are met:
1660
  // - No modules say to deny access.
1661
  // - At least one module says to grant access.
1662
  $access = module_invoke_all('file_entity_access', $op, $file, $account);
1663
  if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
1664
    return $rights[$account->uid][$cache_id][$op] = FALSE;
1665
  }
1666
  elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
1667
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1668
  }
1669

    
1670

    
1671
  // Fall back to default behaviors on view.
1672
  if ($op == 'view' && is_object($file)) {
1673
    $scheme = file_uri_scheme($file->uri);
1674
    $wrapper = file_entity_get_stream_wrapper($scheme);
1675

    
1676
    if (!empty($wrapper['private'])) {
1677
      // For private files, users can view private files if the
1678
      // user has the 'view private files' permission.
1679
      if (user_access('view private files', $account)) {
1680
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1681
      }
1682

    
1683
      // For private files, users can view their own private files if the
1684
      // user is not anonymous, and has the 'view own private files' permission.
1685
      if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
1686
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1687
      }
1688
    }
1689
    elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
1690
      // For non-private files, allow to see if user owns the file.
1691
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1692
    }
1693
    elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
1694
      // For non-private files, users can view if they have the 'view files'
1695
      // permission.
1696
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1697
    }
1698
  }
1699

    
1700
  return FALSE;
1701
}
1702

    
1703
/**
1704
 * Implements hook_file_entity_access().
1705
 */
1706
function file_entity_file_entity_access($op, $file, $account) {
1707
  // If the file URI is invalid, deny access.
1708
  if (is_object($file) && !file_valid_uri($file->uri)) {
1709
    return FILE_ENTITY_ACCESS_DENY;
1710
  }
1711

    
1712
  if ($op == 'create') {
1713
    if (user_access('create files')) {
1714
      return FILE_ENTITY_ACCESS_ALLOW;
1715
    }
1716
  }
1717

    
1718
  if (!empty($file)) {
1719
    $type = is_string($file) ? $file : $file->type;
1720

    
1721
    if (in_array($type, file_entity_permissions_get_configured_types())) {
1722
      if ($op == 'download') {
1723
        if (user_access('download any ' . $type . ' files', $account) || is_object($file) && user_access('download own ' . $type . ' files', $account) && ($account->uid == $file->uid)) {
1724
          return FILE_ENTITY_ACCESS_ALLOW;
1725
        }
1726
      }
1727

    
1728
      if ($op == 'update') {
1729
        if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1730
          return FILE_ENTITY_ACCESS_ALLOW;
1731
        }
1732
      }
1733

    
1734
      if ($op == 'delete') {
1735
        if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1736
          return FILE_ENTITY_ACCESS_ALLOW;
1737
        }
1738
      }
1739
    }
1740
  }
1741

    
1742
  return FILE_ENTITY_ACCESS_IGNORE;
1743
}
1744

    
1745
/**
1746
 * Implements hook_query_TAG_alter().
1747
 *
1748
 * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
1749
 * file access checks for the user account given by the 'account' meta-data (or
1750
 * global $user if not provided).
1751
 */
1752
function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
1753
  _file_entity_query_file_entity_access_alter($query, 'file');
1754
}
1755

    
1756
/**
1757
 * Implements hook_query_TAG_alter().
1758
 *
1759
 * This function implements the same functionality as
1760
 * file_entity_query_file_access_alter() for the SQL field storage engine. File
1761
 * access conditions are added for field values belonging to files only.
1762
 */
1763
function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
1764
  //_file_entity_query_file_entity_access_alter($query, 'entity');
1765
}
1766

    
1767
/**
1768
 * Helper for file entity access functions.
1769
 *
1770
 * @param $query
1771
 *   The query to add conditions to.
1772
 * @param $type
1773
 *   Either 'file' or 'entity' depending on what sort of query it is. See
1774
 *   file_entity_query_file_entity_access_alter() and
1775
 *   file_entity_query_entity_field_access_alter() for more.
1776
 */
1777
function _file_entity_query_file_entity_access_alter($query, $type) {
1778
  global $user;
1779

    
1780
  // Read meta-data from query, if provided.
1781
  if (!$account = $query->getMetaData('account')) {
1782
    $account = $user;
1783
  }
1784

    
1785
  // If $account can bypass file access, we don't need to alter the query.
1786
  if (user_access('bypass file access', $account)) {
1787
    return;
1788
  }
1789

    
1790
  $tables = $query->getTables();
1791
  $base_table = $query->getMetaData('base_table');
1792
  // If no base table is specified explicitly, search for one.
1793
  if (!$base_table) {
1794
    $fallback = '';
1795
    foreach ($tables as $alias => $table_info) {
1796
      if (!($table_info instanceof SelectQueryInterface)) {
1797
        $table = $table_info['table'];
1798
        // If the file_managed table is in the query, it wins immediately.
1799
        if ($table == 'file_managed') {
1800
          $base_table = $table;
1801
          break;
1802
        }
1803
        // Check whether the table has a foreign key to file_managed.fid. If it
1804
        // does, do not run this check again as we found a base table and only
1805
        // file_managed can triumph that.
1806
        if (!$base_table) {
1807
          // The schema is cached.
1808
          $schema = drupal_get_schema($table);
1809
          if (isset($schema['fields']['fid'])) {
1810
            if (isset($schema['foreign keys'])) {
1811
              foreach ($schema['foreign keys'] as $relation) {
1812
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
1813
                  $base_table = $table;
1814
                }
1815
              }
1816
            }
1817
            else {
1818
              // At least it's a fid. A table with a field called fid is very
1819
              // very likely to be a file_managed.fid in a file access query.
1820
              $fallback = $table;
1821
            }
1822
          }
1823
        }
1824
      }
1825
    }
1826
    // If there is nothing else, use the fallback.
1827
    if (!$base_table) {
1828
      if ($fallback) {
1829
        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);
1830
        $base_table = $fallback;
1831
      }
1832
      else {
1833
        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.'));
1834
      }
1835
    }
1836
  }
1837

    
1838
  if ($type == 'entity') {
1839
    // The original query looked something like:
1840
    // @code
1841
    //  SELECT fid FROM sometable s
1842
    //  WHERE ($file_access_conditions)
1843
    // @endcode
1844
    //
1845
    // Our query will look like:
1846
    // @code
1847
    //  SELECT entity_type, entity_id
1848
    //  FROM field_data_something s
1849
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1850
    // @endcode
1851
    //
1852
    // So instead of directly adding to the query object, we need to collect
1853
    // all of the file access conditions in a separate db_and() object and
1854
    // then add it to the query at the end.
1855
    $file_conditions = db_and();
1856
  }
1857
  foreach ($tables as $falias => $tableinfo) {
1858
    $table = $tableinfo['table'];
1859
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
1860
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
1861
      $subquery_conditions = db_or();
1862

    
1863
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
1864
      if (!empty($wrappers['public'])) {
1865
        if (user_access('view files', $account)) {
1866
          foreach (array_keys($wrappers['public']) as $wrapper) {
1867
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1868
          }
1869
        }
1870
        elseif (user_access('view own files', $account)) {
1871
          foreach (array_keys($wrappers['public']) as $wrapper) {
1872
            $subquery_conditions->condition(db_and()
1873
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1874
              ->condition('fm_access.uid', $account->uid)
1875
            );
1876
          }
1877
        }
1878
      }
1879
      if (!empty($wrappers['private'])) {
1880
        if (user_access('view private files', $account)) {
1881
          foreach (array_keys($wrappers['private']) as $wrapper) {
1882
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1883
          }
1884
        }
1885
        elseif (user_access('view own private files', $account)) {
1886
          foreach (array_keys($wrappers['private']) as $wrapper) {
1887
            $subquery_conditions->condition(db_and()
1888
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1889
              ->condition('fm_access.uid', $account->uid)
1890
            );
1891
          }
1892
        }
1893
      }
1894

    
1895
      if ($subquery_conditions->count()) {
1896
        $subquery->condition($subquery_conditions);
1897

    
1898
        $field = 'fid';
1899
        // Now handle entities.
1900
        if ($type == 'entity') {
1901
          // Set a common alias for entities.
1902
          $base_alias = $falias;
1903
          $field = 'entity_id';
1904
        }
1905
        $subquery->where("$falias.$field = fm_access.fid");
1906

    
1907
        // For an entity query, attach the subquery to entity conditions.
1908
        if ($type == 'entity') {
1909
          $file_conditions->exists($subquery);
1910
        }
1911
        // Otherwise attach it to the node query itself.
1912
        elseif ($table == 'file_managed') {
1913
          // Fix for https://drupal.org/node/2073085
1914
          $db_or = db_or();
1915
          $db_or->exists($subquery);
1916
          $db_or->isNull($falias . '.' . $field);
1917
          $query->condition($db_or);
1918
        }
1919
        else {
1920
          $query->exists($subquery);
1921
        }
1922
      }
1923
    }
1924
  }
1925

    
1926
  if ($type == 'entity' && $file_conditions->count()) {
1927
    // All the file access conditions are only for field values belonging to
1928
    // files.
1929
    $file_conditions->condition("$base_alias.entity_type", 'file');
1930
    $or = db_or();
1931
    $or->condition($file_conditions);
1932
    // If the field value belongs to a non-file entity type then this function
1933
    // does not do anything with it.
1934
    $or->condition("$base_alias.entity_type", 'file', '<>');
1935
    // Add the compiled set of rules to the query.
1936
    $query->condition($or);
1937
  }
1938
}
1939

    
1940
/**
1941
 * Implements hook_file_download().
1942
 */
1943
function file_entity_file_download($uri) {
1944
  // Load the file from the URI.
1945
  $file = file_uri_to_object($uri);
1946

    
1947
  // An existing file wasn't found, so we don't control access.
1948
  // E.g. image derivatives will fall here.
1949
  if (empty($file->fid)) {
1950
    return NULL;
1951
  }
1952

    
1953
  // Allow the user to download the file if they have appropriate permissions.
1954
  if (file_entity_access('view', $file)) {
1955
    return file_get_content_headers($file);
1956
  }
1957

    
1958
  return -1;
1959
}
1960

    
1961
/**
1962
 * Helper function to generate standard file permission list for a given type.
1963
 *
1964
 * @param $type
1965
 *   The machine-readable name of the file type.
1966
 * @return array
1967
 *   An array of permission names and descriptions.
1968
 */
1969
function file_entity_list_permissions($type) {
1970
  $info = file_type_load($type);
1971

    
1972
  // Build standard list of file permissions for this type.
1973
  $permissions = array(
1974
    "edit own $type files" => array(
1975
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
1976
    ),
1977
    "edit any $type files" => array(
1978
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
1979
    ),
1980
    "delete own $type files" => array(
1981
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
1982
    ),
1983
    "delete any $type files" => array(
1984
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
1985
    ),
1986
    "download own $type files" => array(
1987
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
1988
    ),
1989
    "download any $type files" => array(
1990
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
1991
    ),
1992
  );
1993

    
1994
  return $permissions;
1995
}
1996

    
1997
/**
1998
 * Returns an array of file types that should be managed by permissions.
1999
 *
2000
 * By default, this will include all file types in the system. To exclude a
2001
 * specific file from getting permissions defined for it, set the
2002
 * file_entity_permissions_$type variable to 0. File entity does not provide an
2003
 * interface for doing so, however, contrib modules may exclude their own files
2004
 * in hook_install(). Alternatively, contrib modules may configure all file
2005
 * types at once, or decide to apply some other hook_file_entity_access()
2006
 * implementation to some or all file types.
2007
 *
2008
 * @return
2009
 *   An array of file types managed by this module.
2010
 */
2011
function file_entity_permissions_get_configured_types() {
2012

    
2013
  $configured_types = array();
2014

    
2015
  foreach (file_type_get_enabled_types() as $type => $info) {
2016
    if (variable_get('file_entity_permissions_' . $type, 1)) {
2017
      $configured_types[] = $type;
2018
    }
2019
  }
2020

    
2021
  return $configured_types;
2022
}
2023

    
2024
/**
2025
 * @} End of "defgroup file_entity_access".
2026
 *
2027
 * Implements hook_file_default_types().
2028
 */
2029
function file_entity_file_default_types() {
2030
  $types = array();
2031

    
2032
  // Image.
2033
  $types['image'] = (object) array(
2034
    'api_version' => 1,
2035
    'type' => 'image',
2036
    'label' => t('Image'),
2037
    'description' => t('An <em>Image</em> file is a still visual.'),
2038
    'mimetypes' => array(
2039
      'image/*',
2040
    ),
2041
  );
2042

    
2043
  // Video.
2044
  $types['video'] = (object) array(
2045
    'api_version' => 1,
2046
    'type' => 'video',
2047
    'label' => t('Video'),
2048
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2049
    'mimetypes' => array(
2050
      'video/*',
2051
    ),
2052
  );
2053

    
2054
  // Audio.
2055
  $types['audio'] = (object) array(
2056
    'api_version' => 1,
2057
    'type' => 'audio',
2058
    'label' => t('Audio'),
2059
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2060
    'mimetypes' => array(
2061
      'audio/*',
2062
    ),
2063
  );
2064

    
2065
  // Document.
2066
  $types['document'] = (object) array(
2067
    'api_version' => 1,
2068
    'type' => 'document',
2069
    'label' => t('Document'),
2070
    'description' => t('A <em>Document</em> file is written information.'),
2071
    'mimetypes' => array(
2072
      'text/plain',
2073
      'application/msword',
2074
      'application/vnd.ms-excel',
2075
      'application/pdf',
2076
      'application/vnd.ms-powerpoint',
2077
      'application/vnd.oasis.opendocument.text',
2078
      'application/vnd.oasis.opendocument.spreadsheet',
2079
      'application/vnd.oasis.opendocument.presentation',
2080
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2081
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2082
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2083
    ),
2084
  );
2085

    
2086
  return $types;
2087
}
2088

    
2089
/**
2090
 * Implements hook_file_operations().
2091
 */
2092
function file_entity_file_operations() {
2093
  $operations = array(
2094
    'permanent' => array(
2095
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2096
      'callback' => 'file_entity_mass_update',
2097
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2098
    ),
2099
    'temporary' => array(
2100
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2101
      'callback' => 'file_entity_mass_update',
2102
      'callback arguments' => array('updates' => array('status' => 0)),
2103
    ),
2104
    'delete' => array(
2105
      'label' => t('Delete selected files'),
2106
      'callback' => NULL,
2107
    ),
2108
  );
2109
  return $operations;
2110
}
2111

    
2112
/**
2113
 * Clear the field cache for any entities referencing a specific file.
2114
 *
2115
 * @param object $file
2116
 *   A file object.
2117
 */
2118
function file_entity_invalidate_field_caches($file) {
2119
  $entity_types = &drupal_static(__FUNCTION__);
2120

    
2121
  // Gather the list of entity types which support field caching.
2122
  if (!isset($entity_types)) {
2123
    $entity_types = array();
2124
    foreach (entity_get_info() as $entity_type => $entity_info) {
2125
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2126
        $entity_types[] = $entity_type;
2127
      }
2128
    }
2129
  }
2130

    
2131
  // If no entity types support field caching, then there is no work to be done.
2132
  if (empty($entity_types)) {
2133
    return;
2134
  }
2135

    
2136
  $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();
2137
  if (!empty($records)) {
2138
    $cids = array();
2139
    foreach ($records as $record) {
2140
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2141
    }
2142
    cache_clear_all($cids, 'cache_field');
2143
  }
2144
}
2145

    
2146
/**
2147
 * Check if a file entity is considered local or not.
2148
 *
2149
 * @param object $file
2150
 *   A file entity object from file_load().
2151
 *
2152
 * @return
2153
 *   TRUE if the file is using a local stream wrapper, or FALSE otherwise.
2154
 */
2155
function file_entity_file_is_local($file) {
2156
  $scheme = file_uri_scheme($file->uri);
2157
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
2158
  return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
2159
}
2160

    
2161
/**
2162
 * Check if a file entity is considered writeable or not.
2163
 *
2164
 * @param object $file
2165
 *   A file entity object from file_load().
2166
 *
2167
 * @return
2168
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2169
 *   or FALSE otherwise.
2170
 */
2171
function file_entity_file_is_writeable($file) {
2172
  $scheme = file_uri_scheme($file->uri);
2173
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2174
  return !empty($wrappers[$scheme]);
2175
}
2176

    
2177
/**
2178
 * Pre-render callback for adding validation descriptions to file upload fields.
2179
 */
2180
function file_entity_upload_validators_pre_render($element) {
2181
  if (!empty($element['#upload_validators'])) {
2182
    if (!isset($element['#description'])) {
2183
      $element['#description'] = '';
2184
    }
2185
    if ($element['#description'] !== FALSE) {
2186
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2187
    }
2188
  }
2189
  return $element;
2190
}
2191

    
2192
/**
2193
 * @name pathauto_file Pathauto integration for the core file module.
2194
 * @{
2195
 */
2196

    
2197
/**
2198
 * Implements hook_file_insert() on behalf of pathauto.module.
2199
 */
2200
function pathauto_file_insert($file) {
2201
  pathauto_file_update_alias($file, 'insert');
2202
}
2203

    
2204
/**
2205
 * Implements hook_file_update() on behalf of pathauto.module.
2206
 */
2207
function pathauto_file_update($file) {
2208
  pathauto_file_update_alias($file, 'update');
2209
}
2210

    
2211
/**
2212
 * Implements hook_file_delete() on behalf of pathauto.module.
2213
 */
2214
function pathauto_file_delete($file) {
2215
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2216
}
2217

    
2218
/**
2219
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2220
 *
2221
 * Add the Pathauto settings to the file form.
2222
 */
2223
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2224
  $file = $form_state['file'];
2225
  $langcode = pathauto_entity_language('file', $file);
2226
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2227
}
2228

    
2229
/**
2230
 * Implements hook_file_operations() on behalf of pathauto.module.
2231
 */
2232
function pathauto_file_operations() {
2233
  $operations['pathauto_update_alias'] = array(
2234
    'label' => t('Update URL alias'),
2235
    'callback' => 'pathauto_file_update_alias_multiple',
2236
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2237
  );
2238
  return $operations;
2239
}
2240

    
2241
/**
2242
 * Update the URL aliases for an individual file.
2243
 *
2244
 * @param $file
2245
 *   A file object.
2246
 * @param $op
2247
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2248
 * @param $options
2249
 *   An optional array of additional options.
2250
 */
2251
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2252
  // Skip processing if the user has disabled pathauto for the file.
2253
  if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
2254
    return;
2255
  }
2256

    
2257
  $options += array('language' => pathauto_entity_language('file', $file));
2258

    
2259
  // Skip processing if the file has no pattern.
2260
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2261
    return;
2262
  }
2263

    
2264
  module_load_include('inc', 'pathauto');
2265
  $uri = entity_uri('file', $file);
2266
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2267
}
2268

    
2269
/**
2270
 * Update the URL aliases for multiple files.
2271
 *
2272
 * @param $fids
2273
 *   An array of file IDs.
2274
 * @param $op
2275
 *   Operation being performed on the files ('insert', 'update' or
2276
 *   'bulkupdate').
2277
 * @param $options
2278
 *   An optional array of additional options.
2279
 */
2280
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2281
  $options += array('message' => FALSE);
2282

    
2283
  $files = file_load_multiple($fids);
2284
  foreach ($files as $file) {
2285
    pathauto_file_update_alias($file, $op, $options);
2286
  }
2287

    
2288
  if (!empty($options['message'])) {
2289
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2290
  }
2291
}
2292

    
2293
/**
2294
 * Update action wrapper for pathauto_file_update_alias().
2295
 */
2296
function pathauto_file_update_action($file, $context = array()) {
2297
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2298
}
2299

    
2300
/**
2301
 * @} End of "name pathauto_file".
2302
 */
2303

    
2304
/**
2305
 * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
2306
 */
2307
function path_form_file_entity_edit_alter(&$form, $form_state) {
2308
  // Make sure this does not show up on the delete confirmation form.
2309
  if (empty($form_state['confirm_delete'])) {
2310
    $file = $form_state['file'];
2311
    $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
2312
    $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2313
    $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
2314
    $path = (isset($file->fid) ? path_load($conditions) : array());
2315
    if ($path === FALSE) {
2316
      $path = array();
2317
    }
2318
    $path += array(
2319
      'pid' => NULL,
2320
      'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
2321
      'alias' => '',
2322
      'language' => $langcode,
2323
    );
2324
    $form['path'] = array(
2325
      '#type' => 'fieldset',
2326
      '#title' => t('URL path settings'),
2327
      '#collapsible' => TRUE,
2328
      '#collapsed' => empty($path['alias']),
2329
      '#group' => 'additional_settings',
2330
      '#attributes' => array(
2331
        'class' => array('path-form'),
2332
      ),
2333
      '#attached' => array(
2334
        'js' => array(drupal_get_path('module', 'path') . '/path.js'),
2335
      ),
2336
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
2337
      '#weight' => 30,
2338
      '#tree' => TRUE,
2339
      '#element_validate' => array('path_form_element_validate'),
2340
    );
2341
    $form['path']['alias'] = array(
2342
      '#type' => 'textfield',
2343
      '#title' => t('URL alias'),
2344
      '#default_value' => $path['alias'],
2345
      '#maxlength' => 255,
2346
      '#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.'),
2347
    );
2348
    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
2349
    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
2350
    $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
2351
  }
2352
}
2353

    
2354
/**
2355
 * Implements hook_file_insert() on behalf of path.module.
2356
 */
2357
function path_file_insert($file) {
2358
  if (isset($file->path)) {
2359
    $path = $file->path;
2360
    $path['alias'] = trim($path['alias']);
2361
    // Only save a non-empty alias.
2362
    if (!empty($path['alias'])) {
2363
      // Ensure fields for programmatic executions.
2364
      $path['source'] = 'file/' . $file->fid;
2365
      // Core does not provide a way to store the file language but contrib
2366
      // modules can do it so we need to take this into account.
2367
      $langcode = entity_language('file', $file);
2368
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2369
      path_save($path);
2370
    }
2371
  }
2372
}
2373

    
2374
/**
2375
 * Implements hook_file_update() on behalf of path.module.
2376
 */
2377
function path_file_update($file) {
2378
  if (isset($file->path)) {
2379
    $path = $file->path;
2380
    $path['alias'] = trim($path['alias']);
2381
    // Delete old alias if user erased it.
2382
    if (!empty($path['fid']) && empty($path['alias'])) {
2383
      path_delete($path['fid']);
2384
    }
2385
    // Only save a non-empty alias.
2386
    if (!empty($path['alias'])) {
2387
      // Ensure fields for programmatic executions.
2388
      $path['source'] = 'file/' . $file->fid;
2389
      // Core does not provide a way to store the file language but contrib
2390
      // modules can do it so we need to take this into account.
2391
      $langcode = entity_language('file', $file);
2392
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2393
      path_save($path);
2394
    }
2395
  }
2396
}
2397

    
2398
/**
2399
 * Implements hook_file_delete() on behalf of path.module.
2400
 */
2401
function path_file_delete($file) {
2402
  // Delete all aliases associated with this file.
2403
  path_delete(array('source' => 'file/' . $file->fid));
2404
}
2405

    
2406
/**
2407
 * Checks if pattern(s) match mimetype(s).
2408
 */
2409
function file_entity_match_mimetypes($needle, $haystack) {
2410
  $needle = is_array($needle) ? $needle : array($needle);
2411
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2412

    
2413
  foreach ($haystack as $mimetype) {
2414
    foreach ($needle as $search) {
2415
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2416
        return TRUE;
2417
      }
2418
    }
2419
  }
2420

    
2421
  return FALSE;
2422
}
2423

    
2424
/**
2425
 * A wrapper function for the PHP function fnmatch().
2426
 *
2427
 * We include this, because Windows servers do not implement fnmatch() until
2428
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2429
 */
2430
function file_entity_fnmatch($pattern, $string) {
2431
  if (!function_exists('fnmatch')) {
2432
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2433
  }
2434
  return fnmatch($pattern, $string);
2435
}
2436

    
2437
/**
2438
 * Return an URI for a file download.
2439
 */
2440
function file_entity_download_uri($file) {
2441
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2442
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2443
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2444
  }
2445
  return $uri;
2446
}
2447

    
2448
function file_entity_file_get_mimetype_type($file) {
2449
  list($type, $subtype) = explode('/', $file->filemime, 2);
2450
  return $type;
2451
}
2452

    
2453
/**
2454
 * Implements hook_admin_menu_map().
2455
 */
2456
function file_entity_admin_menu_map() {
2457
  if (!user_access('administer file types')) {
2458
    return;
2459
  }
2460
  $map['admin/structure/file-types/manage/%file_type'] = array(
2461
    'parent' => 'admin/structure/file-types',
2462
    'arguments' => array(
2463
      array('%file_type' => array_keys(file_entity_type_get_names())),
2464
    ),
2465
  );
2466
  return $map;
2467
}
2468

    
2469
/*
2470
 * Generates a token to protect a file download URL.
2471
 *
2472
 * This prevents unauthorized crawling of all file download URLs since the
2473
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
2474
 * to guess or attempt many at once. This can be costly both in CPU time
2475
 * and bandwidth.
2476
 *
2477
 * @see image_style_path_token()
2478
 *
2479
 * @param object $file
2480
 *   A file entity object.
2481
 *
2482
 * @return string
2483
 *   An eight-character token which can be used to protect file downloads
2484
 *   against denial-of-service attacks.
2485
 */
2486
function file_entity_get_download_token($file) {
2487
  // Return the first eight characters.
2488
  return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
2489
}
2490

    
2491
/**
2492
 * Find all fields that are of a certain field type.
2493
 *
2494
 * @param string $field_type
2495
 *   A field type.
2496
 *
2497
 * @return array
2498
 *   An array of field names that match the type $field_type.
2499
 */
2500
function _file_entity_get_fields_by_type($field_type) {
2501
  $return = array();
2502
  if (function_exists('field_info_field_map')) {
2503
    foreach (field_info_field_map() as $field_name => $field) {
2504
      if ($field['type'] == $field_type) {
2505
        $return[$field_name] = $field_name;
2506
      }
2507
    }
2508
  }
2509
  else {
2510
    foreach (field_info_fields() as $field_name => $field) {
2511
      if ($field['type'] == $field_type) {
2512
        $return[$field_name] = $field_name;
2513
      }
2514
    }
2515
  }
2516
  return $return;
2517
}
2518

    
2519
/**
2520
 * Implements hook_field_attach_load().
2521
 */
2522
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2523
  // Loop over all the entities looking for entities with attached images.
2524
  foreach ($entities as $entity) {
2525
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2526
    // Examine every image field instance attached to this entity's bundle.
2527
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2528
    foreach ($instances as $field_name => $instance) {
2529
      if (!empty($entity->{$field_name})) {
2530
        foreach ($entity->{$field_name} as $langcode => $items) {
2531
          foreach ($items as $delta => $item) {
2532
            // If alt and title text is not specified, fall back to alt and
2533
            // title text on the file.
2534
            if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
2535
              $file = file_load($item['fid']);
2536
              foreach (array('alt', 'title') as $key) {
2537
                if (empty($item[$key]) && !empty($file->{$key})) {
2538
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2539
                }
2540
              }
2541
            }
2542
          }
2543
        }
2544
      }
2545
    }
2546
  }
2547
}
2548

    
2549
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2550
  $wrappers = array();
2551
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2552
    if (empty($wrapper['private'])) {
2553
      $wrappers['public'][$key] = $wrapper['name'];
2554
    }
2555
    else {
2556
      $wrappers['private'][$key] = $wrapper['name'];
2557
    }
2558
  }
2559
  return $wrappers;
2560
}