Project

General

Profile

Paste
Download (85.4 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / file_entity / file_entity.module @ 1f142f4f

1
<?php
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
391
  return $items;
392
}
393

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

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

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

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

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

    
460
  return $permissions;
461
}
462

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

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

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

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

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

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

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

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

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

    
571
/**
572
 * Implements hook_search_execute().
573
 */
574
function file_entity_search_execute($keys = NULL, $conditions = NULL) {
575
  global $user;
576

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

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

    
592
  // Add the ranking expressions.
593
  _file_entity_rankings($query);
594

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

    
608
    $extra = module_invoke_all('file_entity_search_result', $file);
609

    
610
    $types = file_entity_type_get_names();
611

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

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

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

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

    
666
  $output = drupal_render($form['info']);
667

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

    
678
  $output .= drupal_render_children($form);
679
  return $output;
680
}
681

    
682
/**
683
 * Implements hook_update_index().
684
 */
685
function file_entity_update_index() {
686
  $limit = (int)variable_get('search_cron_limit', 100);
687

    
688
  $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'));
689

    
690
  foreach ($result as $file) {
691
    _file_entity_index_file($file);
692
  }
693
}
694

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

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

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

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

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

    
721
  // Update index
722
  search_index($file->fid, 'file', $text);
723
}
724

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

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

    
778
    $form['#validate'][] = 'file_entity_search_validate';
779
  }
780
}
781

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

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

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

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

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

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

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

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

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

    
957
  // Enable Metatag support.
958
  $entity_info['file']['metatags'] = TRUE;
959

    
960
  // Ensure some of the Entity API callbacks are supported.
961
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
962
  $entity_info['file']['view callback'] = 'file_entity_metadata_view_file';
963
  $entity_info['file']['form callback'] = 'file_entity_metadata_form_file';
964
  $entity_info['file']['access callback'] = 'file_entity_access';
965

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

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

    
1010
  return $info;
1011
}
1012

    
1013
/**
1014
 * Implements hook_field_display_ENTITY_TYPE_alter().
1015
 */
1016
function file_entity_field_display_file_alter(&$display, $context) {
1017
  // Hide field labels in search index.
1018
  if ($context['view_mode'] == 'search_index') {
1019
    $display['label'] = 'hidden';
1020
  }
1021
}
1022

    
1023
/**
1024
 * URI callback for file entities.
1025
 */
1026
function file_entity_uri($file) {
1027
  $uri['path'] = 'file/' . $file->fid;
1028
  return $uri;
1029
}
1030

    
1031
/**
1032
 * Entity API callback to view files.
1033
 */
1034
function file_entity_metadata_view_file($entities, $view_mode = 'full', $langcode = NULL) {
1035
  $result = file_view_multiple($entities, $view_mode, 0, $langcode);
1036
  // Make sure to key the result with 'file' instead of 'files'.
1037
  return array('file' => reset($result));
1038
}
1039

    
1040
/**
1041
 * Entity API callback to get the form of a file entity.
1042
 */
1043
function file_entity_metadata_form_file($file) {
1044
  // Pre-populate the form-state with the right form include.
1045
  $form_state['build_info']['args'] = array($file);
1046
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
1047
  return drupal_build_form('file_entity_edit', $form_state);
1048
}
1049

    
1050
/**
1051
 * Implements hook_ctools_plugin_directory().
1052
 */
1053
function file_entity_ctools_plugin_directory($module, $plugin) {
1054
  if (in_array($module, array('panelizer', 'ctools', 'page_manager'))) {
1055
    return 'plugins/' . $plugin;
1056
  }
1057
}
1058

    
1059
/**
1060
 * Implements hook_field_extra_fields().
1061
 *
1062
 * Adds 'file' as an extra field, so that its display and form component can be
1063
 * weighted relative to the fields that are added to file entity bundles.
1064
 */
1065
function file_entity_field_extra_fields() {
1066
  $info = array();
1067

    
1068
  if ($file_type_names = file_entity_type_get_names()) {
1069
    foreach ($file_type_names as $type => $name) {
1070
      $info['file'][$type]['form']['filename'] = array(
1071
        'label' => t('File name'),
1072
        'description' => t('File name'),
1073
        'weight' => -10,
1074
      );
1075
      $info['file'][$type]['form']['preview'] = array(
1076
        'label' => t('File'),
1077
        'description' => t('File preview'),
1078
        'weight' => -5,
1079
      );
1080
      $info['file'][$type]['display']['file'] = array(
1081
        'label' => t('File'),
1082
        'description' => t('File display'),
1083
        'weight' => 0,
1084
      );
1085
    }
1086
  }
1087

    
1088
  return $info;
1089
}
1090

    
1091
/**
1092
 * Implements hook_file_formatter_info().
1093
 */
