Projet

Général

Profil

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

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

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
        'controls_list' => array(
898
          'download' => 'download',
899
          'remote_playback' => 'remote_playback',
900
        ),
901
        'autoplay' => FALSE,
902
        'loop' => FALSE,
903
        'preload' => NULL,
904
      ),
905
      'file' => 'file_entity.theme.inc',
906
    ),
907
    'file_entity_file_video' => array(
908
      'variables' => array(
909
        'files' => array(),
910
        'controls' => TRUE,
911
        'controls_list' => array(
912
          'fullscreen' => 'fullscreen',
913
          'download' => 'download',
914
          'remote_playback' => 'remote_playback',
915
        ),
916
        'autoplay' => FALSE,
917
        'loop' => FALSE,
918
        'muted' => FALSE,
919
        'width' => NULL,
920
        'height' => NULL,
921
        'preload' => NULL,
922
      ),
923
      'file' => 'file_entity.theme.inc',
924
    ),
925
  );
926
}
927

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

    
960
  // Search integration is provided by file_entity.module, so search-related
961
  // view modes for files are defined here and not in search.module.
962
  if (module_exists('search')) {
963
    $entity_info['file']['view modes']['search_index'] = array(
964
      'label' => t('Search index'),
965
      'custom settings' => FALSE,
966
    );
967
    $entity_info['file']['view modes']['search_result'] = array(
968
      'label' => t('Search result'),
969
      'custom settings' => FALSE,
970
    );
971
  }
972

    
973
  foreach (file_type_get_enabled_types() as $type) {
974
    $entity_info['file']['bundles'][$type->type] = array(
975
      'label' => $type->label,
976
      'admin' => array(
977
        'path' => 'admin/structure/file-types/manage/%file_type',
978
        'real path' => 'admin/structure/file-types/manage/' . $type->type,
979
        'bundle argument' => 4,
980
        'access arguments' => array('administer file types'),
981
      ),
982
    );
983
  }
984

    
985
  // Enable Metatag support.
986
  $entity_info['file']['metatags'] = TRUE;
987

    
988
  // Ensure some of the Entity API callbacks are supported.
989
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
990
  $entity_info['file']['view callback'] = 'file_entity_metadata_view_file';
991
  $entity_info['file']['form callback'] = 'file_entity_metadata_form_file';
992
  $entity_info['file']['access callback'] = 'file_entity_access';
993

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

    
1023
/**
1024
 * Implements hook_entity_property_info().
1025
 */
1026
function file_entity_entity_property_info() {
1027
  $info['file']['properties']['type'] = array(
1028
    'label' => t('File type'),
1029
    'type' => 'token',
1030
    'description' => t('The type of the file.'),
1031
    'setter callback' => 'entity_property_verbatim_set',
1032
    'setter permission' => 'administer files',
1033
    'options list' => 'file_entity_type_get_names',
1034
    'required' => TRUE,
1035
    'schema field' => 'type',
1036
  );
1037

    
1038
  return $info;
1039
}
1040

    
1041
/**
1042
 * Implements hook_field_display_ENTITY_TYPE_alter().
1043
 */
1044
function file_entity_field_display_file_alter(&$display, $context) {
1045
  // Hide field labels in search index.
1046
  if ($context['view_mode'] == 'search_index') {
1047
    $display['label'] = 'hidden';
1048
  }
1049
}
1050

    
1051
/**
1052
 * URI callback for file entities.
1053
 */
1054
function file_entity_uri($file) {
1055
  $uri['path'] = 'file/' . $file->fid;
1056
  return $uri;
1057
}
1058

    
1059
/**
1060
 * Entity API callback to view files.
1061
 */
1062
function file_entity_metadata_view_file($entities, $view_mode = 'full', $langcode = NULL) {
1063
  $result = file_view_multiple($entities, $view_mode, 0, $langcode);
1064
  // Make sure to key the result with 'file' instead of 'files'.
1065
  return array('file' => reset($result));
1066
}
1067

    
1068
/**
1069
 * Entity API callback to get the form of a file entity.
1070
 */
1071
function file_entity_metadata_form_file($file) {
1072
  // Pre-populate the form-state with the right form include.
1073
  $form_state['build_info']['args'] = array($file);
1074
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
1075
  return drupal_build_form('file_entity_edit', $form_state);
1076
}
1077

    
1078
/**
1079
 * Implements hook_ctools_plugin_directory().
1080
 */
1081
function file_entity_ctools_plugin_directory($module, $plugin) {
1082
  if (in_array($module, array('panelizer', 'ctools', 'page_manager'))) {
1083
    return 'plugins/' . $plugin;
1084
  }
1085
}
1086

    
1087
/**
1088
 * Implements hook_field_extra_fields().
1089
 *
1090
 * Adds 'file' as an extra field, so that its display and form component can be
1091
 * weighted relative to the fields that are added to file entity bundles.
1092
 */
1093
function file_entity_field_extra_fields() {
1094
  $info = array();
1095

    
1096
  if ($file_type_names = file_entity_type_get_names()) {
1097
    foreach ($file_type_names as $type => $name) {
1098
      $info['file'][$type]['form']['filename'] = array(
1099
        'label' => t('File name'),
1100
        'description' => t('File name'),
1101
        'weight' => -10,
1102
      );
1103
      $info['file'][$type]['form']['preview'] = array(
1104
        'label' => t('File'),
1105
        'description' => t('File preview'),
1106
        'weight' => -5,
1107
      );
1108
      $info['file'][$type]['display']['file'] = array(
1109
        'label' => t('File'),
1110
        'description' => t('File display'),
1111
        'weight' => 0,
1112
      );
1113
    }
1114
  }
1115

    
1116
  return $info;
1117
}
1118

    
1119
/**
1120
 * Implements hook_file_formatter_info().
1121
 */
1122
function file_entity_file_formatter_info() {
1123
  $formatters = array();
1124

    
1125
  // Allow file field formatters to be reused for displaying the file entity's
1126
  // file pseudo-field.
1127
  foreach (field_info_formatter_types() as $key => $formatter) {
1128
    if (array_intersect($formatter['field types'], array('file', 'image'))) {
1129
      $key = 'file_field_' . $key;
1130
      $formatters[$key] = array(
1131
        'label' => $formatter['label'],
1132
        'description' => !empty($formatter['description']) ? $formatter['description'] : '',
1133
        'view callback' => 'file_entity_file_formatter_file_field_view',
1134
      );
1135
      if (!empty($formatter['settings'])) {
1136
        $formatters[$key] += array(
1137
          'default settings' => $formatter['settings'],
1138
          'settings callback' => 'file_entity_file_formatter_file_field_settings',
1139
        );
1140
      }
1141
      if (!empty($formatter['file formatter'])) {
1142
        $formatters[$key] += $formatter['file formatter'];
1143
      }
1144
    }
1145
  }
1146

    
1147
  // Add a simple file formatter for displaying an image in a chosen style.
1148
  if (module_exists('image')) {
1149
    $formatters['file_image'] = array(
1150
      'label' => t('Image'),
1151
      'default settings' => array(
1152
        'image_style' => '',
1153
        'alt' => '[file:field-file-image-alt-text]',
1154
        'title' => '[file:field-file-image-title-text]'
1155
      ),
1156
      'view callback' => 'file_entity_file_formatter_file_image_view',
1157
      'settings callback' => 'file_entity_file_formatter_file_image_settings',
1158
      'hidden' => TRUE,
1159
      'mime types' => array('image/*'),
1160
    );
1161
  }
1162

    
1163
  return $formatters;
1164
}
1165

    
1166
/**
1167
 * Implements hook_file_formatter_FORMATTER_view().
1168
 *
1169
 * This function provides a bridge to the field formatter API, so that file
1170
 * field formatters can be reused for displaying the file entity's file
1171
 * pseudo-field.
1172
 */
