Projet

Général

Profil

Paste
Télécharger (87,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / file_entity / file_entity.module @ 59ae487e

1
<?php
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
409
  return $items;
410
}
411

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

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

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

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

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

    
478
  return $permissions;
479
}
480

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
628
    $types = file_entity_type_get_names();
629

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1029
  return $info;
1030
}
1031

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

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

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

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

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

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

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

    
1107
  return $info;
1108
}
1109

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

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

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

    
1154
  return $formatters;
1155
}
1156

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1357
  return $element;
1358
}
1359

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1533
  return $names;
1534
}
1535

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

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

    
1550
  return $labels;
1551
}
1552

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1703

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

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

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

    
1733
  return FALSE;
1734
}
1735

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

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

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

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

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

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

    
1775
  return FILE_ENTITY_ACCESS_IGNORE;
1776
}
1777

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

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

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

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

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

    
1823
  $tables = $query->getTables();
1824
  $base_table = $query->getMetaData('base_table');
1825
  // Do not use the base table for general entity queries unless this is
1826
  // querying the file_managed table directly.
1827
  if ($base_table && $type == 'entity' && $base_table != 'file_managed') {
1828
    $base_table = '';
1829
  }
1830

    
1831
  // If no base table is specified explicitly, search for one.
1832
  if (!$base_table) {
1833
    $fallback = '';
1834
    foreach ($tables as $alias => $table_info) {
1835
      if (!($table_info instanceof SelectQueryInterface)) {
1836
        $table = $table_info['table'];
1837
        // If the file_managed table is in the query, it wins immediately.
1838
        if ($table == 'file_managed') {
1839
          $base_table = $table;
1840
          break;
1841
        }
1842
        // Check whether the table has a foreign key to file_managed.fid. If it
1843
        // does, do not run this check again as we found a base table and only
1844
        // file_managed can triumph that.
1845
        if (!$base_table) {
1846
          // The schema is cached.
1847
          $schema = drupal_get_schema($table);
1848
          if (isset($schema['fields']['fid'])) {
1849
            if (isset($schema['foreign keys'])) {
1850
              foreach ($schema['foreign keys'] as $relation) {
1851
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
1852
                  $base_table = $table;
1853
                }
1854
              }
1855
            }
1856
            else {
1857
              // At least it's a fid. A table with a field called fid is very
1858
              // very likely to be a file_managed.fid in a file access query.
1859
              $fallback = $table;
1860
            }
1861
          }
1862
          elseif (isset($schema['fields']['entity_id']) && isset($schema['fields']['entity_type']) && isset($schema['fields']['deleted']) && isset($schema['fields']['delta'])) {
1863
            // The table is a field data table, use it as fallback.
1864
            $base_table = $table;
1865
          }
1866
        }
1867
      }
1868
    }
1869
    // If there is nothing else, use the fallback.
1870
    if (!$base_table) {
1871
      if ($fallback) {
1872
        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);
1873
        $base_table = $fallback;
1874
      }
1875
      else {
1876
        // Ignore this query as it was a general field query and no
1877
        // relationships were found to the file_managed table.
1878
        if ($type == 'entity') {
1879
          return;
1880
        }
1881
        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.'));
1882
      }
1883
    }
1884
  }
1885

    
1886
  if ($type == 'entity') {
1887
    // The original query looked something like:
1888
    // @code
1889
    //  SELECT fid FROM sometable s
1890
    //  WHERE ($file_access_conditions)
1891
    // @endcode
1892
    //
1893
    // Our query will look like:
1894
    // @code
1895
    //  SELECT entity_type, entity_id
1896
    //  FROM field_data_something s
1897
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1898
    // @endcode
1899
    //
1900
    // So instead of directly adding to the query object, we need to collect
1901
    // all of the file access conditions in a separate db_and() object and
1902
    // then add it to the query at the end.
1903
    $file_conditions = db_and();
1904
  }