1094
function file_entity_file_formatter_info() {
1095
  $formatters = array();
1096

    
1097
  // Allow file field formatters to be reused for displaying the file entity's
1098
  // file pseudo-field.
1099
  foreach (field_info_formatter_types() as $key => $formatter) {
1100
    if (array_intersect($formatter['field types'], array('file', 'image'))) {
1101
      $key = 'file_field_' . $key;
1102
      $formatters[$key] = array(
1103
        'label' => $formatter['label'],
1104
        'description' => !empty($formatter['description']) ? $formatter['description'] : '',
1105
        'view callback' => 'file_entity_file_formatter_file_field_view',
1106
      );
1107
      if (!empty($formatter['settings'])) {
1108
        $formatters[$key] += array(
1109
          'default settings' => $formatter['settings'],
1110
          'settings callback' => 'file_entity_file_formatter_file_field_settings',
1111
        );
1112
      }
1113
      if (!empty($formatter['file formatter'])) {
1114
        $formatters[$key] += $formatter['file formatter'];
1115
      }
1116
    }
1117
  }
1118

    
1119
  // Add a simple file formatter for displaying an image in a chosen style.
1120
  if (module_exists('image')) {
1121
    $formatters['file_image'] = array(
1122
      'label' => t('Image'),
1123
      'default settings' => array(
1124
        'image_style' => '',
1125
        'alt' => '[file:field_file_image_alt_text]',
1126
        'title' => '[file:field_file_image_title_text]'
1127
      ),
1128
      'view callback' => 'file_entity_file_formatter_file_image_view',
1129
      'settings callback' => 'file_entity_file_formatter_file_image_settings',
1130
      'hidden' => TRUE,
1131
      'mime types' => array('image/*'),
1132
    );
1133
  }
1134

    
1135
  return $formatters;
1136
}
1137

    
1138
/**
1139
 * Implements hook_file_formatter_FORMATTER_view().
1140
 *
1141
 * This function provides a bridge to the field formatter API, so that file
1142
 * field formatters can be reused for displaying the file entity's file
1143
 * pseudo-field.
1144
 */
1145
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
1146
  if (strpos($display['type'], 'file_field_') === 0) {
1147
    $field_formatter_type = substr($display['type'], strlen('file_field_'));
1148
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1149
    if (isset($field_formatter_info['module'])) {
1150
      // Set $display['type'] to what hook_field_formatter_*() expects.
1151
      $display['type'] = $field_formatter_type;
1152

    
1153
      // Allow any attribute overrides (e.g. from the Media module) to be
1154
      // respected.
1155
      $item = (array) $file;
1156
      if (!empty($file->override['attributes'])) {
1157
        $item = array_merge($item, $file->override['attributes']);
1158
      }
1159

    
1160
      // Set $items to what file field formatters expect. See file_field_load(),
1161
      // and note that, here, $file is already a fully loaded entity.
1162
      $items = array($item);
1163

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

    
1191
/**
1192
 * Implements hook_file_formatter_FORMATTER_settings().
1193
 *
1194
 * This function provides a bridge to the field formatter API, so that file
1195
 * field formatters can be reused for displaying the file entity's file
1196
 * pseudo-field.
1197
 */
1198
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
1199
  if (strpos($formatter_type, 'file_field_') === 0) {
1200
    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
1201
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1202

    
1203
    // Invoke hook_field_formatter_settings_form(). We are reusing field
1204
    // formatter functions, but we are not working with a Field API field, so
1205
    // set $field accordingly. Unfortunately, the API is for $settings to be
1206
    // transfered via the $instance parameter, so we must mock it.
1207
    if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
1208
      $field = NULL;
1209
      $mock_instance = array(
1210
        'display' => array(
1211
          $view_mode => array(
1212
            'type' => $field_formatter_type,
1213
            'settings' => $settings,
1214
          ),
1215
        ),
1216
        'entity_type' => 'file',
1217
        'bundle' => $file_type,
1218
      );
1219
      return $function($field, $mock_instance, $view_mode, $form, $form_state);
1220
    }
1221
  }
1222
}
1223

    
1224
/**
1225
 * Implements hook_file_formatter_FORMATTER_view().
1226
 *
1227
 * Returns a drupal_render() array to display an image of the chosen style.
1228
 *
1229
 * This formatter is only capable of displaying local images. If the passed in
1230
 * file is either not local or not an image, nothing is returned, so that
1231
 * file_view_file() can try another formatter.
1232
 */
1233
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
1234
  // Prevent PHP notices when trying to read empty files.
1235
  // @see http://drupal.org/node/681042
1236
  if (!$file->filesize) {
1237
    return;
1238
  }
1239

    
1240
  // Do not bother proceeding if this file does not have an image mime type.
1241
  if (file_entity_file_get_mimetype_type($file) != 'image') {
1242
    return;
1243
  }
1244

    
1245
  if (file_entity_file_is_readable($file)) {
1246
    // We don't sanitize here.
1247
    // @see http://drupal.org/node/1553094#comment-6257382
1248
    // Theme function will take care of escaping.
1249
    if (!isset($file->metadata)) {
1250
      $file->metadata = array();
1251
    }
1252
    $file->metadata += array('width' => NULL, 'height' => NULL);
1253
    $replace_options = array(
1254
      'clear' => TRUE,
1255
      'sanitize' => FALSE,
1256
    );
1257
    if (!empty($display['settings']['image_style'])) {
1258
      $element = array(
1259
        '#theme' => 'image_style',
1260
        '#style_name' => $display['settings']['image_style'],
1261
        '#path' => $file->uri,
1262
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1263
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1264
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1265
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1266
      );
1267
    }
1268
    else {
1269
      $element = array(
1270
        '#theme' => 'image',
1271
        '#path' => $file->uri,
1272
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1273
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1274
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1275
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1276
      );
1277
    }
1278
    return $element;
1279
  }
1280
}
1281

    
1282
/**
1283
 * Check if a file entity is readable or not.
1284
 *
1285
 * @param object $file
1286
 *   A file entity object from file_load().
1287
 *
1288
 * @return boolean
1289
 *   TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
1290
 */
1291
function file_entity_file_is_readable($file) {
1292
  $scheme = file_uri_scheme($file->uri);
1293
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
1294
  return !empty($wrappers[$scheme]);
1295
}
1296

    
1297
/**
1298
 * Implements hook_file_formatter_FORMATTER_settings().
1299
 *
1300
 * Returns form elements for configuring the 'file_image' formatter.
1301
 */
1302
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
1303
  $element = array();
1304
  $element['image_style'] = array(
1305
    '#title' => t('Image style'),
1306
    '#type' => 'select',
1307
    '#options' => image_style_options(FALSE),
1308
    '#default_value' => $settings['image_style'],
1309
    '#empty_option' => t('None (original image)'),
1310
  );