1173
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
1174
  if (strpos($display['type'], 'file_field_') === 0) {
1175
    $field_formatter_type = substr($display['type'], strlen('file_field_'));
1176
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1177
    if (isset($field_formatter_info['module'])) {
1178
      // Set $display['type'] to what hook_field_formatter_*() expects.
1179
      $display['type'] = $field_formatter_type;
1180

    
1181
      // Allow any attribute overrides (e.g. from the Media module) to be
1182
      // respected.
1183
      $item = (array) $file;
1184
      if (!empty($file->override['attributes'])) {
1185
        $item = array_merge($item, $file->override['attributes']);
1186
      }
1187

    
1188
      // Set $items to what file field formatters expect. See file_field_load(),
1189
      // and note that, here, $file is already a fully loaded entity.
1190
      $items = array($item);
1191

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

    
1219
/**
1220
 * Implements hook_file_formatter_FORMATTER_settings().
1221
 *
1222
 * This function provides a bridge to the field formatter API, so that file
1223
 * field formatters can be reused for displaying the file entity's file
1224
 * pseudo-field.
1225
 */
1226
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
1227
  if (strpos($formatter_type, 'file_field_') === 0) {
1228
    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
1229
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1230

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

    
1252
/**
1253
 * Replace file entity title text.
1254
 *
1255
 * @param $file
1256
 *   The file entity.
1257
 * @param $replace_options
1258
 *   (Optional) Options to pass to token_replace().
1259
 * @param $title
1260
 *   (Optional) The title text to use.
1261
 *
1262
 * @return string
1263
 *   Returns the replaced title text.
1264
 */
1265
function file_entity_replace_title($file, $replace_options = array(), $title = NULL) {
1266
  $replace_options += array(
1267
    'clear' => TRUE,
1268
    'sanitize' => FALSE,
1269
  );
1270

    
1271
  $title_default = '[file:field_file_image_title_text]';
1272
  if (!isset($title)) {
1273
    $title = variable_get('file_entity_title', $title_default);
1274
  }
1275
  // If the defaults are not changed then inlining replacement is much faster
1276
  // than dealing with the token system.
1277
  if ($title === $title_default) {
1278
    $title_items = field_get_items('file', $file, 'field_file_image_title_text');
1279
    return $title_items ? $title_items[0]['value'] : '';
1280
  }
1281
  elseif (!empty($title)) {
1282
    $token_replaced = token_replace($title, array('file' => $file), $replace_options);
1283
    return decode_entities($token_replaced); // Filter out possible XSS.
1284
  }
1285

    
1286
  return '';
1287
}
1288

    
1289
/**
1290
 * Replace file entity alt.
1291
 *
1292
 * @param $file
1293
 *   The file entity.
1294
 * @param $replace_options
1295
 *   (Optional) Options to pass to token_replace().
1296
 * @param $alt
1297
 *   (Optional) The alt text to use.
1298
 *
1299
 * @return string
1300
 *   Returns the replaced alt text.
1301
 */
1302
function file_entity_replace_alt($file, $replace_options = array(), $alt = NULL) {
1303
  $replace_options += array(
1304
    'clear' => TRUE,
1305
    'sanitize' => FALSE,
1306
  );
1307

    
1308
  $alt_default = '[file:field_file_image_alt_text]';
1309

    
1310
  if (!isset($alt)) {
1311
    $alt = variable_get('file_entity_alt', $alt_default);
1312
  }
1313

    
1314
  // If the defaults are not changed then inlining replacement is much faster
1315
  // than dealing with the token system.
1316
  if ($alt === $alt_default) {
1317
    $alt_items = field_get_items('file', $file, 'field_file_image_alt_text');
1318
    return $alt_items ? $alt_items[0]['value'] : '';
1319
  }
1320
  elseif (!empty($alt)) {
1321
    $token_replaced = token_replace($alt, array('file' => $file), $replace_options);
1322
    return decode_entities($token_replaced); // Filter out possible XSS.
1323
  }
1324
}
1325

    
1326

    
1327
/**
1328
 * Implements hook_file_formatter_FORMATTER_view().
1329
 *
1330
 * Returns a drupal_render() array to display an image of the chosen style.
1331
 *
1332
 * This formatter is only capable of displaying local images. If the passed in
1333
 * file is either not local or not an image, nothing is returned, so that
1334
 * file_view_file() can try another formatter.
1335
 */
1336
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
1337
  // Prevent PHP notices when trying to read empty files.
1338
  // @see http://drupal.org/node/681042
1339
  if (!$file->filesize) {
1340
    return;
1341
  }
1342

    
1343
  // Do not bother proceeding if this file does not have an image mime type.
1344
  if (file_entity_file_get_mimetype_type($file) != 'image') {
1345
    return;
1346
  }
1347

    
1348
  if (file_entity_file_is_readable($file)) {
1349
    // We don't sanitize here.
1350
    // @see http://drupal.org/node/1553094#comment-6257382
1351
    // Theme function will take care of escaping.
1352
    if (!isset($file->metadata)) {
1353
      $file->metadata = array();
1354
    }
1355
    $file->metadata += array('width' => NULL, 'height' => NULL);
1356
    $replace_options = array(
1357
      'clear' => TRUE,
1358
      'sanitize' => FALSE,
1359
    );
1360
    if (!empty($display['settings']['image_style'])) {
1361
      $element = array(
1362
        '#theme' => 'image_style',
1363
        '#style_name' => $display['settings']['image_style'],
1364
        '#path' => $file->uri,
1365
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1366
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1367
        '#alt' => file_entity_replace_alt($file, $replace_options, $display['settings']['alt']),
1368
        '#title' => file_entity_replace_title($file, $replace_options, $display['settings']['title']),
1369
      );
1370
    }
1371
    else {
1372
      $element = array(
1373
        '#theme' => 'image',
1374
        '#path' => $file->uri,
1375
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1376
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1377
        '#alt' => file_entity_replace_alt($file, $replace_options, $display['settings']['alt']),
1378
        '#title' => file_entity_replace_title($file, $replace_options, $display['settings']['title']),
1379
      );
1380
    }
1381
    return $element;
1382
  }
1383
}
1384

    
1385
/**
1386
 * Check if a file entity is readable or not.
1387
 *
1388
 * @param object $file
1389
 *   A file entity object from file_load().
1390
 *
1391
 * @return boolean
1392
 *   TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
1393
 */
1394
function file_entity_file_is_readable($file) {
1395
  $scheme = file_uri_scheme($file->uri);
1396
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
1397
  return !empty($wrappers[$scheme]);
1398
}
1399

    
1400
/**
1401
 * Implements hook_file_formatter_FORMATTER_settings().
1402
 *
1403
 * Returns form elements for configuring the 'file_image' formatter.
1404
 */
1405
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
1406
  $element = array();
1407
  $element['image_style'] = array(
1408
    '#title' => t('Image style'),
1409
    '#type' => 'select',
1410
    '#options' => image_style_options(FALSE),
1411
    '#default_value' => $settings['image_style'],
1412
    '#empty_option' => t('None (original image)'),
1413
  );
1414

    
1415
  // For image files we allow the alt attribute (required in HTML).
1416
  $element['alt'] = array(
1417
    '#title' => t('Alt attribute'),
1418
    '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
1419
    '#type' => 'textfield',
1420
    '#default_value' => $settings['alt'],
1421
  );
1422

    
1423
  // Allow the setting of the title attribute.
1424
  $element['title'] = array(
1425
    '#title' => t('Title attribute'),
1426
    '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
1427
    '#type' => 'textfield',
1428
    '#default_value' => $settings['title'],
1429
  );
1430

    
1431
  if (module_exists('token')) {
1432
    $element['alt']['#description'] .= t('This field supports tokens.');
1433
    $element['title']['#description'] .= t('This field supports tokens.');
1434
    $element['tokens'] = array(
1435
      '#theme' => 'token_tree',
1436
      '#token_types' => array('file'),
1437
      '#dialog' => TRUE,
1438
    );
1439
  }
1440

    
1441
  return $element;
1442
}
1443

    
1444
/**
1445
 * Menu access callback for the 'view mode file display settings' pages.
1446
 *
1447
 * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
1448
 * be enabled.
1449
 */
1450
function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
1451
  // Deny access if the view mode isn't configured to use custom display
1452
  // settings.
1453
  $view_mode_settings = field_view_mode_settings('file', $file_type->type);
1454
  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
1455
  if (!$visibility) {
1456
    return FALSE;
1457
  }
1458

    
1459
  // Otherwise, continue to an $access_callback check.
1460
  $args = array_slice(func_get_args(), 3);
1461
  $callback = empty($access_callback) ? 0 : trim($access_callback);
1462
  if (is_numeric($callback)) {
1463
    return (bool) $callback;
1464
  }
1465
  elseif (function_exists($access_callback)) {
1466
    return call_user_func_array($access_callback, $args);
1467
  }
1468
}
1469

    
1470
/**
1471
 * Implements hook_modules_enabled().
1472
 */