1905
  foreach ($tables as $falias => $tableinfo) {
1906
    $table = $tableinfo['table'];
1907
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
1908
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
1909
      $subquery_conditions = db_or();
1910

    
1911
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
1912
      if (!empty($wrappers['public'])) {
1913
        if (user_access('view files', $account)) {
1914
          foreach (array_keys($wrappers['public']) as $wrapper) {
1915
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1916
          }
1917
        }
1918
        elseif (user_access('view own files', $account)) {
1919
          foreach (array_keys($wrappers['public']) as $wrapper) {
1920
            $subquery_conditions->condition(db_and()
1921
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1922
              ->condition('fm_access.uid', $account->uid)
1923
            );
1924
          }
1925
        }
1926
      }
1927
      if (!empty($wrappers['private'])) {
1928
        if (user_access('view private files', $account)) {
1929
          foreach (array_keys($wrappers['private']) as $wrapper) {
1930
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1931
          }
1932
        }
1933
        elseif (user_access('view own private files', $account)) {
1934
          foreach (array_keys($wrappers['private']) as $wrapper) {
1935
            $subquery_conditions->condition(db_and()
1936
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1937
              ->condition('fm_access.uid', $account->uid)
1938
            );
1939
          }
1940
        }
1941
      }
1942

    
1943
      // If there is no subquery conditions, the query is likely for file usage.
1944
      // Or user can only access public files.
1945
      // If there are subquery conditions then add them to the subquery.
1946
      if ($subquery_conditions->count() >= 1) {
1947
        $subquery->condition($subquery_conditions);
1948
      }
1949

    
1950
      $field = 'fid';
1951
      // Now handle entities.
1952
      if ($type == 'entity') {
1953
        // Set a common alias for entities.
1954
        $base_alias = $falias;
1955
        $field = ($falias == 'file_managed' ? 'fid' : 'entity_id');
1956
      }
1957
      $subquery->where("$falias.$field = fm_access.fid");
1958

    
1959
      // For an entity query, attach the subquery to entity conditions.
1960
      if ($type == 'entity') {
1961
        $file_conditions->exists($subquery);
1962
      }
1963
      // Otherwise attach it to the node query itself.
1964
      elseif ($table == 'file_managed') {
1965
        // Fix for https://drupal.org/node/2073085
1966
        $db_or = db_or();
1967
        $db_or->exists($subquery);
1968
        $db_or->isNull($falias . '.' . $field);
1969
        $query->condition($db_or);
1970
      }
1971
      else {
1972
        $query->exists($subquery);
1973
      }
1974
    }
1975
  }
1976

    
1977
  if ($type == 'entity' && $file_conditions->count()) {
1978
    // All the file access conditions are only for field values belonging to
1979
    // files.
1980
    if ($base_alias !== 'file_managed') {
1981
      $file_conditions->condition("$base_alias.entity_type", 'file');
1982
      $or = db_or();
1983
      $or->condition($file_conditions);
1984
      // If the field value belongs to a non-file entity type then this function
1985
      // does not do anything with it.
1986
      $or->condition("$base_alias.entity_type", 'file', '<>');
1987
      // Add the compiled set of rules to the query.
1988
      $query->condition($or);
1989
    }
1990
    else {
1991
      $query->condition($file_conditions);
1992
    }
1993
  }
1994
}
1995

    
1996
/**
1997
 * Implements hook_file_download().
1998
 */
1999
function file_entity_file_download($uri) {
2000
  // Load the file from the URI.
2001
  $file = file_uri_to_object($uri);
2002

    
2003
  // An existing file wasn't found, so we don't control access.
2004
  // E.g. image derivatives will fall here.
2005
  if (empty($file->fid)) {
2006
    return NULL;
2007
  }
2008

    
2009
  // Allow the user to download the file if they have appropriate permissions.
2010
  if (file_entity_access('view', $file)) {
2011
    return file_get_content_headers($file);
2012
  }
2013

    
2014
  return NULL;
2015
}
2016

    
2017
/**
2018
 * Helper function to generate standard file permission list for a given type.
2019
 *
2020
 * @param $type
2021
 *   The machine-readable name of the file type.
2022
 * @return array
2023
 *   An array of permission names and descriptions.
2024
 */