1311

    
1312
  // For image files we allow the alt attribute (required in HTML).
1313
  $element['alt'] = array(
1314
    '#title' => t('Alt attribute'),
1315
    '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
1316
    '#type' => 'textfield',
1317
    '#default_value' => $settings['alt'],
1318
  );
1319

    
1320
  // Allow the setting of the title attribute.
1321
  $element['title'] = array(
1322
    '#title' => t('Title attribute'),
1323
    '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
1324
    '#type' => 'textfield',
1325
    '#default_value' => $settings['title'],
1326
  );
1327

    
1328
  if (module_exists('token')) {
1329
    $element['alt']['#description'] .= t('This field supports tokens.');
1330
    $element['title']['#description'] .= t('This field supports tokens.');
1331
    $element['tokens'] = array(
1332
      '#theme' => 'token_tree',
1333
      '#token_types' => array('file'),
1334
      '#dialog' => TRUE,
1335
    );
1336
  }
1337

    
1338
  return $element;
1339
}
1340

    
1341
/**
1342
 * Menu access callback for the 'view mode file display settings' pages.
1343
 *
1344
 * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
1345
 * be enabled.
1346
 */
1347
function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
1348
  // Deny access if the view mode isn't configured to use custom display
1349
  // settings.
1350
  $view_mode_settings = field_view_mode_settings('file', $file_type->type);
1351
  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
1352
  if (!$visibility) {
1353
    return FALSE;
1354
  }
1355

    
1356
  // Otherwise, continue to an $access_callback check.
1357
  $args = array_slice(func_get_args(), 3);
1358
  $callback = empty($access_callback) ? 0 : trim($access_callback);
1359
  if (is_numeric($callback)) {
1360
    return (bool) $callback;
1361
  }
1362
  elseif (function_exists($access_callback)) {
1363
    return call_user_func_array($access_callback, $args);
1364
  }
1365
}
1366

    
1367
/**
1368
 * Implements hook_modules_enabled().
1369
 */
1370
function file_entity_modules_enabled($modules) {
1371
  file_info_cache_clear();
1372
}
1373

    
1374
/**
1375
 * Implements hook_modules_disabled().
1376
 */
1377
function file_entity_modules_disabled($modules) {
1378
  file_info_cache_clear();
1379
}
1380

    
1381
/**
1382
 * Implements hook_views_api().
1383
 */
1384
function file_entity_views_api() {
1385
  return array(
1386
    'api' => 3,
1387
  );
1388
}
1389

    
1390
/**
1391
 * Returns whether the current page is the full page view of the passed-in file.
1392
 *
1393
 * @param $file
1394
 *   A file object.
1395
 */
1396
function file_entity_is_page($file) {
1397
  $page_file = menu_get_object('file', 1);
1398
  return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
1399
}
1400

    
1401
/**
1402
 * Process variables for file_entity.tpl.php
1403
 *
1404
 * The $variables array contains the following arguments:
1405
 * - $file
1406
 * - $view_mode
1407
 *
1408
 * @see file_entity.tpl.php
1409
 */
1410
function template_preprocess_file_entity(&$variables) {
1411
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
1412
  $variables['file'] = $variables['elements']['#file'];
1413
  $file = $variables['file'];
1414

    
1415
  $variables['id'] = drupal_html_id('file-'. $file->fid);
1416
  $variables['date']      = format_date($file->timestamp);
1417
  $account = user_load($file->uid);
1418
  $variables['name']      = theme('username', array('account' => $account));
1419

    
1420
  $uri = entity_uri('file', $file);
1421
  $variables['file_url']  = url($uri['path'], $uri['options']);
1422
  $label = entity_label('file', $file);
1423
  $variables['label']     = check_plain($label);
1424
  $variables['page']      = $view_mode == 'full' && file_entity_is_page($file);
1425

    
1426
  // Hide the file name from being displayed until we can figure out a better
1427
  // way to control this. We cannot simply not output the title since
1428
  // contextual links require $title_suffix to be output in the template.
1429
  // @see http://drupal.org/node/1245266
1430
  if (!$variables['page']) {
1431
    $variables['title_attributes_array']['class'][] = 'element-invisible';
1432
  }
1433

    
1434
  // Flatten the file object's member fields.
1435
  $variables = array_merge((array) $file, $variables);
1436

    
1437
  // Helpful $content variable for templates.
1438
  $variables += array('content' => array());
1439
  foreach (element_children($variables['elements']) as $key) {
1440
    $variables['content'][$key] = $variables['elements'][$key];
1441
  }
1442

    
1443
  // Make the field variables available with the appropriate language.
1444
  field_attach_preprocess('file', $file, $variables['content'], $variables);
1445

    
1446
  // Attach the file object to the content element.
1447
  $variables['content']['file']['#file'] = $file;
1448

    
1449
  // Display post information only on certain file types.
1450
  if (variable_get('file_submitted_' . $file->type, FALSE)) {
1451
    $variables['display_submitted'] = TRUE;
1452
    $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1453
    $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
1454
  }
1455
  else {
1456
    $variables['display_submitted'] = FALSE;
1457
    $variables['submitted'] = '';
1458
    $variables['user_picture'] = '';
1459
  }
1460

    
1461
  // Gather file classes.
1462
  $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
1463
  $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
1464
  if ($file->status != FILE_STATUS_PERMANENT) {
1465
    $variables['classes_array'][] = 'file-temporary';
1466
  }
1467

    
1468
  // Change the 'file-entity' class into 'file'
1469
  if ($variables['classes_array'][0] == 'file-entity') {
1470
    $variables['classes_array'][0] = 'file';
1471
  }
1472

    
1473
  // Clean up name so there are no underscores.
1474
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
1475
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
1476
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
1477
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
1478
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
1479
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
1480
}
1481

    
1482
/**
1483
 * Returns the file type name of the passed file or file type string.
1484
 *
1485
 * @param $file
1486
 *   A file object or string that indicates the file type to return.
1487
 *
1488
 * @return
1489
 *   The file type name or FALSE if the file type is not found.
1490
 */