1473
function file_entity_modules_enabled($modules) {
1474
  file_info_cache_clear();
1475
}
1476

    
1477
/**
1478
 * Implements hook_modules_disabled().
1479
 */
1480
function file_entity_modules_disabled($modules) {
1481
  file_info_cache_clear();
1482
}
1483

    
1484
/**
1485
 * Implements hook_views_api().
1486
 */
1487
function file_entity_views_api() {
1488
  return array(
1489
    'api' => 3,
1490
  );
1491
}
1492

    
1493
/**
1494
 * Returns whether the current page is the full page view of the passed-in file.
1495
 *
1496
 * @param $file
1497
 *   A file object.
1498
 */
1499
function file_entity_is_page($file) {
1500
  $page_file = menu_get_object('file', 1);
1501
  return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
1502
}
1503

    
1504
/**
1505
 * Process variables for file_entity.tpl.php
1506
 *
1507
 * The $variables array contains the following arguments:
1508
 * - $file
1509
 * - $view_mode
1510
 *
1511
 * @see file_entity.tpl.php
1512
 */
1513
function template_preprocess_file_entity(&$variables) {
1514
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
1515
  $variables['file'] = $variables['elements']['#file'];
1516
  $file = $variables['file'];
1517

    
1518
  $variables['id'] = drupal_html_id('file-'. $file->fid);
1519
  $variables['date']      = format_date($file->timestamp);
1520
  $account = user_load($file->uid);
1521
  $variables['name']      = theme('username', array('account' => $account));
1522

    
1523
  $uri = entity_uri('file', $file);
1524
  $variables['file_url']  = url($uri['path'], $uri['options']);
1525
  $label = entity_label('file', $file);
1526
  $variables['label']     = check_plain($label);
1527
  $variables['page']      = $view_mode == 'full' && file_entity_is_page($file);
1528

    
1529
  // Hide the file name from being displayed until we can figure out a better
1530
  // way to control this. We cannot simply not output the title since
1531
  // contextual links require $title_suffix to be output in the template.
1532
  // @see http://drupal.org/node/1245266
1533
  if (!$variables['page']) {
1534
    $variables['title_attributes_array']['class'][] = 'element-invisible';
1535
  }
1536

    
1537
  // Flatten the file object's member fields.
1538
  $variables = array_merge((array) $file, $variables);
1539

    
1540
  // Helpful $content variable for templates.
1541
  $variables += array('content' => array());
1542
  foreach (element_children($variables['elements']) as $key) {
1543
    $variables['content'][$key] = $variables['elements'][$key];
1544
  }
1545

    
1546
  // Make the field variables available with the appropriate language.
1547
  field_attach_preprocess('file', $file, $variables['content'], $variables);
1548

    
1549
  // Attach the file object to the content element.
1550
  $variables['content']['file']['#file'] = $file;
1551

    
1552
  // Display post information only on certain file types.
1553
  if (variable_get('file_submitted_' . $file->type, FALSE)) {
1554
    $variables['display_submitted'] = TRUE;
1555
    $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1556
    $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
1557
  }
1558
  else {
1559
    $variables['display_submitted'] = FALSE;
1560
    $variables['submitted'] = '';
1561
    $variables['user_picture'] = '';
1562
  }
1563

    
1564
  // Gather file classes.
1565
  $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
1566
  $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
1567
  if ($file->status != FILE_STATUS_PERMANENT) {
1568
    $variables['classes_array'][] = 'file-temporary';
1569
  }
1570

    
1571
  // Change the 'file-entity' class into 'file'
1572
  if ($variables['classes_array'][0] == 'file-entity') {
1573
    $variables['classes_array'][0] = 'file';
1574
  }
1575

    
1576
  // Clean up name so there are no underscores.
1577
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
1578
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
1579
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
1580
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
1581
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
1582
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
1583
}
1584

    
1585
/**
1586
 * Returns the file type name of the passed file or file type string.
1587
 *
1588
 * @param $file
1589
 *   A file object or string that indicates the file type to return.
1590
 *
1591
 * @return
1592
 *   The file type name or FALSE if the file type is not found.
1593
 */
1594
function file_entity_type_get_name($file) {
1595
  $type = is_object($file) ? $file->type : $file;
1596
  $info = entity_get_info('file');
1597
  return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
1598
}
1599

    
1600
/**
1601
 * Returns a list of available file type names.
1602
 *
1603
 * @return
1604
 *   An array of file type names, keyed by the type.
1605
 */
1606
function file_entity_type_get_names() {
1607
  $names = &drupal_static(__FUNCTION__);
1608

    
1609
  if (!isset($names)) {
1610
    $names = array();
1611
    $info = entity_get_info('file');
1612
    foreach ($info['bundles'] as $bundle => $bundle_info) {
1613
      $names[$bundle] = $bundle_info['label'];
1614
    }
1615
  }
1616

    
1617
  return $names;
1618
}
1619

    
1620
/**
1621
 * Return an array of available view modes for file entities.
1622
 */