2025
function file_entity_list_permissions($type) {
2026
  $info = file_type_load($type);
2027

    
2028
  // Build standard list of file permissions for this type.
2029
  $permissions = array(
2030
    "edit own $type files" => array(
2031
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
2032
    ),
2033
    "edit any $type files" => array(
2034
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
2035
    ),
2036
    "delete own $type files" => array(
2037
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
2038
    ),
2039
    "delete any $type files" => array(
2040
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
2041
    ),
2042
    "download own $type files" => array(
2043
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
2044
    ),
2045
    "download any $type files" => array(
2046
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
2047
    ),
2048
  );
2049

    
2050
  return $permissions;
2051
}
2052

    
2053
/**
2054
 * Returns an array of file types that should be managed by permissions.
2055
 *
2056
 * By default, this will include all file types in the system. To exclude a
2057
 * specific file from getting permissions defined for it, set the
2058
 * file_entity_permissions_$type variable to 0. File entity does not provide an
2059
 * interface for doing so, however, contrib modules may exclude their own files
2060
 * in hook_install(). Alternatively, contrib modules may configure all file
2061
 * types at once, or decide to apply some other hook_file_entity_access()
2062
 * implementation to some or all file types.
2063
 *
2064
 * @return
2065
 *   An array of file types managed by this module.
2066
 */
2067
function file_entity_permissions_get_configured_types() {
2068

    
2069
  $configured_types = array();
2070

    
2071
  foreach (file_type_get_enabled_types() as $type => $info) {
2072
    if (variable_get('file_entity_permissions_' . $type, 1)) {
2073
      $configured_types[] = $type;
2074
    }
2075
  }
2076

    
2077
  return $configured_types;
2078
}
2079

    
2080
/**
2081
 * @} End of "defgroup file_entity_access".
2082
 *
2083
 * Implements hook_file_default_types().
2084
 */
2085
function file_entity_file_default_types() {
2086
  $types = array();
2087

    
2088
  // Image.
2089
  $types['image'] = (object) array(
2090
    'api_version' => 1,
2091
    'type' => 'image',
2092
    'label' => t('Image'),
2093
    'description' => t('An <em>Image</em> file is a still visual.'),
2094
    'mimetypes' => array(
2095
      'image/*',
2096
    ),
2097
  );
2098

    
2099
  // Video.
2100
  $types['video'] = (object) array(
2101
    'api_version' => 1,
2102
    'type' => 'video',
2103
    'label' => t('Video'),
2104
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2105
    'mimetypes' => array(
2106
      'video/*',
2107
    ),
2108
  );
2109

    
2110
  // Audio.
2111
  $types['audio'] = (object) array(
2112
    'api_version' => 1,
2113
    'type' => 'audio',
2114
    'label' => t('Audio'),
2115
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2116
    'mimetypes' => array(
2117
      'audio/*',
2118
    ),
2119
  );
2120

    
2121
  // Document.
2122
  $types['document'] = (object) array(
2123
    'api_version' => 1,
2124
    'type' => 'document',
2125
    'label' => t('Document'),
2126
    'description' => t('A <em>Document</em> file is written information.'),
2127
    'mimetypes' => array(
2128
      'text/plain',
2129
      'application/msword',
2130
      'application/vnd.ms-excel',
2131
      'application/pdf',
2132
      'application/vnd.ms-powerpoint',
2133
      'application/vnd.oasis.opendocument.text',
2134
      'application/vnd.oasis.opendocument.spreadsheet',
2135
      'application/vnd.oasis.opendocument.presentation',
2136
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2137
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2138
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2139
    ),
2140
  );
2141

    
2142
  return $types;
2143
}
2144

    
2145
/**
2146
 * Implements hook_file_operations().
2147
 */
2148
function file_entity_file_operations() {
2149
  $operations = array(
2150
    'permanent' => array(
2151
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2152
      'callback' => 'file_entity_mass_update',
2153
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2154
    ),
2155
    'temporary' => array(
2156
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2157
      'callback' => 'file_entity_mass_update',
2158
      'callback arguments' => array('updates' => array('status' => 0)),
2159
    ),
2160
    'delete' => array(
2161
      'label' => t('Delete selected files'),
2162
      'callback' => NULL,
2163
    ),
2164
  );
2165
  return $operations;
2166
}
2167

    
2168
/**
2169
 * Clear the field cache for any entities referencing a specific file.
2170
 *
2171
 * @param object $file
2172
 *   A file object.
2173
 */
2174
function file_entity_invalidate_field_caches($file) {
2175
  $entity_types = &drupal_static(__FUNCTION__);
2176

    
2177
  // Gather the list of entity types which support field caching.
2178
  if (!isset($entity_types)) {
2179
    $entity_types = array();
2180
    foreach (entity_get_info() as $entity_type => $entity_info) {
2181
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2182
        $entity_types[] = $entity_type;
2183
      }
2184
    }
2185
  }