1491
function file_entity_type_get_name($file) {
1492
  $type = is_object($file) ? $file->type : $file;
1493
  $info = entity_get_info('file');
1494
  return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
1495
}
1496

    
1497
/**
1498
 * Returns a list of available file type names.
1499
 *
1500
 * @return
1501
 *   An array of file type names, keyed by the type.
1502
 */
1503
function file_entity_type_get_names() {
1504
  $names = &drupal_static(__FUNCTION__);
1505

    
1506
  if (!isset($names)) {
1507
    $names = array();
1508
    $info = entity_get_info('file');
1509
    foreach ($info['bundles'] as $bundle => $bundle_info) {
1510
      $names[$bundle] = $bundle_info['label'];
1511
    }
1512
  }
1513

    
1514
  return $names;
1515
}
1516

    
1517
/**
1518
 * Return an array of available view modes for file entities.
1519
 */
1520
function file_entity_view_mode_labels() {
1521
  $labels = &drupal_static(__FUNCTION__);
1522

    
1523
  if (!isset($options)) {
1524
    $entity_info = entity_get_info('file');
1525
    $labels = array('default' => t('Default'));
1526
    foreach ($entity_info['view modes'] as $machine_name => $mode) {
1527
      $labels[$machine_name] = $mode['label'];
1528
    }
1529
  }
1530

    
1531
  return $labels;
1532
}
1533

    
1534
/**
1535
 * Return the label for a specific file entity view mode.
1536
 */
1537
function file_entity_view_mode_label($view_mode, $default = FALSE) {
1538
  $labels = file_entity_view_mode_labels();
1539
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
1540
}
1541

    
1542
/**
1543
 * Helper function to get a list of hidden stream wrappers.
1544
 *
1545
 * This is used in several places to filter queries for media so that files in
1546
 * temporary:// don't show up.
1547
 */
1548
function file_entity_get_hidden_stream_wrappers() {
1549
  return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
1550
}
1551

    
1552
/**
1553
 * Return a specific stream wrapper's registry information.
1554
 *
1555
 * @param $scheme
1556
 *   A URI scheme, a stream is referenced as "scheme://target".
1557
 *
1558
 * @see file_get_stream_wrappers()
1559
 */
1560
function file_entity_get_stream_wrapper($scheme) {
1561
  $wrappers = file_get_stream_wrappers();
1562
  return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
1563
}
1564

    
1565
/**
1566
 * Implements hook_stream_wrappers_alter().
1567
 */
1568
function file_entity_stream_wrappers_alter(&$wrappers) {
1569
  if (isset($wrappers['private'])) {
1570
    $wrappers['private']['private'] = TRUE;
1571
  }
1572
  if (isset($wrappers['temporary'])) {
1573
    $wrappers['temporary']['private'] = TRUE;
1574
  }
1575
}
1576

    
1577
/**
1578
 * Implements hook_ctools_plugin_api().
1579
 */
1580
function file_entity_ctools_plugin_api($module, $api) {
1581
  if (($module == 'file_entity' && $api == 'file_type') || ($module == 'page_manager' && $api == 'pages_default') || $module == 'panelizer') {
1582
    return array('version' => 1);
1583
  }
1584
  if ($module == 'file_entity' && $api == 'file_default_displays') {
1585
    return array('version' => 1);
1586
  }
1587
}
1588

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

    
1625
/**
1626
 * Determine if a user may perform the given operation on the specified file.
1627
 *
1628
 * @param $op
1629
 *   The operation to be performed on the file. Possible values are:
1630
 *   - "view"
1631
 *   - "download"
1632
 *   - "update"
1633
 *   - "delete"
1634
 *   - "create"
1635
 * @param $file
1636
 *   The file object on which the operation is to be performed, or file type
1637
 *   (e.g. 'image') for "create" operation.
1638
 * @param $account
1639
 *   Optional, a user object representing the user for whom the operation is to
1640
 *   be performed. Determines access for a user other than the current user.
1641
 *
1642
 * @return
1643
 *   TRUE if the operation may be performed, FALSE otherwise.
1644
 */
1645
function file_entity_access($op, $file = NULL, $account = NULL) {
1646
  $rights = &drupal_static(__FUNCTION__, array());
1647

    
1648
  if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
1649
    // If there was no file to check against, and the $op was not one of the
1650
    // supported ones, we return access denied.
1651
    return FALSE;
1652
  }
1653

    
1654
  // If no user object is supplied, the access check is for the current user.
1655
  if (empty($account)) {
1656
    $account = $GLOBALS['user'];
1657
  }
1658

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

    
1663
  // If we've already checked access for this file, user and op, return from
1664
  // cache.
1665
  if (isset($rights[$account->uid][$cache_id][$op])) {
1666
    return $rights[$account->uid][$cache_id][$op];
1667
  }
1668

    
1669
  if (user_access('bypass file access', $account)) {
1670
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1671
  }
1672

    
1673
  // We grant access to the file if both of the following conditions are met:
1674
  // - No modules say to deny access.
1675
  // - At least one module says to grant access.
1676
  $access = module_invoke_all('file_entity_access', $op, $file, $account);
1677
  if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
1678
    return $rights[$account->uid][$cache_id][$op] = FALSE;
1679
  }
1680
  elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
1681
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1682
  }
1683

    
1684

    
1685
  // Fall back to default behaviors on view.