1623
function file_entity_view_mode_labels() {
1624
  $labels = &drupal_static(__FUNCTION__);
1625

    
1626
  if (!isset($options)) {
1627
    $entity_info = entity_get_info('file');
1628
    $labels = array('default' => t('Default'));
1629
    foreach ($entity_info['view modes'] as $machine_name => $mode) {
1630
      $labels[$machine_name] = $mode['label'];
1631
    }
1632
  }
1633

    
1634
  return $labels;
1635
}
1636

    
1637
/**
1638
 * Return the label for a specific file entity view mode.
1639
 */
1640
function file_entity_view_mode_label($view_mode, $default = FALSE) {
1641
  $labels = file_entity_view_mode_labels();
1642
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
1643
}
1644

    
1645
/**
1646
 * Helper function to get a list of hidden stream wrappers.
1647
 *
1648
 * This is used in several places to filter queries for media so that files in
1649
 * temporary:// don't show up.
1650
 */
1651
function file_entity_get_hidden_stream_wrappers() {
1652
  return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
1653
}
1654

    
1655
/**
1656
 * Return a specific stream wrapper's registry information.
1657
 *
1658
 * @param $scheme
1659
 *   A URI scheme, a stream is referenced as "scheme://target".
1660
 *
1661
 * @see file_get_stream_wrappers()
1662
 */
1663
function file_entity_get_stream_wrapper($scheme) {
1664
  $wrappers = file_get_stream_wrappers();
1665
  return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
1666
}
1667

    
1668
/**
1669
 * Implements hook_stream_wrappers_alter().
1670
 */
1671
function file_entity_stream_wrappers_alter(&$wrappers) {
1672
  if (isset($wrappers['private'])) {
1673
    $wrappers['private']['private'] = TRUE;
1674
  }
1675
  if (isset($wrappers['temporary'])) {
1676
    $wrappers['temporary']['private'] = TRUE;
1677
  }
1678
}
1679

    
1680
/**
1681
 * Implements hook_ctools_plugin_api().
1682
 */
1683
function file_entity_ctools_plugin_api($module, $api) {
1684
  if (($module == 'file_entity' && $api == 'file_type') || ($module == 'page_manager' && $api == 'pages_default') || $module == 'panelizer') {
1685
    return array('version' => 1);
1686
  }
1687
  if ($module == 'file_entity' && $api == 'file_default_displays') {
1688
    return array('version' => 1);
1689
  }
1690
}
1691

    
1692
/**
1693
 * @defgroup file_entity_access File access rights
1694
 * @{
1695
 * The file access system determines who can do what to which files.
1696
 *
1697
 * In determining access rights for a file, file_entity_access() first checks
1698
 * whether the user has the "bypass file access" permission. Such users have
1699
 * unrestricted access to all files. user 1 will always pass this check.
1700
 *
1701
 * Next, all implementations of hook_file_entity_access() will be called. Each
1702
 * implementation may explicitly allow, explicitly deny, or ignore the access
1703
 * request. If at least one module says to deny the request, it will be rejected.
1704
 * If no modules deny the request and at least one says to allow it, the request
1705
 * will be permitted.
1706
 *
1707
 * There is no access grant system for files.
1708
 *
1709
 * In file listings, the process above is followed except that
1710
 * hook_file_entity_access() is not called on each file for performance reasons
1711
 * and for proper functioning of the pager system. When adding a filelisting to
1712
 * your module, be sure to use a dynamic query created by db_select()
1713
 * and add a tag of "file_entity_access". This will allow modules dealing
1714
 * with file access to ensure only files to which the user has access
1715
 * are retrieved, through the use of hook_query_TAG_alter().
1716
 *
1717
 * Note: Even a single module returning FILE_ENTITY_ACCESS_DENY from
1718
 * hook_file_entity_access() will block access to the file. Therefore,
1719
 * implementers should take care to not deny access unless they really intend to.
1720
 * Unless a module wishes to actively deny access it should return
1721
 * FILE_ENTITY_ACCESS_IGNORE (or simply return nothing)
1722
 * to allow other modules to control access.
1723
 *
1724
 * Stream wrappers that are considered private should implement a 'private'
1725
 * flag equal to TRUE in hook_stream_wrappers().
1726
 */
1727

    
1728
/**
1729
 * Determine if a user may perform the given operation on the specified file.
1730
 *
1731
 * @param $op
1732
 *   The operation to be performed on the file. Possible values are:
1733
 *   - "view"
1734
 *   - "download"
1735
 *   - "update"
1736
 *   - "delete"
1737
 *   - "create"
1738
 * @param $file
1739
 *   The file object on which the operation is to be performed, or file type
1740
 *   (e.g. 'image') for "create" operation.
1741
 * @param $account
1742
 *   Optional, a user object representing the user for whom the operation is to
1743
 *   be performed. Determines access for a user other than the current user.
1744
 *
1745
 * @return
1746
 *   TRUE if the operation may be performed, FALSE otherwise.
1747
 */
1748
function file_entity_access($op, $file = NULL, $account = NULL) {
1749
  $rights = &drupal_static(__FUNCTION__, array());
1750

    
1751
  if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
1752
    // If there was no file to check against, and the $op was not one of the
1753
    // supported ones, we return access denied.
1754
    return FALSE;
1755
  }
1756

    
1757
  // If no user object is supplied, the access check is for the current user.
1758
  if (empty($account)) {
1759
    $account = $GLOBALS['user'];
1760
  }
1761

    
1762
  // $file may be either an object or a file type. Since file types cannot be
1763
  // an integer, use either fid or type as the static cache id.
1764
  $cache_id = NULL;
1765
  if (is_object($file) && !empty($file->fid)) {
1766
    $cache_id = $file->fid;
1767
  }
1768
  elseif ($op == 'create' && is_string($file)) {
1769
    $cache_id = $file;
1770
  }
1771
  elseif ($op == 'create' && is_object($file) && !empty($file->type)) {
1772
    $cache_id = $file->type;
1773
  }
1774
  else {
1775
    $cache_id = drupal_hash_base64(serialize($file));
1776
  }
1777

    
1778
  // If we've already checked access for this file, user and op, return from
1779
  // cache.
1780
  if (isset($rights[$account->uid][$cache_id][$op])) {
1781
    return $rights[$account->uid][$cache_id][$op];
1782
  }
1783

    
1784
  if (user_access('bypass file access', $account)) {
1785
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1786
  }
1787

    
1788
  // We grant access to the file if both of the following conditions are met:
1789
  // - No modules say to deny access.
1790
  // - At least one module says to grant access.
1791
  $access = module_invoke_all('file_entity_access', $op, $file, $account);
1792
  if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
1793
    return $rights[$account->uid][$cache_id][$op] = FALSE;
1794
  }
1795
  elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
1796
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1797
  }
1798

    
1799

    
1800
  // Fall back to default behaviors on view.
1801
  if ($op == 'view' && is_object($file)) {
1802
    $scheme = file_uri_scheme($file->uri);
1803
    $wrapper = file_entity_get_stream_wrapper($scheme);
1804

    
1805
    if (!empty($wrapper['private'])) {
1806
      // For private files, users can view private files if the
1807
      // user has the 'view private files' permission.
1808
      if (user_access('view private files', $account)) {
1809
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1810
      }
1811

    
1812
      // For private files, users can view their own private files if the
1813
      // user is not anonymous, and has the 'view own private files' permission.
1814
      if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
1815
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1816
      }
1817
    }
1818
    elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
1819
      // For non-private files, allow to see if user owns the file.