2186

    
2187
  // If no entity types support field caching, then there is no work to be done.
2188
  if (empty($entity_types)) {
2189
    return;
2190
  }
2191

    
2192
  $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();
2193
  if (!empty($records)) {
2194
    $cids = array();
2195
    foreach ($records as $record) {
2196
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2197
    }
2198
    cache_clear_all($cids, 'cache_field');
2199
  }
2200
}
2201

    
2202
/**
2203
 * Check if a file entity is considered local or not.
2204
 *
2205
 * @param object $file
2206
 *   A file entity object from file_load().
2207
 *
2208
 * @return
2209
 *   TRUE if the file is using a local stream wrapper, or FALSE otherwise.
2210
 */
2211
function file_entity_file_is_local($file) {
2212
  $scheme = file_uri_scheme($file->uri);
2213
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
2214
  return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
2215
}
2216

    
2217
/**
2218
 * Check if a file entity is considered writeable or not.
2219
 *
2220
 * @param object $file
2221
 *   A file entity object from file_load().
2222
 *
2223
 * @return
2224
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2225
 *   or FALSE otherwise.
2226
 */
2227
function file_entity_file_is_writeable($file) {
2228
  $scheme = file_uri_scheme($file->uri);
2229
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2230
  return !empty($wrappers[$scheme]);
2231
}
2232

    
2233
/**
2234
 * Pre-render callback for adding validation descriptions to file upload fields.
2235
 */
2236
function file_entity_upload_validators_pre_render($element) {
2237
  if (!empty($element['#upload_validators'])) {
2238
    if (!isset($element['#description'])) {
2239
      $element['#description'] = '';
2240
    }
2241
    if ($element['#description'] !== FALSE) {
2242
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2243
    }
2244
  }
2245
  return $element;
2246
}
2247

    
2248
/**
2249
 * @name pathauto_file Pathauto integration for the core file module.
2250
 * @{
2251
 */
2252

    
2253
/**
2254
 * Implements hook_file_insert() on behalf of pathauto.module.
2255
 */
2256
function pathauto_file_insert($file) {
2257
  pathauto_file_update_alias($file, 'insert');
2258
}
2259

    
2260
/**
2261
 * Implements hook_file_update() on behalf of pathauto.module.
2262
 */
2263
function pathauto_file_update($file) {
2264
  pathauto_file_update_alias($file, 'update');
2265
}
2266

    
2267
/**
2268
 * Implements hook_file_delete() on behalf of pathauto.module.
2269
 */
2270
function pathauto_file_delete($file) {
2271
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2272
}
2273

    
2274
/**
2275
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2276
 *
2277
 * Add the Pathauto settings to the file form.
2278
 */
2279
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2280
  $file = $form_state['file'];
2281
  $langcode = pathauto_entity_language('file', $file);
2282
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2283
}
2284

    
2285
/**
2286
 * Implements hook_file_operations() on behalf of pathauto.module.
2287
 */
2288
function pathauto_file_operations() {
2289
  $operations['pathauto_update_alias'] = array(
2290
    'label' => t('Update URL alias'),
2291
    'callback' => 'pathauto_file_update_alias_multiple',
2292
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2293
  );
2294
  return $operations;
2295
}
2296

    
2297
/**
2298
 * Update the URL aliases for an individual file.
2299
 *
2300
 * @param $file
2301
 *   A file object.
2302
 * @param $op
2303
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2304
 * @param $options
2305
 *   An optional array of additional options.
2306
 */
2307
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2308
  // Skip processing if the user has disabled pathauto for the file.
2309
  if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
2310
    return;
2311
  }
2312

    
2313
  $options += array('language' => pathauto_entity_language('file', $file));
2314

    
2315
  // Skip processing if the file has no pattern.
2316
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2317
    return;
2318
  }
2319

    
2320
  module_load_include('inc', 'pathauto');
2321
  $uri = entity_uri('file', $file);
2322
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2323
}
2324

    
2325
/**
2326
 * Update the URL aliases for multiple files.
2327
 *
2328
 * @param $fids
2329
 *   An array of file IDs.
2330
 * @param $op
2331
 *   Operation being performed on the files ('insert', 'update' or
2332
 *   'bulkupdate').
2333
 * @param $options
2334
 *   An optional array of additional options.
2335
 */