1686
  if ($op == 'view' && is_object($file)) {
1687
    $scheme = file_uri_scheme($file->uri);
1688
    $wrapper = file_entity_get_stream_wrapper($scheme);
1689

    
1690
    if (!empty($wrapper['private'])) {
1691
      // For private files, users can view private files if the
1692
      // user has the 'view private files' permission.
1693
      if (user_access('view private files', $account)) {
1694
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1695
      }
1696

    
1697
      // For private files, users can view their own private files if the
1698
      // user is not anonymous, and has the 'view own private files' permission.
1699
      if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
1700
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1701
      }
1702
    }
1703
    elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
1704
      // For non-private files, allow to see if user owns the file.
1705
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1706
    }
1707
    elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
1708
      // For non-private files, users can view if they have the 'view files'
1709
      // permission.
1710
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1711
    }
1712
  }
1713

    
1714
  return FALSE;
1715
}
1716

    
1717
/**
1718
 * Implements hook_file_entity_access().
1719
 */
1720
function file_entity_file_entity_access($op, $file, $account) {
1721
  // If the file URI is invalid, deny access.
1722
  if (is_object($file) && !file_valid_uri($file->uri)) {
1723
    return FILE_ENTITY_ACCESS_DENY;
1724
  }
1725

    
1726
  if ($op == 'create') {
1727
    if (user_access('create files')) {
1728
      return FILE_ENTITY_ACCESS_ALLOW;
1729
    }
1730
  }
1731

    
1732
  if (!empty($file)) {
1733
    $type = is_string($file) ? $file : $file->type;
1734

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

    
1742
      if ($op == 'update') {
1743
        if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1744
          return FILE_ENTITY_ACCESS_ALLOW;
1745
        }
1746
      }
1747

    
1748
      if ($op == 'delete') {
1749
        if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1750
          return FILE_ENTITY_ACCESS_ALLOW;
1751
        }
1752
      }
1753
    }
1754
  }
1755

    
1756
  return FILE_ENTITY_ACCESS_IGNORE;
1757
}
1758

    
1759
/**
1760
 * Implements hook_query_TAG_alter().
1761
 *
1762
 * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
1763
 * file access checks for the user account given by the 'account' meta-data (or
1764
 * global $user if not provided).
1765
 */
1766
function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
1767
  _file_entity_query_file_entity_access_alter($query, 'file');
1768
}
1769

    
1770
/**
1771
 * Implements hook_query_TAG_alter().
1772
 *
1773
 * This function implements the same functionality as
1774
 * file_entity_query_file_access_alter() for the SQL field storage engine. File
1775
 * access conditions are added for field values belonging to files only.
1776
 */
1777
function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
1778
  //_file_entity_query_file_entity_access_alter($query, 'entity');
1779
}
1780

    
1781
/**
1782
 * Helper for file entity access functions.
1783
 *
1784
 * @param $query
1785
 *   The query to add conditions to.
1786
 * @param $type
1787
 *   Either 'file' or 'entity' depending on what sort of query it is. See
1788
 *   file_entity_query_file_entity_access_alter() and
1789
 *   file_entity_query_entity_field_access_alter() for more.
1790
 */
1791
function _file_entity_query_file_entity_access_alter($query, $type) {
1792
  global $user;
1793

    
1794
  // Read meta-data from query, if provided.
1795
  if (!$account = $query->getMetaData('account')) {
1796
    $account = $user;
1797
  }
1798

    
1799
  // If $account can bypass file access, we don't need to alter the query.
1800
  if (user_access('bypass file access', $account)) {
1801
    return;
1802
  }
1803

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

    
1852
  if ($type == 'entity') {
1853
    // The original query looked something like:
1854
    // @code
1855
    //  SELECT fid FROM sometable s
1856
    //  WHERE ($file_access_conditions)
1857
    // @endcode
1858
    //
1859
    // Our query will look like:
1860
    // @code
1861
    //  SELECT entity_type, entity_id
1862
    //  FROM field_data_something s
1863
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1864
    // @endcode
1865
    //
1866
    // So instead of directly adding to the query object, we need to collect
1867
    // all of the file access conditions in a separate db_and() object and
1868
    // then add it to the query at the end.
1869
    $file_conditions = db_and();
1870
  }
1871
  foreach ($tables as $falias => $tableinfo) {
1872
    $table = $tableinfo['table'];
1873
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
1874
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
1875
      $subquery_conditions = db_or();
1876

    
1877
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
1878
      if (!empty($wrappers['public'])) {
1879
        if (user_access('view files', $account)) {
1880
          foreach (array_keys($wrappers['public']) as $wrapper) {
1881
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1882
          }
1883
        }
1884
        elseif (user_access('view own files', $account)) {
1885
          foreach (array_keys($wrappers['public']) as $wrapper) {
1886
            $subquery_conditions->condition(db_and()
1887
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1888
              ->condition('fm_access.uid', $account->uid)
1889
            );
1890
          }
1891
        }
1892
      }
1893
      if (!empty($wrappers['private'])) {
1894
        if (user_access('view private files', $account)) {
1895
          foreach (array_keys($wrappers['private']) as $wrapper) {
1896
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1897
          }
1898
        }
1899
        elseif (user_access('view own private files', $account)) {
1900
          foreach (array_keys($wrappers['private']) as $wrapper) {
1901
            $subquery_conditions->condition(db_and()
1902
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1903
              ->condition('fm_access.uid', $account->uid)
1904
            );
1905
          }
1906
        }
1907
      }
1908

    
1909
      if ($subquery_conditions->count()) {
1910
        $subquery->condition($subquery_conditions);
1911

    
1912
        $field = 'fid';
1913
        // Now handle entities.
1914
        if ($type == 'entity') {
1915
          // Set a common alias for entities.
1916
          $base_alias = $falias;
1917
          $field = 'entity_id';
1918
        }
1919
        $subquery->where("$falias.$field = fm_access.fid");
1920

    
1921
        // For an entity query, attach the subquery to entity conditions.
1922
        if ($type == 'entity') {
1923
          $file_conditions->exists($subquery);
1924
        }
1925
        // Otherwise attach it to the node query itself.
1926
        elseif ($table == 'file_managed') {
1927
          // Fix for https://drupal.org/node/2073085
1928
          $db_or = db_or();
1929
          $db_or->exists($subquery);
1930
          $db_or->isNull($falias . '.' . $field);
1931
          $query->condition($db_or);
1932
        }
1933
        else {
1934
          $query->exists($subquery);
1935
        }
1936
      }
1937
    }
1938
  }