1820
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1821
    }
1822
    elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
1823
      // For non-private files, users can view if they have the 'view files'
1824
      // permission.
1825
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1826
    }
1827
  }
1828

    
1829
  return FALSE;
1830
}
1831

    
1832
/**
1833
 * Implements hook_file_entity_access().
1834
 */
1835
function file_entity_file_entity_access($op, $file, $account) {
1836
  // If the op is "create," all that's needed is to check the create permission.
1837
  if ($op == 'create') {
1838
    if (user_access('create files')) {
1839
      return FILE_ENTITY_ACCESS_ALLOW;
1840
    }
1841
  }
1842
  // If the file URI is invalid, deny access.
1843
  if (is_object($file) && isset($file->uri) && !file_valid_uri($file->uri)) {
1844
    if(isset($file->is_new) && $file->is_new == true && user_access('create files')) {
1845
      return FILE_ENTITY_ACCESS_ALLOW;
1846
    }
1847
    return FILE_ENTITY_ACCESS_DENY;
1848
  }
1849

    
1850
  if (!empty($file)) {
1851
    $type = is_string($file) ? $file : $file->type;
1852

    
1853
    if (in_array($type, file_entity_permissions_get_configured_types())) {
1854
      if ($op == 'download') {
1855
        if (user_access('download any ' . $type . ' files', $account) || is_object($file) && user_access('download own ' . $type . ' files', $account) && ($account->uid == $file->uid)) {
1856
          return FILE_ENTITY_ACCESS_ALLOW;
1857
        }
1858
      }
1859

    
1860
      if ($op == 'update') {
1861
        if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1862
          return FILE_ENTITY_ACCESS_ALLOW;
1863
        }
1864
      }
1865

    
1866
      if ($op == 'delete') {
1867
        if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1868
          return FILE_ENTITY_ACCESS_ALLOW;
1869
        }
1870
      }
1871
    }
1872
  }
1873

    
1874
  return FILE_ENTITY_ACCESS_IGNORE;
1875
}
1876

    
1877
/**
1878
 * Implements hook_query_TAG_alter().
1879
 *
1880
 * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
1881
 * file access checks for the user account given by the 'account' meta-data (or
1882
 * global $user if not provided).
1883
 */
1884
function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
1885
  _file_entity_query_file_entity_access_alter($query, 'file');
1886
}
1887

    
1888
/**
1889
 * Implements hook_query_TAG_alter().
1890
 *
1891
 * This function implements the same functionality as
1892
 * file_entity_query_file_access_alter() for the SQL field storage engine. File
1893
 * access conditions are added for field values belonging to files only.
1894
 */
1895
function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
1896
  //_file_entity_query_file_entity_access_alter($query, 'entity');
1897
}
1898

    
1899
/**
1900
 * Helper for file entity access functions.
1901
 *
1902
 * @param $query
1903
 *   The query to add conditions to.
1904
 * @param $type
1905
 *   Either 'file' or 'entity' depending on what sort of query it is. See
1906
 *   file_entity_query_file_entity_access_alter() and
1907
 *   file_entity_query_entity_field_access_alter() for more.
1908
 */
1909
function _file_entity_query_file_entity_access_alter($query, $type) {
1910
  global $user;
1911

    
1912
  // Read meta-data from query, if provided.
1913
  if (!$account = $query->getMetaData('account')) {
1914
    $account = $user;
1915
  }
1916

    
1917
  // If $account can bypass file access, we don't need to alter the query.
1918
  if (user_access('bypass file access', $account)) {
1919
    return;
1920
  }
1921

    
1922
  // A conflict with og_query_og_membership_alter() causes a fatal error
1923
  // if both hooks alter the query.
1924
  if (module_exists('og') && $query->hasTag('og_membership')) {
1925
    foreach($query->getMetaData('entity_field_query')->fields as $field) {
1926
      if (og_is_group_audience_field($field['field_name'])) {
1927
        return;
1928
      }
1929
    }
1930
  }
1931

    
1932
  $tables = $query->getTables();
1933
  $base_table = $query->getMetaData('base_table');
1934
  // If no base table is specified explicitly, search for one.
1935
  if (!$base_table) {
1936
    $fallback = '';
1937
    foreach ($tables as $alias => $table_info) {
1938
      if (!($table_info instanceof SelectQueryInterface || $table_info['table'] instanceof SelectQueryInterface)) {
1939
        $table = $table_info['table'];
1940
        // If the file_managed table is in the query, it wins immediately.
1941
        if ($table == 'file_managed') {
1942
          $base_table = $table;
1943
          break;
1944
        }
1945
        // Check whether the table has a foreign key to file_managed.fid. If it
1946
        // does, do not run this check again as we found a base table and only
1947
        // file_managed can triumph that.
1948
        if (!$base_table) {
1949
          // The schema is cached.
1950
          $schema = drupal_get_schema($table);
1951
          if (isset($schema['fields']['fid'])) {
1952
            if (isset($schema['foreign keys'])) {
1953
              foreach ($schema['foreign keys'] as $relation) {
1954
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
1955
                  $base_table = $table;
1956
                }
1957
              }
1958
            }
1959
            else {
1960
              // At least it's a fid. A table with a field called fid is very
1961
              // very likely to be a file_managed.fid in a file access query.
1962
              $fallback = $table;
1963
            }
1964
          }
1965
        }
1966
      }
1967
    }
1968
    // If there is nothing else, use the fallback.
1969
    if (!$base_table) {
1970
      if ($fallback) {
1971
        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);
1972
        $base_table = $fallback;
1973
      }
1974
      else {
1975
        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.'));
1976
      }
1977
    }
1978
  }
1979

    
1980
  if ($type == 'entity') {
1981
    // The original query looked something like:
1982
    // @code
1983
    //  SELECT fid FROM sometable s
1984
    //  WHERE ($file_access_conditions)
1985
    // @endcode
1986
    //
1987
    // Our query will look like:
1988
    // @code
1989
    //  SELECT entity_type, entity_id
1990
    //  FROM field_data_something s
1991
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1992
    // @endcode
1993
    //
1994
    // So instead of directly adding to the query object, we need to collect
1995
    // all of the file access conditions in a separate db_and() object and
1996
    // then add it to the query at the end.
1997
    $file_conditions = db_and();
1998
  }