2336
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2337
  $options += array('message' => FALSE);
2338

    
2339
  $files = file_load_multiple($fids);
2340
  foreach ($files as $file) {
2341
    pathauto_file_update_alias($file, $op, $options);
2342
  }
2343

    
2344
  if (!empty($options['message'])) {
2345
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2346
  }
2347
}
2348

    
2349
/**
2350
 * Update action wrapper for pathauto_file_update_alias().
2351
 */
2352
function pathauto_file_update_action($file, $context = array()) {
2353
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2354
}
2355

    
2356
/**
2357
 * @} End of "name pathauto_file".
2358
 */
2359

    
2360
/**
2361
 * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
2362
 */
2363
function path_form_file_entity_edit_alter(&$form, $form_state) {
2364
  // Make sure this does not show up on the delete confirmation form.
2365
  if (empty($form_state['confirm_delete'])) {
2366
    $file = $form_state['file'];
2367
    $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
2368
    $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2369
    $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
2370
    $path = (isset($file->fid) ? path_load($conditions) : array());
2371
    if ($path === FALSE) {
2372
      $path = array();
2373
    }
2374
    $path += array(
2375
      'pid' => NULL,
2376
      'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
2377
      'alias' => '',
2378
      'language' => $langcode,
2379
    );
2380
    $form['path'] = array(
2381
      '#type' => 'fieldset',
2382
      '#title' => t('URL path settings'),
2383
      '#collapsible' => TRUE,
2384
      '#collapsed' => empty($path['alias']),
2385
      '#group' => 'additional_settings',
2386
      '#attributes' => array(
2387
        'class' => array('path-form'),
2388
      ),
2389
      '#attached' => array(
2390
        'js' => array(drupal_get_path('module', 'path') . '/path.js'),
2391
      ),
2392
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
2393
      '#weight' => 30,
2394
      '#tree' => TRUE,
2395
      '#element_validate' => array('path_form_element_validate'),
2396
    );
2397
    $form['path']['alias'] = array(
2398
      '#type' => 'textfield',
2399
      '#title' => t('URL alias'),
2400
      '#default_value' => $path['alias'],
2401
      '#maxlength' => 255,
2402
      '#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.'),
2403
    );
2404
    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
2405
    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
2406
    $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
2407
  }
2408
}
2409

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

    
2430
/**
2431
 * Implements hook_file_update() on behalf of path.module.
2432
 */
2433
function path_file_update($file) {
2434
  if (isset($file->path)) {
2435
    $path = $file->path;
2436
    $path['alias'] = trim($path['alias']);
2437
    // Delete old alias if user erased it.
2438
    if (!empty($path['fid']) && empty($path['alias'])) {
2439
      path_delete($path['fid']);
2440
    }
2441
    // Only save a non-empty alias.
2442
    if (!empty($path['alias'])) {
2443
      // Ensure fields for programmatic executions.
2444
      $path['source'] = 'file/' . $file->fid;
2445
      // Core does not provide a way to store the file language but contrib
2446
      // modules can do it so we need to take this into account.
2447
      $langcode = entity_language('file', $file);
2448
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2449
      path_save($path);
2450
    }
2451
  }
2452
}
2453

    
2454
/**
2455
 * Implements hook_file_delete() on behalf of path.module.
2456
 */
2457
function path_file_delete($file) {
2458
  // Delete all aliases associated with this file.
2459
  path_delete(array('source' => 'file/' . $file->fid));
2460
}
2461

    
2462
/**
2463
 * Checks if pattern(s) match mimetype(s).
2464
 */
2465
function file_entity_match_mimetypes($needle, $haystack) {
2466
  $needle = is_array($needle) ? $needle : array($needle);
2467
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2468

    
2469
  foreach ($haystack as $mimetype) {
2470
    foreach ($needle as $search) {
2471
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2472
        return TRUE;
2473
      }
2474
    }
2475
  }
2476

    
2477
  return FALSE;
2478
}
2479

    
2480
/**
2481
 * A wrapper function for the PHP function fnmatch().
2482
 *
2483
 * We include this, because Windows servers do not implement fnmatch() until
2484
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2485
 */