1939

    
1940
  if ($type == 'entity' && $file_conditions->count()) {
1941
    // All the file access conditions are only for field values belonging to
1942
    // files.
1943
    $file_conditions->condition("$base_alias.entity_type", 'file');
1944
    $or = db_or();
1945
    $or->condition($file_conditions);
1946
    // If the field value belongs to a non-file entity type then this function
1947
    // does not do anything with it.
1948
    $or->condition("$base_alias.entity_type", 'file', '<>');
1949
    // Add the compiled set of rules to the query.
1950
    $query->condition($or);
1951
  }
1952
}
1953

    
1954
/**
1955
 * Implements hook_file_download().
1956
 */
1957
function file_entity_file_download($uri) {
1958
  // Load the file from the URI.
1959
  $file = file_uri_to_object($uri);
1960

    
1961
  // An existing file wasn't found, so we don't control access.
1962
  // E.g. image derivatives will fall here.
1963
  if (empty($file->fid)) {
1964
    return NULL;
1965
  }
1966

    
1967
  // Allow the user to download the file if they have appropriate permissions.
1968
  if (file_entity_access('view', $file)) {
1969
    return file_get_content_headers($file);
1970
  }
1971

    
1972
  return NULL;
1973
}
1974

    
1975
/**
1976
 * Helper function to generate standard file permission list for a given type.
1977
 *
1978
 * @param $type
1979
 *   The machine-readable name of the file type.
1980
 * @return array
1981
 *   An array of permission names and descriptions.
1982
 */
1983
function file_entity_list_permissions($type) {
1984
  $info = file_type_load($type);
1985

    
1986
  // Build standard list of file permissions for this type.
1987
  $permissions = array(
1988
    "edit own $type files" => array(
1989
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
1990
    ),
1991
    "edit any $type files" => array(
1992
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
1993
    ),
1994
    "delete own $type files" => array(
1995
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
1996
    ),
1997
    "delete any $type files" => array(
1998
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
1999
    ),
2000
    "download own $type files" => array(
2001
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
2002
    ),
2003
    "download any $type files" => array(
2004
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
2005
    ),
2006
  );
2007

    
2008
  return $permissions;
2009
}
2010

    
2011
/**
2012
 * Returns an array of file types that should be managed by permissions.
2013
 *
2014
 * By default, this will include all file types in the system. To exclude a
2015
 * specific file from getting permissions defined for it, set the
2016
 * file_entity_permissions_$type variable to 0. File entity does not provide an
2017
 * interface for doing so, however, contrib modules may exclude their own files
2018
 * in hook_install(). Alternatively, contrib modules may configure all file
2019
 * types at once, or decide to apply some other hook_file_entity_access()
2020
 * implementation to some or all file types.
2021
 *
2022
 * @return
2023
 *   An array of file types managed by this module.
2024
 */
2025
function file_entity_permissions_get_configured_types() {
2026

    
2027
  $configured_types = array();
2028

    
2029
  foreach (file_type_get_enabled_types() as $type => $info) {
2030
    if (variable_get('file_entity_permissions_' . $type, 1)) {
2031
      $configured_types[] = $type;
2032
    }
2033
  }
2034

    
2035
  return $configured_types;
2036
}
2037

    
2038
/**
2039
 * @} End of "defgroup file_entity_access".
2040
 *
2041
 * Implements hook_file_default_types().
2042
 */
2043
function file_entity_file_default_types() {
2044
  $types = array();
2045

    
2046
  // Image.
2047
  $types['image'] = (object) array(
2048
    'api_version' => 1,
2049
    'type' => 'image',
2050
    'label' => t('Image'),
2051
    'description' => t('An <em>Image</em> file is a still visual.'),
2052
    'mimetypes' => array(
2053
      'image/*',
2054
    ),
2055
  );
2056

    
2057
  // Video.
2058
  $types['video'] = (object) array(
2059
    'api_version' => 1,
2060
    'type' => 'video',
2061
    'label' => t('Video'),
2062
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2063
    'mimetypes' => array(
2064
      'video/*',
2065
    ),
2066
  );
2067

    
2068
  // Audio.
2069
  $types['audio'] = (object) array(
2070
    'api_version' => 1,
2071
    'type' => 'audio',
2072
    'label' => t('Audio'),
2073
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2074
    'mimetypes' => array(
2075
      'audio/*',
2076
    ),
2077
  );
2078

    
2079
  // Document.
2080
  $types['document'] = (object) array(
2081
    'api_version' => 1,
2082
    'type' => 'document',
2083
    'label' => t('Document'),
2084
    'description' => t('A <em>Document</em> file is written information.'),
2085
    'mimetypes' => array(
2086
      'text/plain',
2087
      'application/msword',
2088
      'application/vnd.ms-excel',
2089
      'application/pdf',
2090
      'application/vnd.ms-powerpoint',
2091
      'application/vnd.oasis.opendocument.text',
2092
      'application/vnd.oasis.opendocument.spreadsheet',
2093
      'application/vnd.oasis.opendocument.presentation',
2094
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2095
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2096
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2097
    ),
2098
  );
2099

    
2100
  return $types;
2101
}
2102

    
2103
/**
2104
 * Implements hook_file_operations().
2105
 */