1999
  foreach ($tables as $falias => $tableinfo) {
2000
    $table = $tableinfo['table'];
2001
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
2002
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
2003
      $subquery_conditions = db_or();
2004

    
2005
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
2006
      if (!empty($wrappers['public'])) {
2007
        if (user_access('view files', $account)) {
2008
          foreach (array_keys($wrappers['public']) as $wrapper) {
2009
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
2010
          }
2011
        }
2012
        elseif (user_access('view own files', $account)) {
2013
          foreach (array_keys($wrappers['public']) as $wrapper) {
2014
            $subquery_conditions->condition(db_and()
2015
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
2016
              ->condition('fm_access.uid', $account->uid)
2017
            );
2018
          }
2019
        }
2020
      }
2021
      if (!empty($wrappers['private'])) {
2022
        if (user_access('view private files', $account)) {
2023
          foreach (array_keys($wrappers['private']) as $wrapper) {
2024
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
2025
          }
2026
        }
2027
        elseif (user_access('view own private files', $account)) {
2028
          foreach (array_keys($wrappers['private']) as $wrapper) {
2029
            $subquery_conditions->condition(db_and()
2030
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
2031
              ->condition('fm_access.uid', $account->uid)
2032
            );
2033
          }
2034
        }
2035
      }
2036

    
2037
      if ($subquery_conditions->count()) {
2038
        $subquery->condition($subquery_conditions);
2039

    
2040
        $field = 'fid';
2041
        // Now handle entities.
2042
        if ($type == 'entity') {
2043
          // Set a common alias for entities.
2044
          $base_alias = $falias;
2045
          $field = 'entity_id';
2046
        }
2047
        $subquery->where("$falias.$field = fm_access.fid");
2048

    
2049
        // For an entity query, attach the subquery to entity conditions.
2050
        if ($type == 'entity') {
2051
          $file_conditions->exists($subquery);
2052
        }
2053
        // Otherwise attach it to the node query itself.
2054
        elseif ($table == 'file_managed') {
2055
          // Fix for https://drupal.org/node/2073085
2056
          $db_or = db_or();
2057
          $db_or->exists($subquery);
2058
          $db_or->isNull($falias . '.' . $field);
2059
          $query->condition($db_or);
2060
        }
2061
        else {
2062
          $query->exists($subquery);
2063
        }
2064
      }
2065
    }
2066
  }
2067

    
2068
  if ($type == 'entity' && $file_conditions->count()) {
2069
    // All the file access conditions are only for field values belonging to
2070
    // files.
2071
    $file_conditions->condition("$base_alias.entity_type", 'file');
2072
    $or = db_or();
2073
    $or->condition($file_conditions);
2074
    // If the field value belongs to a non-file entity type then this function
2075
    // does not do anything with it.
2076
    $or->condition("$base_alias.entity_type", 'file', '<>');
2077
    // Add the compiled set of rules to the query.
2078
    $query->condition($or);
2079
  }
2080
}
2081

    
2082
/**
2083
 * Implements hook_file_download().
2084
 */
2085
function file_entity_file_download($uri) {
2086
  // Load the file from the URI.
2087
  $file = file_uri_to_object($uri);
2088

    
2089
  // An existing file wasn't found, so we don't control access.
2090
  // E.g. image derivatives will fall here.
2091
  if (empty($file->fid)) {
2092
    return NULL;
2093
  }
2094

    
2095
  // Allow the user to download the file if they have appropriate permissions.
2096
  if (file_entity_access('view', $file)) {
2097
    return file_get_content_headers($file);
2098
  }
2099

    
2100
  return NULL;
2101
}
2102

    
2103
/**
2104
 * Helper function to generate standard file permission list for a given type.
2105
 *
2106
 * @param $type
2107
 *   The machine-readable name of the file type.
2108
 * @return array
2109
 *   An array of permission names and descriptions.
2110
 */
2111
function file_entity_list_permissions($type) {
2112
  $info = file_type_load($type);
2113

    
2114
  // Build standard list of file permissions for this type.
2115
  $permissions = array(
2116
    "edit own $type files" => array(
2117
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
2118
    ),
2119
    "edit any $type files" => array(
2120
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
2121
    ),
2122
    "delete own $type files" => array(
2123
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
2124
    ),
2125
    "delete any $type files" => array(
2126
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
2127
    ),
2128
    "download own $type files" => array(
2129
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
2130
    ),
2131
    "download any $type files" => array(
2132
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
2133
    ),
2134
  );
2135

    
2136
  return $permissions;
2137
}
2138

    
2139
/**
2140
 * Returns an array of file types that should be managed by permissions.
2141
 *
2142
 * By default, this will include all file types in the system. To exclude a
2143
 * specific file from getting permissions defined for it, set the
2144
 * file_entity_permissions_$type variable to 0. File entity does not provide an
2145
 * interface for doing so, however, contrib modules may exclude their own files
2146
 * in hook_install(). Alternatively, contrib modules may configure all file
2147
 * types at once, or decide to apply some other hook_file_entity_access()
2148
 * implementation to some or all file types.
2149
 *
2150
 * @return
2151
 *   An array of file types managed by this module.
2152
 */
2153
function file_entity_permissions_get_configured_types() {
2154

    
2155
  $configured_types = array();
2156

    
2157
  foreach (file_type_get_enabled_types() as $type => $info) {
2158
    if (variable_get('file_entity_permissions_' . $type, 1)) {
2159
      $configured_types[] = $type;
2160
    }
2161
  }
2162

    
2163
  return $configured_types;
2164
}
2165

    
2166
/**
2167
 * @} End of "defgroup file_entity_access".
2168
 *
2169
 * Implements hook_file_default_types().
2170
 */
2171
function file_entity_file_default_types() {
2172
  $types = array();
2173

    
2174
  // Image.
2175
  $types['image'] = (object) array(
2176
    'api_version' => 1,
2177
    'type' => 'image',
2178
    'label' => t('Image'),
2179
    'description' => t('An <em>Image</em> file is a still visual.'),
2180
    'mimetypes' => array(
2181
      'image/*',
2182
    ),
2183
  );
2184

    
2185
  // Video.
2186
  $types['video'] = (object) array(
2187
    'api_version' => 1,
2188
    'type' => 'video',
2189
    'label' => t('Video'),
2190
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2191
    'mimetypes' => array(
2192
      'video/*',
2193
    ),
2194
  );
2195

    
2196
  // Audio.
2197
  $types['audio'] = (object) array(
2198
    'api_version' => 1,
2199
    'type' => 'audio',
2200
    'label' => t('Audio'),
2201
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2202
    'mimetypes' => array(
2203
      'audio/*',
2204
    ),
2205
  );
2206

    
2207
  // Document.
2208
  $types['document'] = (object) array(
2209
    'api_version' => 1,
2210
    'type' => 'document',
2211
    'label' => t('Document'),
2212
    'description' => t('A <em>Document</em> file is written information.'),
2213
    'mimetypes' => array(
2214
      'text/html',
2215
      'text/plain',
2216
      'application/acad',
2217
      'application/msword',
2218
      'application/vnd.ms-excel',
2219
      'application/pdf',
2220
      'application/vnd.ms-powerpoint',
2221
      'application/vnd.oasis.opendocument.text',
2222
      'application/vnd.oasis.opendocument.spreadsheet',
2223
      'application/vnd.oasis.opendocument.presentation',
2224
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2225
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2226
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2227
      'application/zip',
2228
      'application/x-7z-compressed',
2229
      'application/x-tar',
2230
      'application/gzip',
2231
    ),
2232
  );
2233

    
2234
  return $types;
2235
}
2236

    
2237
/**
2238
 * Implements hook_file_operations().
2239
 */
2240
function file_entity_file_operations() {
2241
  $operations = array(
2242
    'permanent' => array(
2243
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2244
      'callback' => 'file_entity_mass_update',
2245
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2246
    ),
2247
    'temporary' => array(
2248
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2249
      'callback' => 'file_entity_mass_update',
2250
      'callback arguments' => array('updates' => array('status' => 0)),
2251
    ),
2252
    'delete' => array(
2253
      'label' => t('Delete selected files'),
2254
      'callback' => NULL,
2255
    ),
2256
  );
2257
  return $operations;
2258
}
2259

    
2260
/**
2261
 * Clear the field cache for any entities referencing a specific file.
2262
 *
2263
 * @param object $file
2264
 *   A file object.
2265
 */