2486
function file_entity_fnmatch($pattern, $string) {
2487
  if (!function_exists('fnmatch')) {
2488
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2489
  }
2490
  return fnmatch($pattern, $string);
2491
}
2492

    
2493
/**
2494
 * Return an URI for a file download.
2495
 */
2496
function file_entity_download_uri($file) {
2497
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2498
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2499
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2500
  }
2501
  return $uri;
2502
}
2503

    
2504
function file_entity_file_get_mimetype_type($file) {
2505
  if (is_array($file)) {
2506
    $file = (object) $file;
2507
  }
2508
  list($type, $subtype) = explode('/', $file->filemime, 2);
2509
  return $type;
2510
}
2511

    
2512
/**
2513
 * Implements hook_admin_menu_map().
2514
 */
2515
function file_entity_admin_menu_map() {
2516
  if (!user_access('administer file types')) {
2517
    return;
2518
  }
2519
  $map['admin/structure/file-types/manage/%file_type'] = array(
2520
    'parent' => 'admin/structure/file-types',
2521
    'arguments' => array(
2522
      array('%file_type' => array_keys(file_entity_type_get_names())),
2523
    ),
2524
  );
2525
  return $map;
2526
}
2527

    
2528
/*
2529
 * Generates a token to protect a file download URL.
2530
 *
2531
 * This prevents unauthorized crawling of all file download URLs since the
2532
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
2533
 * to guess or attempt many at once. This can be costly both in CPU time
2534
 * and bandwidth.
2535
 *
2536
 * @see image_style_path_token()
2537
 *
2538
 * @param object $file
2539
 *   A file entity object.
2540
 *
2541
 * @return string
2542
 *   An eight-character token which can be used to protect file downloads
2543
 *   against denial-of-service attacks.
2544
 */
2545
function file_entity_get_download_token($file) {
2546
  // Return the first eight characters.
2547
  return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
2548
}
2549

    
2550
/**
2551
 * Find all fields that are of a certain field type.
2552
 *
2553
 * @param string $field_type
2554
 *   A field type.
2555
 *
2556
 * @return array
2557
 *   An array of field names that match the type $field_type.
2558
 */
2559
function _file_entity_get_fields_by_type($field_type) {
2560
  $return = array();
2561
  if (function_exists('field_info_field_map')) {
2562
    foreach (field_info_field_map() as $field_name => $field) {
2563
      if ($field['type'] == $field_type) {
2564
        $return[$field_name] = $field_name;
2565
      }
2566
    }
2567
  }
2568
  else {
2569
    foreach (field_info_fields() as $field_name => $field) {
2570
      if ($field['type'] == $field_type) {
2571
        $return[$field_name] = $field_name;
2572
      }
2573
    }
2574
  }
2575
  return $return;
2576
}
2577

    
2578
/**
2579
 * Implements hook_field_attach_load().
2580
 */
2581
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2582
  // Loop over all the entities looking for entities with attached images.
2583
  foreach ($entities as $entity) {
2584
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2585
    // Examine every image field instance attached to this entity's bundle.
2586
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2587
    foreach ($instances as $field_name => $instance) {
2588
      if (!empty($entity->{$field_name})) {
2589
        foreach ($entity->{$field_name} as $langcode => $items) {
2590
          foreach ($items as $delta => $item) {
2591
            // If alt and title text is not specified, fall back to alt and
2592
            // title text on the file.
2593
            if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
2594
              $file = file_load($item['fid']);
2595
              foreach (array('alt', 'title') as $key) {
2596
                if (empty($item[$key]) && !empty($file->{$key})) {
2597
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2598
                }
2599
              }
2600
            }
2601
          }
2602
        }
2603
      }
2604
    }
2605
  }
2606
}
2607

    
2608
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2609
  $wrappers = array();
2610
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2611
    if (empty($wrapper['private'])) {
2612
      $wrappers['public'][$key] = $wrapper['name'];
2613
    }
2614
    else {
2615
      $wrappers['private'][$key] = $wrapper['name'];
2616
    }
2617
  }
2618
  return $wrappers;
2619
}
2620

    
2621
/**
2622
 * Implements hook_features_pipe_alter() for the file type component.
2623
 */
2624
function file_entity_features_pipe_file_type_alter(&$pipe, $data, $export) {
2625
  foreach ($data as $file_type) {
2626
    $pipe['variable'][] = "pathauto_file_{$file_type}_pattern";
2627
  }
2628
}
2629