2106
function file_entity_file_operations() {
2107
  $operations = array(
2108
    'permanent' => array(
2109
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2110
      'callback' => 'file_entity_mass_update',
2111
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2112
    ),
2113
    'temporary' => array(
2114
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2115
      'callback' => 'file_entity_mass_update',
2116
      'callback arguments' => array('updates' => array('status' => 0)),
2117
    ),
2118
    'delete' => array(
2119
      'label' => t('Delete selected files'),
2120
      'callback' => NULL,
2121
    ),
2122
  );
2123
  return $operations;
2124
}
2125

    
2126
/**
2127
 * Clear the field cache for any entities referencing a specific file.
2128
 *
2129
 * @param object $file
2130
 *   A file object.
2131
 */
2132
function file_entity_invalidate_field_caches($file) {
2133
  $entity_types = &drupal_static(__FUNCTION__);
2134

    
2135
  // Gather the list of entity types which support field caching.
2136
  if (!isset($entity_types)) {
2137
    $entity_types = array();
2138
    foreach (entity_get_info() as $entity_type => $entity_info) {
2139
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2140
        $entity_types[] = $entity_type;
2141
      }
2142
    }
2143
  }
2144

    
2145
  // If no entity types support field caching, then there is no work to be done.
2146
  if (empty($entity_types)) {
2147
    return;
2148
  }
2149

    
2150
  $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();
2151
  if (!empty($records)) {
2152
    $cids = array();
2153
    foreach ($records as $record) {
2154
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2155
    }
2156
    cache_clear_all($cids, 'cache_field');
2157
  }
2158
}
2159

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

    
2175
/**
2176
 * Check if a file entity is considered writeable or not.
2177
 *
2178
 * @param object $file
2179
 *   A file entity object from file_load().
2180
 *
2181
 * @return
2182
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2183
 *   or FALSE otherwise.
2184
 */
2185
function file_entity_file_is_writeable($file) {
2186
  $scheme = file_uri_scheme($file->uri);
2187
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2188
  return !empty($wrappers[$scheme]);
2189
}
2190

    
2191
/**
2192
 * Pre-render callback for adding validation descriptions to file upload fields.
2193
 */
2194
function file_entity_upload_validators_pre_render($element) {
2195
  if (!empty($element['#upload_validators'])) {
2196
    if (!isset($element['#description'])) {
2197
      $element['#description'] = '';
2198
    }
2199
    if ($element['#description'] !== FALSE) {
2200
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2201
    }
2202
  }
2203
  return $element;
2204
}
2205

    
2206
/**
2207
 * @name pathauto_file Pathauto integration for the core file module.
2208
 * @{
2209
 */
2210

    
2211
/**
2212
 * Implements hook_file_insert() on behalf of pathauto.module.
2213
 */
2214
function pathauto_file_insert($file) {
2215
  pathauto_file_update_alias($file, 'insert');
2216
}
2217

    
2218
/**
2219
 * Implements hook_file_update() on behalf of pathauto.module.
2220
 */
2221
function pathauto_file_update($file) {
2222
  pathauto_file_update_alias($file, 'update');
2223
}
2224

    
2225
/**
2226
 * Implements hook_file_delete() on behalf of pathauto.module.
2227
 */
2228
function pathauto_file_delete($file) {
2229
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2230
}
2231

    
2232
/**
2233
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2234
 *
2235
 * Add the Pathauto settings to the file form.
2236
 */
2237
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2238
  $file = $form_state['file'];
2239
  $langcode = pathauto_entity_language('file', $file);
2240
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2241
}
2242

    
2243
/**
2244
 * Implements hook_file_operations() on behalf of pathauto.module.
2245
 */
2246
function pathauto_file_operations() {
2247
  $operations['pathauto_update_alias'] = array(
2248
    'label' => t('Update URL alias'),
2249
    'callback' => 'pathauto_file_update_alias_multiple',
2250
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2251
  );
2252
  return $operations;
2253
}
2254

    
2255
/**
2256
 * Update the URL aliases for an individual file.
2257
 *
2258
 * @param $file
2259
 *   A file object.
2260
 * @param $op
2261
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2262
 * @param $options
2263
 *   An optional array of additional options.
2264
 */
2265
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2266
  // Skip processing if the user has disabled pathauto for the file.
2267
  if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
2268
    return;
2269
  }
2270

    
2271
  $options += array('language' => pathauto_entity_language('file', $file));
2272

    
2273
  // Skip processing if the file has no pattern.
2274
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2275
    return;
2276
  }
2277

    
2278
  module_load_include('inc', 'pathauto');
2279
  $uri = entity_uri('file', $file);
2280
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2281
}
2282

    
2283
/**
2284
 * Update the URL aliases for multiple files.
2285
 *
2286
 * @param $fids
2287
 *   An array of file IDs.
2288
 * @param $op
2289
 *   Operation being performed on the files ('insert', 'update' or
2290
 *   'bulkupdate').
2291
 * @param $options
2292
 *   An optional array of additional options.
2293
 */
2294
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2295
  $options += array('message' => FALSE);
2296

    
2297
  $files = file_load_multiple($fids);
2298
  foreach ($files as $file) {
2299
    pathauto_file_update_alias($file, $op, $options);
2300
  }
2301

    
2302
  if (!empty($options['message'])) {
2303
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2304
  }
2305
}
2306

    
2307
/**
2308
 * Update action wrapper for pathauto_file_update_alias().
2309
 */