2266
function file_entity_invalidate_field_caches($file) {
2267
  $entity_types = &drupal_static(__FUNCTION__);
2268

    
2269
  // Gather the list of entity types which support field caching.
2270
  if (!isset($entity_types)) {
2271
    $entity_types = array();
2272
    foreach (entity_get_info() as $entity_type => $entity_info) {
2273
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2274
        $entity_types[] = $entity_type;
2275
      }
2276
    }
2277
  }
2278

    
2279
  // If no entity types support field caching, then there is no work to be done.
2280
  if (empty($entity_types)) {
2281
    return;
2282
  }
2283

    
2284
  $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();
2285
  if (!empty($records)) {
2286
    $cids = array();
2287
    foreach ($records as $record) {
2288
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2289
    }
2290
    cache_clear_all($cids, 'cache_field');
2291
  }
2292
}
2293

    
2294
/**
2295
 * Check if a file entity is considered local or not.
2296
 *
2297
 * @param object $file
2298
 *   A file entity object from file_load().
2299
 *
2300
 * @return
2301
 *   TRUE if the file is using a local stream wrapper, or FALSE otherwise.
2302
 */
2303
function file_entity_file_is_local($file) {
2304
  $scheme = file_uri_scheme($file->uri);
2305
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
2306
  return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
2307
}
2308

    
2309
/**
2310
 * Check if a file entity is considered writeable or not.
2311
 *
2312
 * @param object $file
2313
 *   A file entity object from file_load().
2314
 *
2315
 * @return
2316
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2317
 *   or FALSE otherwise.
2318
 */
2319
function file_entity_file_is_writeable($file) {
2320
  $scheme = file_uri_scheme($file->uri);
2321
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2322
  return !empty($wrappers[$scheme]);
2323
}
2324

    
2325
/**
2326
 * Pre-render callback for adding validation descriptions to file upload fields.
2327
 */
2328
function file_entity_upload_validators_pre_render($element) {
2329
  if (!empty($element['#upload_validators'])) {
2330
    if (!isset($element['#description'])) {
2331
      $element['#description'] = '';
2332
    }
2333
    if ($element['#description'] !== FALSE) {
2334
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2335
    }
2336
  }
2337
  return $element;
2338
}
2339

    
2340
/**
2341
 * @name pathauto_file Pathauto integration for the core file module.
2342
 * @{
2343
 */
2344

    
2345
/**
2346
 * Implements hook_file_insert() on behalf of pathauto.module.
2347
 */
2348
function pathauto_file_insert($file) {
2349
  pathauto_file_update_alias($file, 'insert');
2350
}
2351

    
2352
/**
2353
 * Implements hook_file_update() on behalf of pathauto.module.
2354
 */
2355
function pathauto_file_update($file) {
2356
  pathauto_file_update_alias($file, 'update');
2357
}
2358

    
2359
/**
2360
 * Implements hook_file_delete() on behalf of pathauto.module.
2361
 */
2362
function pathauto_file_delete($file) {
2363
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2364
}
2365

    
2366
/**
2367
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2368
 *
2369
 * Add the Pathauto settings to the file form.
2370
 */
2371
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2372
  $file = $form_state['file'];
2373
  $langcode = pathauto_entity_language('file', $file);
2374
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2375
}
2376

    
2377
/**
2378
 * Implements hook_file_operations() on behalf of pathauto.module.
2379
 */
2380
function pathauto_file_operations() {
2381
  $operations['pathauto_update_alias'] = array(
2382
    'label' => t('Update URL alias'),
2383
    'callback' => 'pathauto_file_update_alias_multiple',
2384
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2385
  );
2386
  return $operations;
2387
}
2388

    
2389
/**
2390
 * Update the URL aliases for an individual file.
2391
 *
2392
 * @param $file
2393
 *   A file object.
2394
 * @param $op
2395
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2396
 * @param $options
2397
 *   An optional array of additional options.
2398
 */
2399
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2400
  // Skip processing if the user has disabled pathauto for the file.
2401
  if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
2402
    return;
2403
  }
2404

    
2405
  $options += array('language' => pathauto_entity_language('file', $file));
2406

    
2407
  // Skip processing if the file has no pattern.
2408
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2409
    return;
2410
  }
2411

    
2412
  // Skip processing if pathauto_entity module is enabled.
2413
  if (module_exists('pathauto_entity')) {
2414
    return;
2415
  }
2416

    
2417
  module_load_include('inc', 'pathauto');
2418
  $uri = entity_uri('file', $file);
2419
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2420
}
2421

    
2422
/**
2423
 * Update the URL aliases for multiple files.
2424
 *
2425
 * @param $fids
2426
 *   An array of file IDs.
2427
 * @param $op
2428
 *   Operation being performed on the files ('insert', 'update' or
2429
 *   'bulkupdate').
2430
 * @param $options
2431
 *   An optional array of additional options.
2432
 */
2433
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2434
  $options += array('message' => FALSE);
2435

    
2436
  $files = file_load_multiple($fids);
2437
  foreach ($files as $file) {
2438
    pathauto_file_update_alias($file, $op, $options);
2439
  }
2440

    
2441
  if (!empty($options['message'])) {
2442
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2443
  }
2444
}
2445

    
2446
/**
2447
 * Update action wrapper for pathauto_file_update_alias().
2448
 */
2449
function pathauto_file_update_action($file, $context = array()) {
2450
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2451
}
2452

    
2453
/**
2454
 * @} End of "name pathauto_file".
2455
 */
2456

    
2457
/**
2458
 * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
2459
 */
2460
function path_form_file_entity_edit_alter(&$form, $form_state) {
2461
  // Make sure this does not show up on the delete confirmation form.
2462
  if (empty($form_state['confirm_delete'])) {
2463
    $file = $form_state['file'];
2464
    $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
2465
    $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2466
    $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
2467
    $path = (isset($file->fid) ? path_load($conditions) : array());
2468
    if ($path === FALSE) {
2469
      $path = array();
2470
    }
2471
    $path += array(
2472
      'pid' => NULL,
2473
      'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
2474
      'alias' => '',
2475
      'language' => $langcode,
2476
    );
2477
    $form['path'] = array(
2478
      '#type' => 'fieldset',
2479
      '#title' => t('URL path settings'),
2480
      '#collapsible' => TRUE,
2481
      '#collapsed' => empty($path['alias']),
2482
      '#group' => 'additional_settings',
2483
      '#attributes' => array(
2484
        'class' => array('path-form'),
2485
      ),
2486
      '#attached' => array(
2487
        'js' => array(drupal_get_path('module', 'path') . '/path.js'),
2488
      ),
2489
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
2490
      '#weight' => 30,
2491
      '#tree' => TRUE,
2492
      '#element_validate' => array('path_form_element_validate'),
2493
    );
2494
    $form['path']['alias'] = array(
2495
      '#type' => 'textfield',
2496
      '#title' => t('URL alias'),
2497
      '#default_value' => $path['alias'],
2498
      '#maxlength' => 255,
2499
      '#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.'),
2500
    );
2501
    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
2502
    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
2503
    $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
2504
  }
2505
}
2506

    
2507
/**
2508
 * Implements hook_file_insert() on behalf of path.module.
2509
 */