2310
function pathauto_file_update_action($file, $context = array()) {
2311
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2312
}
2313

    
2314
/**
2315
 * @} End of "name pathauto_file".
2316
 */
2317

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

    
2368
/**
2369
 * Implements hook_file_insert() on behalf of path.module.
2370
 */
2371
function path_file_insert($file) {
2372
  if (isset($file->path)) {
2373
    $path = $file->path;
2374
    $path['alias'] = trim($path['alias']);
2375
    // Only save a non-empty alias.
2376
    if (!empty($path['alias'])) {
2377
      // Ensure fields for programmatic executions.
2378
      $path['source'] = 'file/' . $file->fid;
2379
      // Core does not provide a way to store the file language but contrib
2380
      // modules can do it so we need to take this into account.
2381
      $langcode = entity_language('file', $file);
2382
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2383
      path_save($path);
2384
    }
2385
  }
2386
}
2387

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

    
2412
/**
2413
 * Implements hook_file_delete() on behalf of path.module.
2414
 */
2415
function path_file_delete($file) {
2416
  // Delete all aliases associated with this file.
2417
  path_delete(array('source' => 'file/' . $file->fid));
2418
}
2419

    
2420
/**
2421
 * Checks if pattern(s) match mimetype(s).
2422
 */
2423
function file_entity_match_mimetypes($needle, $haystack) {
2424
  $needle = is_array($needle) ? $needle : array($needle);
2425
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2426

    
2427
  foreach ($haystack as $mimetype) {
2428
    foreach ($needle as $search) {
2429
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2430
        return TRUE;
2431
      }
2432
    }
2433
  }
2434

    
2435
  return FALSE;
2436
}
2437

    
2438
/**
2439
 * A wrapper function for the PHP function fnmatch().
2440
 *
2441
 * We include this, because Windows servers do not implement fnmatch() until
2442
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2443
 */
2444
function file_entity_fnmatch($pattern, $string) {
2445
  if (!function_exists('fnmatch')) {
2446
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2447
  }
2448
  return fnmatch($pattern, $string);
2449
}
2450

    
2451
/**
2452
 * Return an URI for a file download.
2453
 */
2454
function file_entity_download_uri($file) {
2455
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2456
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2457
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2458
  }
2459
  return $uri;
2460
}
2461

    
2462
function file_entity_file_get_mimetype_type($file) {
2463
  list($type, $subtype) = explode('/', $file->filemime, 2);
2464
  return $type;
2465
}
2466

    
2467
/**
2468
 * Implements hook_admin_menu_map().
2469
 */
2470
function file_entity_admin_menu_map() {
2471
  if (!user_access('administer file types')) {
2472
    return;
2473
  }
2474
  $map['admin/structure/file-types/manage/%file_type'] = array(
2475
    'parent' => 'admin/structure/file-types',
2476
    'arguments' => array(
2477
      array('%file_type' => array_keys(file_entity_type_get_names())),
2478
    ),
2479
  );
2480
  return $map;
2481
}
2482

    
2483
/*
2484
 * Generates a token to protect a file download URL.
2485
 *
2486
 * This prevents unauthorized crawling of all file download URLs since the
2487
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
2488
 * to guess or attempt many at once. This can be costly both in CPU time
2489
 * and bandwidth.
2490
 *
2491
 * @see image_style_path_token()
2492
 *
2493
 * @param object $file
2494
 *   A file entity object.
2495
 *
2496
 * @return string
2497
 *   An eight-character token which can be used to protect file downloads
2498
 *   against denial-of-service attacks.
2499
 */
2500
function file_entity_get_download_token($file) {
2501
  // Return the first eight characters.
2502
  return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
2503
}
2504

    
2505
/**
2506
 * Find all fields that are of a certain field type.
2507
 *
2508
 * @param string $field_type
2509
 *   A field type.
2510
 *
2511
 * @return array
2512
 *   An array of field names that match the type $field_type.
2513
 */
2514
function _file_entity_get_fields_by_type($field_type) {
2515
  $return = array();
2516
  if (function_exists('field_info_field_map')) {
2517
    foreach (field_info_field_map() as $field_name => $field) {
2518
      if ($field['type'] == $field_type) {
2519
        $return[$field_name] = $field_name;
2520
      }
2521
    }
2522
  }
2523
  else {
2524
    foreach (field_info_fields() as $field_name => $field) {
2525
      if ($field['type'] == $field_type) {
2526
        $return[$field_name] = $field_name;
2527
      }
2528
    }
2529
  }
2530
  return $return;
2531
}
2532

    
2533
/**
2534
 * Implements hook_field_attach_load().
2535
 */
2536
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2537
  // Loop over all the entities looking for entities with attached images.
2538
  foreach ($entities as $entity) {
2539
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2540
    // Examine every image field instance attached to this entity's bundle.
2541
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2542
    foreach ($instances as $field_name => $instance) {
2543
      if (!empty($entity->{$field_name})) {
2544
        foreach ($entity->{$field_name} as $langcode => $items) {
2545
          foreach ($items as $delta => $item) {
2546
            // If alt and title text is not specified, fall back to alt and
2547
            // title text on the file.
2548
            if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
2549
              $file = file_load($item['fid']);
2550
              foreach (array('alt', 'title') as $key) {
2551
                if (empty($item[$key]) && !empty($file->{$key})) {
2552
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2553
                }
2554
              }
2555
            }
2556
          }
2557
        }
2558
      }
2559
    }
2560
  }
2561
}
2562

    
2563
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2564
  $wrappers = array();
2565
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2566
    if (empty($wrapper['private'])) {
2567
      $wrappers['public'][$key] = $wrapper['name'];
2568
    }
2569
    else {
2570
      $wrappers['private'][$key] = $wrapper['name'];
2571
    }
2572
  }
2573
  return $wrappers;
2574
}