2510
function path_file_insert($file) {
2511
  if (isset($file->path)) {
2512
    $path = $file->path;
2513
    $path['alias'] = trim($path['alias']);
2514
    // Only save a non-empty alias.
2515
    if (!empty($path['alias'])) {
2516
      // Ensure fields for programmatic executions.
2517
      $path['source'] = 'file/' . $file->fid;
2518
      // Core does not provide a way to store the file language but contrib
2519
      // modules can do it so we need to take this into account.
2520
      $langcode = entity_language('file', $file);
2521
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2522
      path_save($path);
2523
    }
2524
  }
2525
}
2526

    
2527
/**
2528
 * Implements hook_file_update() on behalf of path.module.
2529
 */
2530
function path_file_update($file) {
2531
  if (isset($file->path)) {
2532
    $path = $file->path;
2533
    $path['alias'] = trim($path['alias']);
2534
    // Delete old alias if user erased it.
2535
    if (!empty($path['fid']) && empty($path['alias'])) {
2536
      path_delete($path['fid']);
2537
    }
2538
    // Only save a non-empty alias.
2539
    if (!empty($path['alias'])) {
2540
      // Ensure fields for programmatic executions.
2541
      $path['source'] = 'file/' . $file->fid;
2542
      // Core does not provide a way to store the file language but contrib
2543
      // modules can do it so we need to take this into account.
2544
      $langcode = entity_language('file', $file);
2545
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2546
      path_save($path);
2547
    }
2548
  }
2549
}
2550

    
2551
/**
2552
 * Implements hook_file_delete() on behalf of path.module.
2553
 */
2554
function path_file_delete($file) {
2555
  // Delete all aliases associated with this file.
2556
  path_delete(array('source' => 'file/' . $file->fid));
2557
}
2558

    
2559
/**
2560
 * Checks if pattern(s) match mimetype(s).
2561
 */
2562
function file_entity_match_mimetypes($needle, $haystack) {
2563
  $needle = is_array($needle) ? $needle : array($needle);
2564
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2565

    
2566
  foreach ($haystack as $mimetype) {
2567
    foreach ($needle as $search) {
2568
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2569
        return TRUE;
2570
      }
2571
    }
2572
  }
2573

    
2574
  return FALSE;
2575
}
2576

    
2577
/**
2578
 * A wrapper function for the PHP function fnmatch().
2579
 *
2580
 * We include this, because Windows servers do not implement fnmatch() until
2581
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2582
 */
2583
function file_entity_fnmatch($pattern, $string) {
2584
  if (!function_exists('fnmatch')) {
2585
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2586
  }
2587
  return fnmatch($pattern, $string);
2588
}
2589

    
2590
/**
2591
 * Return an URI for a file download.
2592
 */
2593
function file_entity_download_uri($file) {
2594
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2595
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2596
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2597
  }
2598
  return $uri;
2599
}
2600

    
2601
function file_entity_file_get_mimetype_type($file) {
2602
  if (is_array($file)) {
2603
    $file = (object) $file;
2604
  }
2605
  list($type, $subtype) = explode('/', $file->filemime, 2);
2606
  return $type;
2607
}
2608

    
2609
/**
2610
 * Implements hook_admin_menu_map().
2611
 */
2612
function file_entity_admin_menu_map() {
2613
  if (!user_access('administer file types')) {
2614
    return;
2615
  }
2616
  $map['admin/structure/file-types/manage/%file_type'] = array(
2617
    'parent' => 'admin/structure/file-types',
2618
    'arguments' => array(
2619
      array('%file_type' => array_keys(file_entity_type_get_names())),
2620
    ),
2621
  );
2622
  return $map;
2623
}
2624

    
2625
/*
2626
 * Generates a token to protect a file download URL.
2627
 *
2628
 * This prevents unauthorized crawling of all file download URLs since the
2629
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
2630
 * to guess or attempt many at once. This can be costly both in CPU time
2631
 * and bandwidth.
2632
 *
2633
 * @see image_style_path_token()
2634
 *
2635
 * @param object $file
2636
 *   A file entity object.
2637
 *
2638
 * @return string
2639
 *   An eight-character token which can be used to protect file downloads
2640
 *   against denial-of-service attacks.
2641
 */
2642
function file_entity_get_download_token($file) {
2643
  // Return the first eight characters.
2644
  return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
2645
}
2646

    
2647
/**
2648
 * Find all fields that are of a certain field type.
2649
 *
2650
 * @param string $field_type
2651
 *   A field type.
2652
 *
2653
 * @return array
2654
 *   An array of field names that match the type $field_type.
2655
 */
2656
function _file_entity_get_fields_by_type($field_type) {
2657
  $return = array();
2658
  if (function_exists('field_info_field_map')) {
2659
    foreach (field_info_field_map() as $field_name => $field) {
2660
      if ($field['type'] == $field_type) {
2661
        $return[$field_name] = $field_name;
2662
      }
2663
    }
2664
  }
2665
  else {
2666
    foreach (field_info_fields() as $field_name => $field) {
2667
      if ($field['type'] == $field_type) {
2668
        $return[$field_name] = $field_name;
2669
      }
2670
    }
2671
  }
2672
  return $return;
2673
}
2674

    
2675
/**
2676
 * Implements hook_field_attach_load().
2677
 */
2678
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2679
  // Loop over all the entities looking for entities with attached images.
2680
  foreach ($entities as $entity) {
2681
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2682
    // Examine every image field instance attached to this entity's bundle.
2683
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2684
    foreach ($instances as $field_name => $instance) {
2685
      if (!empty($entity->{$field_name})) {
2686
        foreach ($entity->{$field_name} as $langcode => $items) {
2687
          foreach ($items as $delta => $item) {
2688
            // If alt and title text is not specified, fall back to alt and
2689
            // title text on the file.
2690
            if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
2691
              $file = file_load($item['fid']);
2692
              foreach (array('alt', 'title') as $key) {
2693
                if (empty($item[$key]) && !empty($file->{$key})) {
2694
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2695
                }
2696
              }
2697
            }
2698
          }
2699
        }
2700
      }
2701
    }
2702
  }
2703
}
2704

    
2705
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2706
  $wrappers = array();
2707
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2708
    if (empty($wrapper['private'])) {
2709
      $wrappers['public'][$key] = $wrapper['name'];
2710
    }
2711
    else {
2712
      $wrappers['private'][$key] = $wrapper['name'];
2713
    }
2714
  }
2715
  return $wrappers;
2716
}
2717

    
2718
/**
2719
 * Implements hook_features_pipe_alter() for the file type component.
2720
 */
2721
function file_entity_features_pipe_file_type_alter(&$pipe, $data, $export) {
2722
  foreach ($data as $file_type) {
2723
    $pipe['variable'][] = "pathauto_file_{$file_type}_pattern";
2724
  }
2725
}
2726

    
2727
/**
2728
 * Implements hook_FORM_ID_alter().
2729
 */
2730
function file_entity_form_system_performance_settings_alter(&$form, &$form_state) {
2731
  $form['bandwidth_optimization']['file_entity_total_count_optimization'] = array(
2732
    '#type' => 'checkbox',
2733
    '#title' => t('Optimize the calculation of the total usage count of files in the files overview.'),
2734
    '#default_value' => variable_get('file_entity_total_count_optimization', FALSE),
2735
    '#description' => t('Recommended if the files admin page loads too slowly due to a high number of files.'),
2736
  );
2737
}