Projet

Général

Profil

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

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

1 85ad3d82 Assos Assos
<?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 ca0757b9 Assos Assos
    'file_operations',
42 85ad3d82 Assos Assos
    '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 1f142f4f Florent Torregrosa
    // Stream wrappers
83
    'stream_wrappers',
84
    'stream_wrappers_alter',
85 85ad3d82 Assos Assos
  );
86
  $info += array_fill_keys($hooks, array('group' => 'file'));
87
}
88
89 ca0757b9 Assos Assos
/**
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 85ad3d82 Assos Assos
/**
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 ca0757b9 Assos Assos
  $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 85ad3d82 Assos Assos
  $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 0ccfec7f Assos Assos
  // 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 85ad3d82 Assos Assos
  return $items;
410
}
411
412
/**
413
 * Implements hook_menu_local_tasks_alter().
414
 */
415
function file_entity_menu_local_tasks_alter(&$data, $router_item, $root_path) {
416
  // Add action link to 'file/add' on 'admin/content/file' page.
417
  if ($root_path == 'admin/content/file') {
418
    $item = menu_get_item('file/add');
419
    if (!empty($item['access'])) {
420
      $data['actions']['output'][] = array(
421
        '#theme' => 'menu_local_action',
422
        '#link' => $item,
423
        '#weight' => $item['weight'],
424
      );
425
    }
426
  }
427
}
428
429
/**
430
 * Implement hook_permission().
431
 */
432
function file_entity_permission() {
433
  $permissions = array(
434
    'bypass file access' => array(
435
      'title' => t('Bypass file access control'),
436
      'description' => t('View, edit and delete all files regardless of permission restrictions.'),
437
      'restrict access' => TRUE,
438
    ),
439
    'administer file types' => array(
440
      'title' => t('Administer file types'),
441
      'restrict access' => TRUE,
442
    ),
443
    'administer files' => array(
444
      'title' => t('Administer files'),
445
      'restrict access' => TRUE,
446
    ),
447
    'create files' => array(
448
      'title' => t('Add and upload new files'),
449
    ),
450
    'view own private files' => array(
451
      'title' => t('View own private files'),
452
    ),
453
    'view own files' => array(
454
      'title' => t('View own files'),
455
    ),
456
    'view private files' => array(
457
      'title' => t('View private files'),
458
      'restrict access' => TRUE,
459
    ),
460
    'view files' => array(
461
      'title' => t('View files'),
462
    ),
463
  );
464
465
  // Add description for the 'View file details' and 'View own private file
466
  // details' permissions to show which stream wrappers they apply to.
467
  $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
468
  $wrappers += array('public' => array(t('None')), 'private' => array(t('None')));
469
470
  $permissions['view files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['public'])));
471
  $permissions['view own private files']['description'] = t('Includes the following stream wrappers: %wrappers.', array('%wrappers' => implode(', ', $wrappers['private'])));
472
473
  // Generate standard file permissions for all applicable file types.
474
  foreach (file_entity_permissions_get_configured_types() as $type) {
475
    $permissions += file_entity_list_permissions($type);
476
  }
477
478
  return $permissions;
479
}
480
481
/*
482
 * Implements hook_cron_queue_info().
483
 */
484
function file_entity_cron_queue_info() {
485
  $queues['file_entity_type_determine'] = array(
486
    'worker callback' => 'file_entity_type_determine',
487
  );
488
  return $queues;
489
}
490
491
/*
492
 * Determines file type for a given file ID and saves the file.
493
 *
494
 * @param $fid
495
 *   A file ID.
496
 */
497
function file_entity_type_determine($fid) {
498
  if ($file = file_load($fid)) {
499
    // The file type will be automatically determined when saving the file.
500
    file_save($file);
501
  }
502
}
503
504
/**
505
 * Gather the rankings from the the hook_ranking implementations.
506
 *
507
 * @param $query
508
 *   A query object that has been extended with the Search DB Extender.
509
 */
510
function _file_entity_rankings(SelectQueryExtender $query) {
511
  if ($ranking = module_invoke_all('file_ranking')) {
512
    $tables = &$query->getTables();
513
    foreach ($ranking as $rank => $values) {
514
      if ($file_rank = variable_get('file_entity_rank_' . $rank, 0)) {
515
        // If the table defined in the ranking isn't already joined, then add it.
516
        if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
517
          $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
518
        }
519
        $arguments = isset($values['arguments']) ? $values['arguments'] : array();
520
        $query->addScore($values['score'], $arguments, $file_rank);
521
      }
522
    }
523
  }
524
}
525
526
/**
527
 * Implements hook_search_info().
528
 */
529
function file_entity_search_info() {
530
  return array(
531
    'title' => 'Files',
532
    'path' => 'file',
533
  );
534
}
535
536
/**
537
 * Implements hook_search_access().
538
 */
539
function file_entity_search_access() {
540
  return user_access('view own private files') || user_access('view own files') || user_access('view private files') || user_access('view files');
541
}
542
543
/**
544
 * Implements hook_search_reset().
545
 */
546
function file_entity_search_reset() {
547
  db_update('search_dataset')
548
    ->fields(array('reindex' => REQUEST_TIME))
549
    ->condition('type', 'file')
550
    ->execute();
551
}
552
553
/**
554
 * Implements hook_search_status().
555
 */
556
function file_entity_search_status() {
557
  $total = db_query('SELECT COUNT(*) FROM {file_managed}')->fetchField();
558
  $remaining = db_query("SELECT COUNT(*) FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0")->fetchField();
559
  return array('remaining' => $remaining, 'total' => $total);
560
}
561
562
/**
563
 * Implements hook_search_admin().
564
 */
565
function file_entity_search_admin() {
566
  // Output form for defining rank factor weights.
567
  $form['file_ranking'] = array(
568
    '#type' => 'fieldset',
569
    '#title' => t('File ranking'),
570
  );
571
  $form['file_ranking']['#theme'] = 'file_entity_search_admin';
572
  $form['file_ranking']['info'] = array(
573
    '#value' => '<em>' . t('The following numbers control which properties the file search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
574
  );
575
576
  // Note: reversed to reflect that higher number = higher ranking.
577
  $options = drupal_map_assoc(range(0, 10));
578
  foreach (module_invoke_all('file_ranking') as $var => $values) {
579
    $form['file_ranking']['factors']['file_entity_rank_' . $var] = array(
580
      '#title' => $values['title'],
581
      '#type' => 'select',
582
      '#options' => $options,
583
      '#default_value' => variable_get('file_entity_rank_' . $var, 0),
584
    );
585
  }
586
  return $form;
587
}
588
589
/**
590
 * Implements hook_search_execute().
591
 */
592
function file_entity_search_execute($keys = NULL, $conditions = NULL) {
593
  global $user;
594
595
  // Build matching conditions
596
  $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
597
  $query->join('file_managed', 'fm', 'fm.fid = i.sid');
598
  $query->searchExpression($keys, 'file');
599
600
  // Insert special keywords.
601
  $query->setOption('type', 'fm.type');
602
  if ($query->setOption('term', 'ti.tid')) {
603
    $query->join('taxonomy_index', 'ti', 'fm.fid = ti.fid');
604
  }
605
  // Only continue if the first pass query matches.
606
  if (!$query->executeFirstPass()) {
607
    return array();
608
  }
609
610
  // Add the ranking expressions.
611
  _file_entity_rankings($query);
612
613
  // Load results.
614
  $find = $query
615
    ->limit(10)
616
    ->addTag('file_access')
617
    ->execute();
618
  $results = array();
619
  foreach ($find as $item) {
620
    // Render the file.
621
    $file = file_load($item->sid);
622
    $build = file_view($file, 'search_result');
623
    unset($build['#theme']);
624
    $file->rendered = drupal_render($build);
625
626
    $extra = module_invoke_all('file_entity_search_result', $file);
627
628
    $types = file_entity_type_get_names();
629
630
    $uri = entity_uri('file', $file);
631
    $results[] = array(
632
      'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
633
      'type' => check_plain($types[$file->type]),
634
      'title' => $file->filename,
635
      'user' => theme('username', array('account' => user_load($file->uid))),
636
      'date' => $file->timestamp,
637
      'file' => $file,
638
      'extra' => $extra,
639
      'score' => $item->calculated_score,
640
      'snippet' => search_excerpt($keys, $file->rendered),
641
      'language' => function_exists('entity_language') ? entity_language('file', $file) : NULL,
642
    );
643
  }
644
  return $results;
645
}
646
647
/**
648
 * Implements hook_file_ranking().
649
 */
650
function file_entity_file_ranking() {
651
  // Create the ranking array and add the basic ranking options.
652
  $ranking = array(
653
    'relevance' => array(
654
      'title' => t('Keyword relevance'),
655
      // Average relevance values hover around 0.15
656
      'score' => 'i.relevance',
657
    ),
658
  );
659
660
  // Add relevance based on creation date.
661
  if ($file_cron_last = variable_get('file_entity_cron_last', 0)) {
662
    $ranking['timestamp'] = array(
663
      'title' => t('Recently posted'),
664
      // Exponential decay with half-life of 6 months, starting at last indexed file
665
      'score' => 'POW(2.0, (fm.timestamp - :file_cron_last) * 6.43e-8)',
666
      'arguments' => array(':file_cron_last' => $file_cron_last),
667
    );
668
  }
669
  return $ranking;
670
}
671
672
/**
673
 * Returns HTML for the file ranking part of the search settings admin page.
674
 *
675
 * @param $variables
676
 *   An associative array containing:
677
 *   - form: A render element representing the form.
678
 *
679
 * @ingroup themeable
680
 */
681
function theme_file_entity_search_admin($variables) {
682
  $form = $variables['form'];
683
684
  $output = drupal_render($form['info']);
685
686
  $header = array(t('Factor'), t('Weight'));
687
  foreach (element_children($form['factors']) as $key) {
688
    $row = array();
689
    $row[] = $form['factors'][$key]['#title'];
690
    $form['factors'][$key]['#title_display'] = 'invisible';
691
    $row[] = drupal_render($form['factors'][$key]);
692
    $rows[] = $row;
693
  }
694
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
695
696
  $output .= drupal_render_children($form);
697
  return $output;
698
}
699
700
/**
701
 * Implements hook_update_index().
702
 */
703
function file_entity_update_index() {
704
  $limit = (int)variable_get('search_cron_limit', 100);
705
706
  $result = db_query_range("SELECT fm.fid FROM {file_managed} fm LEFT JOIN {search_dataset} d ON d.type = 'file' AND d.sid = fm.fid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, fm.fid ASC", 0, $limit, array(), array('target' => 'slave'));
707
708
  foreach ($result as $file) {
709
    _file_entity_index_file($file);
710
  }
711
}
712
713
/**
714
 * Index a single file.
715
 *
716
 * @param $file
717
 *   The file to index.
718
 */
719
function _file_entity_index_file($file) {
720
  $file = file_load($file->fid);
721
722
  // Save the creation time of the most recent indexed file, for the search
723
  // results half-life calculation.
724
  variable_set('file_entity_cron_last', $file->timestamp);
725
726
  // Render the file.
727
  $build = file_view($file, 'search_index');
728
  unset($build['#theme']);
729
  $file->rendered = drupal_render($build);
730
731
  $text = '<h1>' . check_plain($file->filename) . '</h1>' . $file->rendered;
732
733
  // Fetch extra data normally not visible
734
  $extra = module_invoke_all('file_entity_update_index', $file);
735
  foreach ($extra as $t) {
736
    $text .= $t;
737
  }
738
739
  // Update index
740
  search_index($file->fid, 'file', $text);
741
}
742
743
/**
744
 * Implements hook_form_FORM_ID_alter().
745
 */
746
function file_entity_form_search_form_alter(&$form, $form_state) {
747
  if (isset($form['module']) && $form['module']['#value'] == 'file_entity' && user_access('use advanced search')) {
748
    // Keyword boxes:
749
    $form['advanced'] = array(
750
      '#type' => 'fieldset',
751
      '#title' => t('Advanced search'),
752
      '#collapsible' => TRUE,
753
      '#collapsed' => TRUE,
754
      '#attributes' => array('class' => array('search-advanced')),
755
    );
756
    $form['advanced']['keywords'] = array(
757
      '#prefix' => '<div class="criterion">',
758
      '#suffix' => '</div>',
759
    );
760
    $form['advanced']['keywords']['or'] = array(
761
      '#type' => 'textfield',
762
      '#title' => t('Containing any of the words'),
763
      '#size' => 30,
764
      '#maxlength' => 255,
765
    );
766
    $form['advanced']['keywords']['phrase'] = array(
767
      '#type' => 'textfield',
768
      '#title' => t('Containing the phrase'),
769
      '#size' => 30,
770
      '#maxlength' => 255,
771
    );
772
    $form['advanced']['keywords']['negative'] = array(
773
      '#type' => 'textfield',
774
      '#title' => t('Containing none of the words'),
775
      '#size' => 30,
776
      '#maxlength' => 255,
777
    );
778
779
    // File types:
780
    $types = array_map('check_plain', file_entity_type_get_names());
781
    $form['advanced']['type'] = array(
782
      '#type' => 'checkboxes',
783
      '#title' => t('Only of the type(s)'),
784
      '#prefix' => '<div class="criterion">',
785
      '#suffix' => '</div>',
786
      '#options' => $types,
787
    );
788
    $form['advanced']['submit'] = array(
789
      '#type' => 'submit',
790
      '#value' => t('Advanced search'),
791
      '#prefix' => '<div class="action">',
792
      '#suffix' => '</div>',
793
      '#weight' => 100,
794
    );
795
796
    $form['#validate'][] = 'file_entity_search_validate';
797
  }
798
}
799
800
/**
801
 * Form API callback for the search form. Registered in file_entity_form_alter().
802
 */
803
function file_entity_search_validate($form, &$form_state) {
804
  // Initialize using any existing basic search keywords.
805
  $keys = $form_state['values']['processed_keys'];
806
807
  // Insert extra restrictions into the search keywords string.
808
  if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
809
    // Retrieve selected types - Form API sets the value of unselected
810
    // checkboxes to 0.
811
    $form_state['values']['type'] = array_filter($form_state['values']['type']);
812
    if (count($form_state['values']['type'])) {
813
      $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
814
    }
815
  }
816
817
  if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
818
    $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
819
  }
820
  if ($form_state['values']['or'] != '') {
821
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
822
      $keys .= ' ' . implode(' OR ', $matches[1]);
823
    }
824
  }
825
  if ($form_state['values']['negative'] != '') {
826
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
827
      $keys .= ' -' . implode(' -', $matches[1]);
828
    }
829
  }
830
  if ($form_state['values']['phrase'] != '') {
831
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
832
  }
833
  if (!empty($keys)) {
834
    form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
835
  }
836
}
837
838
/**
839
 * Implements hook_admin_paths().
840
 */
841
function file_entity_admin_paths() {
842
  $paths = array(
843
    'file/add' => TRUE,
844
    'file/add/*' => TRUE,
845
    'file/*/edit' => TRUE,
846
    'file/*/usage' => TRUE,
847
    'file/*/delete' => TRUE,
848
  );
849
  return $paths;
850
}
851
852
/**
853
 * Implements hook_action_info_alter().
854
 */
855
function file_entity_action_info_alter(&$actions) {
856
  if (module_exists('pathauto')) {
857
    $actions['pathauto_file_update_action'] = array(
858
      'type' => 'file',
859
      'label' => t('Update file alias'),
860
      'configurable' => FALSE,
861
    );
862
  }
863
}
864
865
/**
866
 * Implements hook_theme().
867
 */
868
function file_entity_theme() {
869
  return array(
870
    'file_entity' => array(
871
      'render element' => 'elements',
872
      'template' => 'file_entity',
873
    ),
874
    'file_entity_search_admin' => array(
875
      'render element' => 'form',
876
    ),
877
    'file_entity_file_type_overview' => array(
878
      'variables' => array('label' => NULL, 'description' => NULL),
879
      'file' => 'file_entity.admin.inc',
880
    ),
881
    'file_entity_file_display_order' => array(
882
      'render element' => 'element',
883
      'file' => 'file_entity.admin.inc',
884
    ),
885
    'file_entity_file_link' => array(
886
      'variables' => array('file' => NULL, 'icon_directory' => NULL),
887
      'file' => 'file_entity.theme.inc',
888
    ),
889
    'file_entity_download_link' => array(
890
      'variables' => array('file' => NULL, 'icon_directory' => NULL, 'text' => NULL),
891
      'file' => 'file_entity.theme.inc',
892
    ),
893
    'file_entity_file_audio' => array(
894
      'variables' => array(
895
        'files' => array(),
896
        'controls' => TRUE,
897
        'autoplay' => FALSE,
898
        'loop' => FALSE,
899 1f142f4f Florent Torregrosa
        'preload' => NULL,
900 85ad3d82 Assos Assos
      ),
901
      'file' => 'file_entity.theme.inc',
902
    ),
903
    'file_entity_file_video' => array(
904
      'variables' => array(
905
        'files' => array(),
906
        'controls' => TRUE,
907
        'autoplay' => FALSE,
908
        'loop' => FALSE,
909
        'muted' => FALSE,
910
        'width' => NULL,
911
        'height' => NULL,
912 1f142f4f Florent Torregrosa
        'preload' => NULL,
913 85ad3d82 Assos Assos
      ),
914
      'file' => 'file_entity.theme.inc',
915
    ),
916
  );
917
}
918
919
/**
920
 * Implements hook_entity_info_alter().
921
 *
922
 * Extends the core file entity to be fieldable. The file type is used as the
923
 * bundle key. File types are implemented as CTools exportables, so modules can
924
 * define default file types via hook_file_default_types(), and the
925
 * administrator can override the default types or add custom ones via
926
 * admin/structure/file-types.
927
 */
928
function file_entity_entity_info_alter(&$entity_info) {
929
  $entity_info['file']['fieldable'] = TRUE;
930
  $entity_info['file']['entity keys']['bundle'] = 'type';
931
  $entity_info['file']['bundle keys']['bundle'] = 'type';
932
  $entity_info['file']['bundles'] = array();
933
  $entity_info['file']['uri callback'] = 'file_entity_uri';
934
  $entity_info['file']['view modes']['teaser'] = array(
935
    'label' => t('Teaser'),
936
    'custom settings' => TRUE,
937
  );
938
  $entity_info['file']['view modes']['full'] = array(
939
    'label' => t('Full content'),
940
    'custom settings' => FALSE,
941
  );
942
  $entity_info['file']['view modes']['preview'] = array(
943
    'label' => t('Preview'),
944
    'custom settings' => TRUE,
945
  );
946
  $entity_info['file']['view modes']['rss'] = array(
947
    'label' => t('RSS'),
948
    'custom settings' => FALSE,
949
  );
950
951
  // Search integration is provided by file_entity.module, so search-related
952
  // view modes for files are defined here and not in search.module.
953
  if (module_exists('search')) {
954
    $entity_info['file']['view modes']['search_index'] = array(
955
      'label' => t('Search index'),
956
      'custom settings' => FALSE,
957
    );
958
    $entity_info['file']['view modes']['search_result'] = array(
959
      'label' => t('Search result'),
960
      'custom settings' => FALSE,
961
    );
962
  }
963
964
  foreach (file_type_get_enabled_types() as $type) {
965
    $entity_info['file']['bundles'][$type->type] = array(
966
      'label' => $type->label,
967
      'admin' => array(
968
        'path' => 'admin/structure/file-types/manage/%file_type',
969
        'real path' => 'admin/structure/file-types/manage/' . $type->type,
970
        'bundle argument' => 4,
971 0ccfec7f Assos Assos
        'access arguments' => array('administer file types'),
972 85ad3d82 Assos Assos
      ),
973
    );
974
  }
975
976
  // Enable Metatag support.
977
  $entity_info['file']['metatags'] = TRUE;
978
979
  // Ensure some of the Entity API callbacks are supported.
980
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
981 1f142f4f Florent Torregrosa
  $entity_info['file']['view callback'] = 'file_entity_metadata_view_file';
982
  $entity_info['file']['form callback'] = 'file_entity_metadata_form_file';
983 85ad3d82 Assos Assos
  $entity_info['file']['access callback'] = 'file_entity_access';
984
985
  // Add integration with the Title module for file name replacement support.
986
  $entity_info['file']['field replacement'] = array(
987
    'filename' => array(
988
      'field' => array(
989
        'type' => 'text',
990
        'cardinality' => 1,
991
        'translatable' => TRUE,
992
      ),
993
      'instance' => array(
994
        'label' => t('File name'),
995
        'description' => t('A field replacing file name.'),
996
        'required' => TRUE,
997
        'settings' => array(
998
          'text_processing' => 0,
999
        ),
1000
        'widget' => array(
1001
          'weight' => -5,
1002
        ),
1003
        'display' => array(
1004
          'default' => array(
1005
            'type' => 'hidden',
1006
          ),
1007
        ),
1008
      ),
1009
      'preprocess_key' => 'filename',
1010
    ),
1011
  );
1012
}
1013
1014
/**
1015
 * Implements hook_entity_property_info().
1016
 */
1017
function file_entity_entity_property_info() {
1018
  $info['file']['properties']['type'] = array(
1019
    'label' => t('File type'),
1020
    'type' => 'token',
1021
    'description' => t('The type of the file.'),
1022
    'setter callback' => 'entity_property_verbatim_set',
1023
    'setter permission' => 'administer files',
1024
    'options list' => 'file_entity_type_get_names',
1025
    'required' => TRUE,
1026
    'schema field' => 'type',
1027
  );
1028
1029
  return $info;
1030
}
1031
1032
/**
1033
 * Implements hook_field_display_ENTITY_TYPE_alter().
1034
 */
1035
function file_entity_field_display_file_alter(&$display, $context) {
1036
  // Hide field labels in search index.
1037
  if ($context['view_mode'] == 'search_index') {
1038
    $display['label'] = 'hidden';
1039
  }
1040
}
1041
1042
/**
1043
 * URI callback for file entities.
1044
 */
1045
function file_entity_uri($file) {
1046
  $uri['path'] = 'file/' . $file->fid;
1047
  return $uri;
1048
}
1049
1050 1f142f4f Florent Torregrosa
/**
1051
 * Entity API callback to view files.
1052
 */
1053
function file_entity_metadata_view_file($entities, $view_mode = 'full', $langcode = NULL) {
1054
  $result = file_view_multiple($entities, $view_mode, 0, $langcode);
1055
  // Make sure to key the result with 'file' instead of 'files'.
1056
  return array('file' => reset($result));
1057
}
1058
1059 85ad3d82 Assos Assos
/**
1060
 * Entity API callback to get the form of a file entity.
1061
 */
1062
function file_entity_metadata_form_file($file) {
1063
  // Pre-populate the form-state with the right form include.
1064
  $form_state['build_info']['args'] = array($file);
1065
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
1066
  return drupal_build_form('file_entity_edit', $form_state);
1067
}
1068
1069
/**
1070
 * Implements hook_ctools_plugin_directory().
1071
 */
1072 1f142f4f Florent Torregrosa
function file_entity_ctools_plugin_directory($module, $plugin) {
1073
  if (in_array($module, array('panelizer', 'ctools', 'page_manager'))) {
1074
    return 'plugins/' . $plugin;
1075 85ad3d82 Assos Assos
  }
1076
}
1077
1078
/**
1079
 * Implements hook_field_extra_fields().
1080
 *
1081
 * Adds 'file' as an extra field, so that its display and form component can be
1082
 * weighted relative to the fields that are added to file entity bundles.
1083
 */
1084
function file_entity_field_extra_fields() {
1085
  $info = array();
1086
1087
  if ($file_type_names = file_entity_type_get_names()) {
1088
    foreach ($file_type_names as $type => $name) {
1089
      $info['file'][$type]['form']['filename'] = array(
1090
        'label' => t('File name'),
1091
        'description' => t('File name'),
1092
        'weight' => -10,
1093
      );
1094
      $info['file'][$type]['form']['preview'] = array(
1095
        'label' => t('File'),
1096
        'description' => t('File preview'),
1097
        'weight' => -5,
1098
      );
1099
      $info['file'][$type]['display']['file'] = array(
1100
        'label' => t('File'),
1101
        'description' => t('File display'),
1102
        'weight' => 0,
1103
      );
1104
    }
1105
  }
1106
1107
  return $info;
1108
}
1109
1110
/**
1111
 * Implements hook_file_formatter_info().
1112
 */
1113
function file_entity_file_formatter_info() {
1114
  $formatters = array();
1115
1116
  // Allow file field formatters to be reused for displaying the file entity's
1117
  // file pseudo-field.
1118
  foreach (field_info_formatter_types() as $key => $formatter) {
1119
    if (array_intersect($formatter['field types'], array('file', 'image'))) {
1120
      $key = 'file_field_' . $key;
1121
      $formatters[$key] = array(
1122
        'label' => $formatter['label'],
1123
        'description' => !empty($formatter['description']) ? $formatter['description'] : '',
1124
        'view callback' => 'file_entity_file_formatter_file_field_view',
1125
      );
1126
      if (!empty($formatter['settings'])) {
1127
        $formatters[$key] += array(
1128
          'default settings' => $formatter['settings'],
1129
          'settings callback' => 'file_entity_file_formatter_file_field_settings',
1130
        );
1131
      }
1132
      if (!empty($formatter['file formatter'])) {
1133
        $formatters[$key] += $formatter['file formatter'];
1134
      }
1135
    }
1136
  }
1137
1138
  // Add a simple file formatter for displaying an image in a chosen style.
1139
  if (module_exists('image')) {
1140
    $formatters['file_image'] = array(
1141
      'label' => t('Image'),
1142
      'default settings' => array(
1143
        'image_style' => '',
1144
        'alt' => '[file:field_file_image_alt_text]',
1145
        'title' => '[file:field_file_image_title_text]'
1146
      ),
1147
      'view callback' => 'file_entity_file_formatter_file_image_view',
1148
      'settings callback' => 'file_entity_file_formatter_file_image_settings',
1149
      'hidden' => TRUE,
1150
      'mime types' => array('image/*'),
1151
    );
1152
  }
1153
1154
  return $formatters;
1155
}
1156
1157
/**
1158
 * Implements hook_file_formatter_FORMATTER_view().
1159
 *
1160
 * This function provides a bridge to the field formatter API, so that file
1161
 * field formatters can be reused for displaying the file entity's file
1162
 * pseudo-field.
1163
 */
1164
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
1165
  if (strpos($display['type'], 'file_field_') === 0) {
1166
    $field_formatter_type = substr($display['type'], strlen('file_field_'));
1167
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1168
    if (isset($field_formatter_info['module'])) {
1169
      // Set $display['type'] to what hook_field_formatter_*() expects.
1170
      $display['type'] = $field_formatter_type;
1171
1172 ca0757b9 Assos Assos
      // Allow any attribute overrides (e.g. from the Media module) to be
1173
      // respected.
1174
      $item = (array) $file;
1175
      if (!empty($file->override['attributes'])) {
1176
        $item = array_merge($item, $file->override['attributes']);
1177
      }
1178
1179 85ad3d82 Assos Assos
      // Set $items to what file field formatters expect. See file_field_load(),
1180
      // and note that, here, $file is already a fully loaded entity.
1181 ca0757b9 Assos Assos
      $items = array($item);
1182 85ad3d82 Assos Assos
1183
      // Invoke hook_field_formatter_prepare_view() and
1184
      // hook_field_formatter_view(). Note that we are reusing field formatter
1185
      // functions, but we are not displaying a Field API field, so we set
1186
      // $field and $instance accordingly, and do not invoke
1187
      // hook_field_prepare_view(). This assumes that the formatter functions do
1188
      // not rely on $field or $instance. A module that implements formatter
1189
      // functions that rely on $field or $instance (and therefore, can only be
1190
      // used for real fields) can prevent this formatter from being used on the
1191
      // pseudo-field by removing it within hook_file_formatter_info_alter().
1192
      $field = $instance = NULL;
1193
      if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
1194
        $fid = $file->fid;
1195
        // hook_field_formatter_prepare_view() alters $items by reference.
1196
        $grouped_items = array($fid => &$items);
1197
        $function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
1198
      }
1199
      if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
1200
        $element = $function('file', $file, $field, $instance, $langcode, $items, $display);
1201
        // We passed the file as $items[0], so return the corresponding element.
1202
        if (isset($element[0])) {
1203
          return $element[0];
1204
        }
1205
      }
1206
    }
1207
  }
1208
}
1209
1210
/**
1211
 * Implements hook_file_formatter_FORMATTER_settings().
1212
 *
1213
 * This function provides a bridge to the field formatter API, so that file
1214
 * field formatters can be reused for displaying the file entity's file
1215
 * pseudo-field.
1216
 */
1217
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
1218
  if (strpos($formatter_type, 'file_field_') === 0) {
1219
    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
1220
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1221
1222
    // Invoke hook_field_formatter_settings_form(). We are reusing field
1223
    // formatter functions, but we are not working with a Field API field, so
1224
    // set $field accordingly. Unfortunately, the API is for $settings to be
1225 0ccfec7f Assos Assos
    // transferred via the $instance parameter, so we must mock it.
1226 85ad3d82 Assos Assos
    if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
1227
      $field = NULL;
1228
      $mock_instance = array(
1229
        'display' => array(
1230
          $view_mode => array(
1231
            'type' => $field_formatter_type,
1232
            'settings' => $settings,
1233
          ),
1234
        ),
1235
        'entity_type' => 'file',
1236
        'bundle' => $file_type,
1237
      );
1238
      return $function($field, $mock_instance, $view_mode, $form, $form_state);
1239
    }
1240
  }
1241
}
1242
1243
/**
1244
 * Implements hook_file_formatter_FORMATTER_view().
1245
 *
1246
 * Returns a drupal_render() array to display an image of the chosen style.
1247
 *
1248
 * This formatter is only capable of displaying local images. If the passed in
1249
 * file is either not local or not an image, nothing is returned, so that
1250
 * file_view_file() can try another formatter.
1251
 */
1252
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
1253
  // Prevent PHP notices when trying to read empty files.
1254
  // @see http://drupal.org/node/681042
1255
  if (!$file->filesize) {
1256
    return;
1257
  }
1258
1259
  // Do not bother proceeding if this file does not have an image mime type.
1260
  if (file_entity_file_get_mimetype_type($file) != 'image') {
1261
    return;
1262
  }
1263
1264
  if (file_entity_file_is_readable($file)) {
1265
    // We don't sanitize here.
1266
    // @see http://drupal.org/node/1553094#comment-6257382
1267
    // Theme function will take care of escaping.
1268
    if (!isset($file->metadata)) {
1269
      $file->metadata = array();
1270
    }
1271
    $file->metadata += array('width' => NULL, 'height' => NULL);
1272
    $replace_options = array(
1273
      'clear' => TRUE,
1274
      'sanitize' => FALSE,
1275
    );
1276
    if (!empty($display['settings']['image_style'])) {
1277
      $element = array(
1278
        '#theme' => 'image_style',
1279
        '#style_name' => $display['settings']['image_style'],
1280
        '#path' => $file->uri,
1281
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1282
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1283
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1284
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1285
      );
1286
    }
1287
    else {
1288
      $element = array(
1289
        '#theme' => 'image',
1290
        '#path' => $file->uri,
1291
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1292
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1293
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1294
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1295
      );
1296
    }
1297
    return $element;
1298
  }
1299
}
1300
1301
/**
1302
 * Check if a file entity is readable or not.
1303
 *
1304
 * @param object $file
1305
 *   A file entity object from file_load().
1306
 *
1307
 * @return boolean
1308
 *   TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
1309
 */
1310
function file_entity_file_is_readable($file) {
1311
  $scheme = file_uri_scheme($file->uri);
1312
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
1313
  return !empty($wrappers[$scheme]);
1314
}
1315
1316
/**
1317
 * Implements hook_file_formatter_FORMATTER_settings().
1318
 *
1319
 * Returns form elements for configuring the 'file_image' formatter.
1320
 */
1321
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
1322
  $element = array();
1323
  $element['image_style'] = array(
1324
    '#title' => t('Image style'),
1325
    '#type' => 'select',
1326
    '#options' => image_style_options(FALSE),
1327
    '#default_value' => $settings['image_style'],
1328
    '#empty_option' => t('None (original image)'),
1329
  );
1330
1331
  // For image files we allow the alt attribute (required in HTML).
1332
  $element['alt'] = array(
1333
    '#title' => t('Alt attribute'),
1334
    '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
1335
    '#type' => 'textfield',
1336
    '#default_value' => $settings['alt'],
1337
  );
1338
1339
  // Allow the setting of the title attribute.
1340
  $element['title'] = array(
1341
    '#title' => t('Title attribute'),
1342
    '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
1343
    '#type' => 'textfield',
1344
    '#default_value' => $settings['title'],
1345
  );
1346
1347
  if (module_exists('token')) {
1348
    $element['alt']['#description'] .= t('This field supports tokens.');
1349
    $element['title']['#description'] .= t('This field supports tokens.');
1350
    $element['tokens'] = array(
1351
      '#theme' => 'token_tree',
1352
      '#token_types' => array('file'),
1353
      '#dialog' => TRUE,
1354
    );
1355
  }
1356
1357
  return $element;
1358
}
1359
1360
/**
1361
 * Menu access callback for the 'view mode file display settings' pages.
1362
 *
1363
 * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
1364
 * be enabled.
1365
 */
1366
function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
1367
  // Deny access if the view mode isn't configured to use custom display
1368
  // settings.
1369
  $view_mode_settings = field_view_mode_settings('file', $file_type->type);
1370
  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
1371
  if (!$visibility) {
1372
    return FALSE;
1373
  }
1374
1375
  // Otherwise, continue to an $access_callback check.
1376
  $args = array_slice(func_get_args(), 3);
1377
  $callback = empty($access_callback) ? 0 : trim($access_callback);
1378
  if (is_numeric($callback)) {
1379
    return (bool) $callback;
1380
  }
1381
  elseif (function_exists($access_callback)) {
1382
    return call_user_func_array($access_callback, $args);
1383
  }
1384
}
1385
1386
/**
1387
 * Implements hook_modules_enabled().
1388
 */
1389
function file_entity_modules_enabled($modules) {
1390
  file_info_cache_clear();
1391
}
1392
1393
/**
1394
 * Implements hook_modules_disabled().
1395
 */
1396
function file_entity_modules_disabled($modules) {
1397
  file_info_cache_clear();
1398
}
1399
1400
/**
1401
 * Implements hook_views_api().
1402
 */
1403
function file_entity_views_api() {
1404
  return array(
1405
    'api' => 3,
1406
  );
1407
}
1408
1409
/**
1410
 * Returns whether the current page is the full page view of the passed-in file.
1411
 *
1412
 * @param $file
1413
 *   A file object.
1414
 */
1415
function file_entity_is_page($file) {
1416
  $page_file = menu_get_object('file', 1);
1417
  return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
1418
}
1419
1420
/**
1421
 * Process variables for file_entity.tpl.php
1422
 *
1423
 * The $variables array contains the following arguments:
1424
 * - $file
1425
 * - $view_mode
1426
 *
1427
 * @see file_entity.tpl.php
1428
 */
1429
function template_preprocess_file_entity(&$variables) {
1430
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
1431
  $variables['file'] = $variables['elements']['#file'];
1432
  $file = $variables['file'];
1433
1434
  $variables['id'] = drupal_html_id('file-'. $file->fid);
1435
  $variables['date']      = format_date($file->timestamp);
1436
  $account = user_load($file->uid);
1437
  $variables['name']      = theme('username', array('account' => $account));
1438
1439
  $uri = entity_uri('file', $file);
1440
  $variables['file_url']  = url($uri['path'], $uri['options']);
1441
  $label = entity_label('file', $file);
1442
  $variables['label']     = check_plain($label);
1443
  $variables['page']      = $view_mode == 'full' && file_entity_is_page($file);
1444
1445
  // Hide the file name from being displayed until we can figure out a better
1446
  // way to control this. We cannot simply not output the title since
1447
  // contextual links require $title_suffix to be output in the template.
1448
  // @see http://drupal.org/node/1245266
1449
  if (!$variables['page']) {
1450
    $variables['title_attributes_array']['class'][] = 'element-invisible';
1451
  }
1452
1453
  // Flatten the file object's member fields.
1454
  $variables = array_merge((array) $file, $variables);
1455
1456
  // Helpful $content variable for templates.
1457
  $variables += array('content' => array());
1458
  foreach (element_children($variables['elements']) as $key) {
1459
    $variables['content'][$key] = $variables['elements'][$key];
1460
  }
1461
1462
  // Make the field variables available with the appropriate language.
1463
  field_attach_preprocess('file', $file, $variables['content'], $variables);
1464
1465
  // Attach the file object to the content element.
1466
  $variables['content']['file']['#file'] = $file;
1467
1468
  // Display post information only on certain file types.
1469
  if (variable_get('file_submitted_' . $file->type, FALSE)) {
1470
    $variables['display_submitted'] = TRUE;
1471
    $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1472
    $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
1473
  }
1474
  else {
1475
    $variables['display_submitted'] = FALSE;
1476
    $variables['submitted'] = '';
1477
    $variables['user_picture'] = '';
1478
  }
1479
1480
  // Gather file classes.
1481
  $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
1482
  $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
1483
  if ($file->status != FILE_STATUS_PERMANENT) {
1484
    $variables['classes_array'][] = 'file-temporary';
1485
  }
1486
1487
  // Change the 'file-entity' class into 'file'
1488
  if ($variables['classes_array'][0] == 'file-entity') {
1489
    $variables['classes_array'][0] = 'file';
1490
  }
1491
1492
  // Clean up name so there are no underscores.
1493
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
1494
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
1495
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
1496
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
1497
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
1498
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
1499
}
1500
1501
/**
1502
 * Returns the file type name of the passed file or file type string.
1503
 *
1504
 * @param $file
1505
 *   A file object or string that indicates the file type to return.
1506
 *
1507
 * @return
1508
 *   The file type name or FALSE if the file type is not found.
1509
 */
1510
function file_entity_type_get_name($file) {
1511
  $type = is_object($file) ? $file->type : $file;
1512
  $info = entity_get_info('file');
1513
  return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
1514
}
1515
1516
/**
1517
 * Returns a list of available file type names.
1518
 *
1519
 * @return
1520
 *   An array of file type names, keyed by the type.
1521
 */
1522
function file_entity_type_get_names() {
1523
  $names = &drupal_static(__FUNCTION__);
1524
1525
  if (!isset($names)) {
1526 1f142f4f Florent Torregrosa
    $names = array();
1527 85ad3d82 Assos Assos
    $info = entity_get_info('file');
1528
    foreach ($info['bundles'] as $bundle => $bundle_info) {
1529
      $names[$bundle] = $bundle_info['label'];
1530
    }
1531
  }
1532
1533
  return $names;
1534
}
1535
1536
/**
1537
 * Return an array of available view modes for file entities.
1538
 */
1539
function file_entity_view_mode_labels() {
1540
  $labels = &drupal_static(__FUNCTION__);
1541
1542
  if (!isset($options)) {
1543
    $entity_info = entity_get_info('file');
1544
    $labels = array('default' => t('Default'));
1545
    foreach ($entity_info['view modes'] as $machine_name => $mode) {
1546
      $labels[$machine_name] = $mode['label'];
1547
    }
1548
  }
1549
1550
  return $labels;
1551
}
1552
1553
/**
1554
 * Return the label for a specific file entity view mode.
1555
 */
1556
function file_entity_view_mode_label($view_mode, $default = FALSE) {
1557
  $labels = file_entity_view_mode_labels();
1558
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
1559
}
1560
1561
/**
1562
 * Helper function to get a list of hidden stream wrappers.
1563
 *
1564
 * This is used in several places to filter queries for media so that files in
1565
 * temporary:// don't show up.
1566
 */
1567
function file_entity_get_hidden_stream_wrappers() {
1568
  return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
1569
}
1570
1571
/**
1572
 * Return a specific stream wrapper's registry information.
1573
 *
1574
 * @param $scheme
1575
 *   A URI scheme, a stream is referenced as "scheme://target".
1576
 *
1577
 * @see file_get_stream_wrappers()
1578
 */
1579
function file_entity_get_stream_wrapper($scheme) {
1580
  $wrappers = file_get_stream_wrappers();
1581
  return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
1582
}
1583
1584
/**
1585
 * Implements hook_stream_wrappers_alter().
1586
 */
1587
function file_entity_stream_wrappers_alter(&$wrappers) {
1588
  if (isset($wrappers['private'])) {
1589
    $wrappers['private']['private'] = TRUE;
1590
  }
1591
  if (isset($wrappers['temporary'])) {
1592
    $wrappers['temporary']['private'] = TRUE;
1593
  }
1594
}
1595
1596
/**
1597
 * Implements hook_ctools_plugin_api().
1598
 */
1599 1f142f4f Florent Torregrosa
function file_entity_ctools_plugin_api($module, $api) {
1600
  if (($module == 'file_entity' && $api == 'file_type') || ($module == 'page_manager' && $api == 'pages_default') || $module == 'panelizer') {
1601 85ad3d82 Assos Assos
    return array('version' => 1);
1602
  }
1603 1f142f4f Florent Torregrosa
  if ($module == 'file_entity' && $api == 'file_default_displays') {
1604 85ad3d82 Assos Assos
    return array('version' => 1);
1605
  }
1606
}
1607
1608
/**
1609
 * @defgroup file_entity_access File access rights
1610
 * @{
1611
 * The file access system determines who can do what to which files.
1612
 *
1613
 * In determining access rights for a file, file_entity_access() first checks
1614
 * whether the user has the "bypass file access" permission. Such users have
1615
 * unrestricted access to all files. user 1 will always pass this check.
1616
 *
1617
 * Next, all implementations of hook_file_entity_access() will be called. Each
1618
 * implementation may explicitly allow, explicitly deny, or ignore the access
1619
 * request. If at least one module says to deny the request, it will be rejected.
1620
 * If no modules deny the request and at least one says to allow it, the request
1621
 * will be permitted.
1622
 *
1623
 * There is no access grant system for files.
1624
 *
1625
 * In file listings, the process above is followed except that
1626
 * hook_file_entity_access() is not called on each file for performance reasons
1627
 * and for proper functioning of the pager system. When adding a filelisting to
1628
 * your module, be sure to use a dynamic query created by db_select()
1629
 * and add a tag of "file_entity_access". This will allow modules dealing
1630
 * with file access to ensure only files to which the user has access
1631
 * are retrieved, through the use of hook_query_TAG_alter().
1632
 *
1633
 * Note: Even a single module returning FILE_ENTITY_ACCESS_DENY from
1634
 * hook_file_entity_access() will block access to the file. Therefore,
1635
 * implementers should take care to not deny access unless they really intend to.
1636
 * Unless a module wishes to actively deny access it should return
1637
 * FILE_ENTITY_ACCESS_IGNORE (or simply return nothing)
1638
 * to allow other modules to control access.
1639
 *
1640
 * Stream wrappers that are considered private should implement a 'private'
1641
 * flag equal to TRUE in hook_stream_wrappers().
1642
 */
1643
1644
/**
1645
 * Determine if a user may perform the given operation on the specified file.
1646
 *
1647
 * @param $op
1648
 *   The operation to be performed on the file. Possible values are:
1649
 *   - "view"
1650
 *   - "download"
1651
 *   - "update"
1652
 *   - "delete"
1653
 *   - "create"
1654
 * @param $file
1655
 *   The file object on which the operation is to be performed, or file type
1656
 *   (e.g. 'image') for "create" operation.
1657
 * @param $account
1658
 *   Optional, a user object representing the user for whom the operation is to
1659
 *   be performed. Determines access for a user other than the current user.
1660
 *
1661
 * @return
1662
 *   TRUE if the operation may be performed, FALSE otherwise.
1663
 */
1664
function file_entity_access($op, $file = NULL, $account = NULL) {
1665
  $rights = &drupal_static(__FUNCTION__, array());
1666
1667
  if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
1668
    // If there was no file to check against, and the $op was not one of the
1669
    // supported ones, we return access denied.
1670
    return FALSE;
1671
  }
1672
1673
  // If no user object is supplied, the access check is for the current user.
1674
  if (empty($account)) {
1675
    $account = $GLOBALS['user'];
1676
  }
1677
1678
  // $file may be either an object or a file type. Since file types cannot be
1679
  // an integer, use either fid or type as the static cache id.
1680
  $cache_id = is_object($file) ? $file->fid : $file;
1681
1682
  // If we've already checked access for this file, user and op, return from
1683
  // cache.
1684
  if (isset($rights[$account->uid][$cache_id][$op])) {
1685
    return $rights[$account->uid][$cache_id][$op];
1686
  }
1687
1688
  if (user_access('bypass file access', $account)) {
1689
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1690
  }
1691
1692
  // We grant access to the file if both of the following conditions are met:
1693
  // - No modules say to deny access.
1694
  // - At least one module says to grant access.
1695
  $access = module_invoke_all('file_entity_access', $op, $file, $account);
1696
  if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
1697
    return $rights[$account->uid][$cache_id][$op] = FALSE;
1698
  }
1699
  elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
1700
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1701
  }
1702
1703
1704
  // Fall back to default behaviors on view.
1705
  if ($op == 'view' && is_object($file)) {
1706
    $scheme = file_uri_scheme($file->uri);
1707
    $wrapper = file_entity_get_stream_wrapper($scheme);
1708
1709
    if (!empty($wrapper['private'])) {
1710
      // For private files, users can view private files if the
1711
      // user has the 'view private files' permission.
1712
      if (user_access('view private files', $account)) {
1713
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1714
      }
1715
1716
      // For private files, users can view their own private files if the
1717
      // user is not anonymous, and has the 'view own private files' permission.
1718
      if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
1719
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1720
      }
1721
    }
1722
    elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
1723
      // For non-private files, allow to see if user owns the file.
1724
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1725
    }
1726
    elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
1727
      // For non-private files, users can view if they have the 'view files'
1728
      // permission.
1729
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1730
    }
1731
  }
1732
1733
  return FALSE;
1734
}
1735
1736
/**
1737
 * Implements hook_file_entity_access().
1738
 */
1739
function file_entity_file_entity_access($op, $file, $account) {
1740
  // If the file URI is invalid, deny access.
1741
  if (is_object($file) && !file_valid_uri($file->uri)) {
1742
    return FILE_ENTITY_ACCESS_DENY;
1743
  }
1744
1745
  if ($op == 'create') {
1746
    if (user_access('create files')) {
1747
      return FILE_ENTITY_ACCESS_ALLOW;
1748
    }
1749
  }
1750
1751
  if (!empty($file)) {
1752
    $type = is_string($file) ? $file : $file->type;
1753
1754
    if (in_array($type, file_entity_permissions_get_configured_types())) {
1755
      if ($op == 'download') {
1756
        if (user_access('download any ' . $type . ' files', $account) || is_object($file) && user_access('download own ' . $type . ' files', $account) && ($account->uid == $file->uid)) {
1757
          return FILE_ENTITY_ACCESS_ALLOW;
1758
        }
1759
      }
1760
1761
      if ($op == 'update') {
1762
        if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1763
          return FILE_ENTITY_ACCESS_ALLOW;
1764
        }
1765
      }
1766
1767
      if ($op == 'delete') {
1768
        if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1769
          return FILE_ENTITY_ACCESS_ALLOW;
1770
        }
1771
      }
1772
    }
1773
  }
1774
1775
  return FILE_ENTITY_ACCESS_IGNORE;
1776
}
1777
1778
/**
1779
 * Implements hook_query_TAG_alter().
1780
 *
1781
 * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
1782
 * file access checks for the user account given by the 'account' meta-data (or
1783
 * global $user if not provided).
1784
 */
1785
function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
1786
  _file_entity_query_file_entity_access_alter($query, 'file');
1787
}
1788
1789
/**
1790
 * Implements hook_query_TAG_alter().
1791
 *
1792
 * This function implements the same functionality as
1793
 * file_entity_query_file_access_alter() for the SQL field storage engine. File
1794
 * access conditions are added for field values belonging to files only.
1795
 */
1796
function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
1797 59ae487e Assos Assos
  _file_entity_query_file_entity_access_alter($query, 'entity');
1798 85ad3d82 Assos Assos
}
1799
1800
/**
1801
 * Helper for file entity access functions.
1802
 *
1803
 * @param $query
1804
 *   The query to add conditions to.
1805
 * @param $type
1806
 *   Either 'file' or 'entity' depending on what sort of query it is. See
1807
 *   file_entity_query_file_entity_access_alter() and
1808
 *   file_entity_query_entity_field_access_alter() for more.
1809
 */
1810
function _file_entity_query_file_entity_access_alter($query, $type) {
1811
  global $user;
1812
1813
  // Read meta-data from query, if provided.
1814
  if (!$account = $query->getMetaData('account')) {
1815
    $account = $user;
1816
  }
1817
1818
  // If $account can bypass file access, we don't need to alter the query.
1819
  if (user_access('bypass file access', $account)) {
1820
    return;
1821
  }
1822
1823
  $tables = $query->getTables();
1824
  $base_table = $query->getMetaData('base_table');
1825 59ae487e Assos Assos
  // Do not use the base table for general entity queries unless this is
1826
  // querying the file_managed table directly.
1827
  if ($base_table && $type == 'entity' && $base_table != 'file_managed') {
1828
    $base_table = '';
1829
  }
1830
1831 85ad3d82 Assos Assos
  // If no base table is specified explicitly, search for one.
1832
  if (!$base_table) {
1833
    $fallback = '';
1834
    foreach ($tables as $alias => $table_info) {
1835
      if (!($table_info instanceof SelectQueryInterface)) {
1836
        $table = $table_info['table'];
1837
        // If the file_managed table is in the query, it wins immediately.
1838
        if ($table == 'file_managed') {
1839
          $base_table = $table;
1840
          break;
1841
        }
1842
        // Check whether the table has a foreign key to file_managed.fid. If it
1843
        // does, do not run this check again as we found a base table and only
1844
        // file_managed can triumph that.
1845
        if (!$base_table) {
1846
          // The schema is cached.
1847
          $schema = drupal_get_schema($table);
1848
          if (isset($schema['fields']['fid'])) {
1849
            if (isset($schema['foreign keys'])) {
1850
              foreach ($schema['foreign keys'] as $relation) {
1851
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
1852
                  $base_table = $table;
1853
                }
1854
              }
1855
            }
1856
            else {
1857
              // At least it's a fid. A table with a field called fid is very
1858
              // very likely to be a file_managed.fid in a file access query.
1859
              $fallback = $table;
1860
            }
1861
          }
1862 59ae487e Assos Assos
          elseif (isset($schema['fields']['entity_id']) && isset($schema['fields']['entity_type']) && isset($schema['fields']['deleted']) && isset($schema['fields']['delta'])) {
1863
            // The table is a field data table, use it as fallback.
1864
            $base_table = $table;
1865
          }
1866 85ad3d82 Assos Assos
        }
1867
      }
1868
    }
1869
    // If there is nothing else, use the fallback.
1870
    if (!$base_table) {
1871
      if ($fallback) {
1872
        watchdog('security', 'Your file listing query is using @fallback as a base table in a query tagged for file access. This might not be secure and might not even work. Specify foreign keys in your schema to file_managed.fid ', array('@fallback' => $fallback), WATCHDOG_WARNING);
1873
        $base_table = $fallback;
1874
      }
1875
      else {
1876 59ae487e Assos Assos
        // Ignore this query as it was a general field query and no
1877
        // relationships were found to the file_managed table.
1878
        if ($type == 'entity') {
1879
          return;
1880
        }
1881 85ad3d82 Assos Assos
        throw new Exception(t('Query tagged for file access but there is no fid. Add foreign keys to file_managed.fid in schema to fix.'));
1882
      }
1883
    }
1884
  }
1885
1886
  if ($type == 'entity') {
1887
    // The original query looked something like:
1888
    // @code
1889
    //  SELECT fid FROM sometable s
1890
    //  WHERE ($file_access_conditions)
1891
    // @endcode
1892
    //
1893
    // Our query will look like:
1894
    // @code
1895
    //  SELECT entity_type, entity_id
1896
    //  FROM field_data_something s
1897
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1898
    // @endcode
1899
    //
1900
    // So instead of directly adding to the query object, we need to collect
1901
    // all of the file access conditions in a separate db_and() object and
1902
    // then add it to the query at the end.
1903
    $file_conditions = db_and();
1904
  }
1905
  foreach ($tables as $falias => $tableinfo) {
1906
    $table = $tableinfo['table'];
1907
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
1908
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
1909
      $subquery_conditions = db_or();
1910
1911
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
1912
      if (!empty($wrappers['public'])) {
1913
        if (user_access('view files', $account)) {
1914
          foreach (array_keys($wrappers['public']) as $wrapper) {
1915
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1916
          }
1917
        }
1918
        elseif (user_access('view own files', $account)) {
1919
          foreach (array_keys($wrappers['public']) as $wrapper) {
1920
            $subquery_conditions->condition(db_and()
1921
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1922
              ->condition('fm_access.uid', $account->uid)
1923
            );
1924
          }
1925
        }
1926
      }
1927
      if (!empty($wrappers['private'])) {
1928
        if (user_access('view private files', $account)) {
1929
          foreach (array_keys($wrappers['private']) as $wrapper) {
1930
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1931
          }
1932
        }
1933
        elseif (user_access('view own private files', $account)) {
1934
          foreach (array_keys($wrappers['private']) as $wrapper) {
1935
            $subquery_conditions->condition(db_and()
1936
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1937
              ->condition('fm_access.uid', $account->uid)
1938
            );
1939
          }
1940
        }
1941
      }
1942
1943 59ae487e Assos Assos
      // If there is no subquery conditions, the query is likely for file usage.
1944
      // Or user can only access public files.
1945
      // If there are subquery conditions then add them to the subquery.
1946
      if ($subquery_conditions->count() >= 1) {
1947 85ad3d82 Assos Assos
        $subquery->condition($subquery_conditions);
1948 59ae487e Assos Assos
      }
1949 85ad3d82 Assos Assos
1950 59ae487e Assos Assos
      $field = 'fid';
1951
      // Now handle entities.
1952
      if ($type == 'entity') {
1953
        // Set a common alias for entities.
1954
        $base_alias = $falias;
1955
        $field = ($falias == 'file_managed' ? 'fid' : 'entity_id');
1956
      }
1957
      $subquery->where("$falias.$field = fm_access.fid");
1958 85ad3d82 Assos Assos
1959 59ae487e Assos Assos
      // For an entity query, attach the subquery to entity conditions.
1960
      if ($type == 'entity') {
1961
        $file_conditions->exists($subquery);
1962
      }
1963
      // Otherwise attach it to the node query itself.
1964
      elseif ($table == 'file_managed') {
1965
        // Fix for https://drupal.org/node/2073085
1966
        $db_or = db_or();
1967
        $db_or->exists($subquery);
1968
        $db_or->isNull($falias . '.' . $field);
1969
        $query->condition($db_or);
1970
      }
1971
      else {
1972
        $query->exists($subquery);
1973 85ad3d82 Assos Assos
      }
1974
    }
1975
  }
1976
1977
  if ($type == 'entity' && $file_conditions->count()) {
1978
    // All the file access conditions are only for field values belonging to
1979
    // files.
1980 59ae487e Assos Assos
    if ($base_alias !== 'file_managed') {
1981
      $file_conditions->condition("$base_alias.entity_type", 'file');
1982
      $or = db_or();
1983
      $or->condition($file_conditions);
1984
      // If the field value belongs to a non-file entity type then this function
1985
      // does not do anything with it.
1986
      $or->condition("$base_alias.entity_type", 'file', '<>');
1987
      // Add the compiled set of rules to the query.
1988
      $query->condition($or);
1989
    }
1990
    else {
1991
      $query->condition($file_conditions);
1992
    }
1993 85ad3d82 Assos Assos
  }
1994
}
1995
1996
/**
1997
 * Implements hook_file_download().
1998
 */
1999
function file_entity_file_download($uri) {
2000
  // Load the file from the URI.
2001
  $file = file_uri_to_object($uri);
2002
2003
  // An existing file wasn't found, so we don't control access.
2004
  // E.g. image derivatives will fall here.
2005
  if (empty($file->fid)) {
2006
    return NULL;
2007
  }
2008
2009
  // Allow the user to download the file if they have appropriate permissions.
2010
  if (file_entity_access('view', $file)) {
2011
    return file_get_content_headers($file);
2012
  }
2013
2014 1f142f4f Florent Torregrosa
  return NULL;
2015 85ad3d82 Assos Assos
}
2016
2017
/**
2018
 * Helper function to generate standard file permission list for a given type.
2019
 *
2020
 * @param $type
2021
 *   The machine-readable name of the file type.
2022
 * @return array
2023
 *   An array of permission names and descriptions.
2024
 */
2025
function file_entity_list_permissions($type) {
2026
  $info = file_type_load($type);
2027
2028
  // Build standard list of file permissions for this type.
2029
  $permissions = array(
2030
    "edit own $type files" => array(
2031
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
2032
    ),
2033
    "edit any $type files" => array(
2034
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
2035
    ),
2036
    "delete own $type files" => array(
2037
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
2038
    ),
2039
    "delete any $type files" => array(
2040
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
2041
    ),
2042
    "download own $type files" => array(
2043
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
2044
    ),
2045
    "download any $type files" => array(
2046
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
2047
    ),
2048
  );
2049
2050
  return $permissions;
2051
}
2052
2053
/**
2054
 * Returns an array of file types that should be managed by permissions.
2055
 *
2056
 * By default, this will include all file types in the system. To exclude a
2057
 * specific file from getting permissions defined for it, set the
2058
 * file_entity_permissions_$type variable to 0. File entity does not provide an
2059
 * interface for doing so, however, contrib modules may exclude their own files
2060
 * in hook_install(). Alternatively, contrib modules may configure all file
2061
 * types at once, or decide to apply some other hook_file_entity_access()
2062
 * implementation to some or all file types.
2063
 *
2064
 * @return
2065
 *   An array of file types managed by this module.
2066
 */
2067
function file_entity_permissions_get_configured_types() {
2068
2069
  $configured_types = array();
2070
2071
  foreach (file_type_get_enabled_types() as $type => $info) {
2072
    if (variable_get('file_entity_permissions_' . $type, 1)) {
2073
      $configured_types[] = $type;
2074
    }
2075
  }
2076
2077
  return $configured_types;
2078
}
2079
2080
/**
2081
 * @} End of "defgroup file_entity_access".
2082
 *
2083
 * Implements hook_file_default_types().
2084
 */
2085
function file_entity_file_default_types() {
2086
  $types = array();
2087
2088
  // Image.
2089
  $types['image'] = (object) array(
2090
    'api_version' => 1,
2091
    'type' => 'image',
2092
    'label' => t('Image'),
2093
    'description' => t('An <em>Image</em> file is a still visual.'),
2094
    'mimetypes' => array(
2095
      'image/*',
2096
    ),
2097
  );
2098
2099
  // Video.
2100
  $types['video'] = (object) array(
2101
    'api_version' => 1,
2102
    'type' => 'video',
2103
    'label' => t('Video'),
2104
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2105
    'mimetypes' => array(
2106
      'video/*',
2107
    ),
2108
  );
2109
2110
  // Audio.
2111
  $types['audio'] = (object) array(
2112
    'api_version' => 1,
2113
    'type' => 'audio',
2114
    'label' => t('Audio'),
2115
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2116
    'mimetypes' => array(
2117
      'audio/*',
2118
    ),
2119
  );
2120
2121
  // Document.
2122
  $types['document'] = (object) array(
2123
    'api_version' => 1,
2124
    'type' => 'document',
2125
    'label' => t('Document'),
2126
    'description' => t('A <em>Document</em> file is written information.'),
2127
    'mimetypes' => array(
2128
      'text/plain',
2129
      'application/msword',
2130
      'application/vnd.ms-excel',
2131
      'application/pdf',
2132
      'application/vnd.ms-powerpoint',
2133
      'application/vnd.oasis.opendocument.text',
2134
      'application/vnd.oasis.opendocument.spreadsheet',
2135
      'application/vnd.oasis.opendocument.presentation',
2136
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2137
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2138
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2139
    ),
2140
  );
2141
2142
  return $types;
2143
}
2144
2145
/**
2146
 * Implements hook_file_operations().
2147
 */
2148
function file_entity_file_operations() {
2149
  $operations = array(
2150
    'permanent' => array(
2151
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2152
      'callback' => 'file_entity_mass_update',
2153
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2154
    ),
2155
    'temporary' => array(
2156
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2157
      'callback' => 'file_entity_mass_update',
2158
      'callback arguments' => array('updates' => array('status' => 0)),
2159
    ),
2160
    'delete' => array(
2161
      'label' => t('Delete selected files'),
2162
      'callback' => NULL,
2163
    ),
2164
  );
2165
  return $operations;
2166
}
2167
2168
/**
2169
 * Clear the field cache for any entities referencing a specific file.
2170
 *
2171
 * @param object $file
2172
 *   A file object.
2173
 */
2174
function file_entity_invalidate_field_caches($file) {
2175
  $entity_types = &drupal_static(__FUNCTION__);
2176
2177
  // Gather the list of entity types which support field caching.
2178
  if (!isset($entity_types)) {
2179
    $entity_types = array();
2180
    foreach (entity_get_info() as $entity_type => $entity_info) {
2181
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2182
        $entity_types[] = $entity_type;
2183
      }
2184
    }
2185
  }
2186
2187
  // If no entity types support field caching, then there is no work to be done.
2188
  if (empty($entity_types)) {
2189
    return;
2190
  }
2191
2192
  $records = db_query("SELECT DISTINCT type, id FROM {file_usage} WHERE fid = :fid AND type IN (:types) AND id > 0", array(':fid' => $file->fid, ':types' => $entity_types))->fetchAll();
2193
  if (!empty($records)) {
2194
    $cids = array();
2195
    foreach ($records as $record) {
2196
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2197
    }
2198
    cache_clear_all($cids, 'cache_field');
2199
  }
2200
}
2201
2202
/**
2203
 * Check if a file entity is considered local or not.
2204
 *
2205
 * @param object $file
2206
 *   A file entity object from file_load().
2207
 *
2208
 * @return
2209
 *   TRUE if the file is using a local stream wrapper, or FALSE otherwise.
2210
 */
2211
function file_entity_file_is_local($file) {
2212
  $scheme = file_uri_scheme($file->uri);
2213
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
2214
  return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
2215
}
2216
2217
/**
2218
 * Check if a file entity is considered writeable or not.
2219
 *
2220
 * @param object $file
2221
 *   A file entity object from file_load().
2222
 *
2223
 * @return
2224
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2225
 *   or FALSE otherwise.
2226
 */
2227
function file_entity_file_is_writeable($file) {
2228
  $scheme = file_uri_scheme($file->uri);
2229
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2230
  return !empty($wrappers[$scheme]);
2231
}
2232
2233
/**
2234
 * Pre-render callback for adding validation descriptions to file upload fields.
2235
 */
2236
function file_entity_upload_validators_pre_render($element) {
2237
  if (!empty($element['#upload_validators'])) {
2238
    if (!isset($element['#description'])) {
2239
      $element['#description'] = '';
2240
    }
2241
    if ($element['#description'] !== FALSE) {
2242
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2243
    }
2244
  }
2245
  return $element;
2246
}
2247
2248
/**
2249
 * @name pathauto_file Pathauto integration for the core file module.
2250
 * @{
2251
 */
2252
2253
/**
2254
 * Implements hook_file_insert() on behalf of pathauto.module.
2255
 */
2256
function pathauto_file_insert($file) {
2257
  pathauto_file_update_alias($file, 'insert');
2258
}
2259
2260
/**
2261
 * Implements hook_file_update() on behalf of pathauto.module.
2262
 */
2263
function pathauto_file_update($file) {
2264
  pathauto_file_update_alias($file, 'update');
2265
}
2266
2267
/**
2268
 * Implements hook_file_delete() on behalf of pathauto.module.
2269
 */
2270
function pathauto_file_delete($file) {
2271
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2272
}
2273
2274
/**
2275
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2276
 *
2277
 * Add the Pathauto settings to the file form.
2278
 */
2279
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2280
  $file = $form_state['file'];
2281
  $langcode = pathauto_entity_language('file', $file);
2282
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2283
}
2284
2285
/**
2286
 * Implements hook_file_operations() on behalf of pathauto.module.
2287
 */
2288
function pathauto_file_operations() {
2289
  $operations['pathauto_update_alias'] = array(
2290
    'label' => t('Update URL alias'),
2291
    'callback' => 'pathauto_file_update_alias_multiple',
2292
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2293
  );
2294
  return $operations;
2295
}
2296
2297
/**
2298
 * Update the URL aliases for an individual file.
2299
 *
2300
 * @param $file
2301
 *   A file object.
2302
 * @param $op
2303
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2304
 * @param $options
2305
 *   An optional array of additional options.
2306
 */
2307
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2308
  // Skip processing if the user has disabled pathauto for the file.
2309 ca0757b9 Assos Assos
  if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
2310 85ad3d82 Assos Assos
    return;
2311
  }
2312
2313
  $options += array('language' => pathauto_entity_language('file', $file));
2314
2315
  // Skip processing if the file has no pattern.
2316
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2317
    return;
2318
  }
2319
2320
  module_load_include('inc', 'pathauto');
2321
  $uri = entity_uri('file', $file);
2322
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2323
}
2324
2325
/**
2326
 * Update the URL aliases for multiple files.
2327
 *
2328
 * @param $fids
2329
 *   An array of file IDs.
2330
 * @param $op
2331
 *   Operation being performed on the files ('insert', 'update' or
2332
 *   'bulkupdate').
2333
 * @param $options
2334
 *   An optional array of additional options.
2335
 */
2336
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2337
  $options += array('message' => FALSE);
2338
2339
  $files = file_load_multiple($fids);
2340
  foreach ($files as $file) {
2341
    pathauto_file_update_alias($file, $op, $options);
2342
  }
2343
2344
  if (!empty($options['message'])) {
2345
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2346
  }
2347
}
2348
2349
/**
2350
 * Update action wrapper for pathauto_file_update_alias().
2351
 */
2352
function pathauto_file_update_action($file, $context = array()) {
2353
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2354
}
2355
2356
/**
2357
 * @} End of "name pathauto_file".
2358
 */
2359
2360
/**
2361
 * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
2362
 */
2363
function path_form_file_entity_edit_alter(&$form, $form_state) {
2364
  // Make sure this does not show up on the delete confirmation form.
2365
  if (empty($form_state['confirm_delete'])) {
2366
    $file = $form_state['file'];
2367
    $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
2368
    $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2369
    $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
2370
    $path = (isset($file->fid) ? path_load($conditions) : array());
2371
    if ($path === FALSE) {
2372
      $path = array();
2373
    }
2374
    $path += array(
2375
      'pid' => NULL,
2376
      'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
2377
      'alias' => '',
2378
      'language' => $langcode,
2379
    );
2380
    $form['path'] = array(
2381
      '#type' => 'fieldset',
2382
      '#title' => t('URL path settings'),
2383
      '#collapsible' => TRUE,
2384
      '#collapsed' => empty($path['alias']),
2385
      '#group' => 'additional_settings',
2386
      '#attributes' => array(
2387
        'class' => array('path-form'),
2388
      ),
2389
      '#attached' => array(
2390
        'js' => array(drupal_get_path('module', 'path') . '/path.js'),
2391
      ),
2392
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
2393
      '#weight' => 30,
2394
      '#tree' => TRUE,
2395
      '#element_validate' => array('path_form_element_validate'),
2396
    );
2397
    $form['path']['alias'] = array(
2398
      '#type' => 'textfield',
2399
      '#title' => t('URL alias'),
2400
      '#default_value' => $path['alias'],
2401
      '#maxlength' => 255,
2402
      '#description' => t('Optionally specify an alternative URL by which this file can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'),
2403
    );
2404
    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
2405
    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
2406
    $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
2407
  }
2408
}
2409
2410
/**
2411
 * Implements hook_file_insert() on behalf of path.module.
2412
 */
2413
function path_file_insert($file) {
2414
  if (isset($file->path)) {
2415
    $path = $file->path;
2416
    $path['alias'] = trim($path['alias']);
2417
    // Only save a non-empty alias.
2418
    if (!empty($path['alias'])) {
2419
      // Ensure fields for programmatic executions.
2420
      $path['source'] = 'file/' . $file->fid;
2421
      // Core does not provide a way to store the file language but contrib
2422
      // modules can do it so we need to take this into account.
2423
      $langcode = entity_language('file', $file);
2424
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2425
      path_save($path);
2426
    }
2427
  }
2428
}
2429
2430
/**
2431
 * Implements hook_file_update() on behalf of path.module.
2432
 */
2433
function path_file_update($file) {
2434
  if (isset($file->path)) {
2435
    $path = $file->path;
2436
    $path['alias'] = trim($path['alias']);
2437
    // Delete old alias if user erased it.
2438
    if (!empty($path['fid']) && empty($path['alias'])) {
2439
      path_delete($path['fid']);
2440
    }
2441
    // Only save a non-empty alias.
2442
    if (!empty($path['alias'])) {
2443
      // Ensure fields for programmatic executions.
2444
      $path['source'] = 'file/' . $file->fid;
2445
      // Core does not provide a way to store the file language but contrib
2446
      // modules can do it so we need to take this into account.
2447
      $langcode = entity_language('file', $file);
2448
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2449
      path_save($path);
2450
    }
2451
  }
2452
}
2453
2454
/**
2455
 * Implements hook_file_delete() on behalf of path.module.
2456
 */
2457
function path_file_delete($file) {
2458
  // Delete all aliases associated with this file.
2459
  path_delete(array('source' => 'file/' . $file->fid));
2460
}
2461
2462
/**
2463
 * Checks if pattern(s) match mimetype(s).
2464
 */
2465
function file_entity_match_mimetypes($needle, $haystack) {
2466
  $needle = is_array($needle) ? $needle : array($needle);
2467
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2468
2469
  foreach ($haystack as $mimetype) {
2470
    foreach ($needle as $search) {
2471
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2472
        return TRUE;
2473
      }
2474
    }
2475
  }
2476
2477
  return FALSE;
2478
}
2479
2480
/**
2481
 * A wrapper function for the PHP function fnmatch().
2482
 *
2483
 * We include this, because Windows servers do not implement fnmatch() until
2484
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2485
 */
2486
function file_entity_fnmatch($pattern, $string) {
2487
  if (!function_exists('fnmatch')) {
2488
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2489
  }
2490
  return fnmatch($pattern, $string);
2491
}
2492
2493
/**
2494
 * Return an URI for a file download.
2495
 */
2496
function file_entity_download_uri($file) {
2497
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2498
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2499
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2500
  }
2501
  return $uri;
2502
}
2503
2504
function file_entity_file_get_mimetype_type($file) {
2505 59ae487e Assos Assos
  if (is_array($file)) {
2506
    $file = (object) $file;
2507
  }
2508 85ad3d82 Assos Assos
  list($type, $subtype) = explode('/', $file->filemime, 2);
2509
  return $type;
2510
}
2511
2512
/**
2513
 * Implements hook_admin_menu_map().
2514
 */
2515
function file_entity_admin_menu_map() {
2516
  if (!user_access('administer file types')) {
2517
    return;
2518
  }
2519
  $map['admin/structure/file-types/manage/%file_type'] = array(
2520
    'parent' => 'admin/structure/file-types',
2521
    'arguments' => array(
2522
      array('%file_type' => array_keys(file_entity_type_get_names())),
2523
    ),
2524
  );
2525
  return $map;
2526
}
2527
2528
/*
2529 ca0757b9 Assos Assos
 * Generates a token to protect a file download URL.
2530
 *
2531
 * This prevents unauthorized crawling of all file download URLs since the
2532
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
2533
 * to guess or attempt many at once. This can be costly both in CPU time
2534
 * and bandwidth.
2535 85ad3d82 Assos Assos
 *
2536 ca0757b9 Assos Assos
 * @see image_style_path_token()
2537 85ad3d82 Assos Assos
 *
2538
 * @param object $file
2539
 *   A file entity object.
2540
 *
2541
 * @return string
2542 ca0757b9 Assos Assos
 *   An eight-character token which can be used to protect file downloads
2543
 *   against denial-of-service attacks.
2544 85ad3d82 Assos Assos
 */
2545
function file_entity_get_download_token($file) {
2546 ca0757b9 Assos Assos
  // Return the first eight characters.
2547
  return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
2548 85ad3d82 Assos Assos
}
2549
2550
/**
2551
 * Find all fields that are of a certain field type.
2552
 *
2553
 * @param string $field_type
2554
 *   A field type.
2555
 *
2556
 * @return array
2557
 *   An array of field names that match the type $field_type.
2558
 */
2559
function _file_entity_get_fields_by_type($field_type) {
2560
  $return = array();
2561
  if (function_exists('field_info_field_map')) {
2562
    foreach (field_info_field_map() as $field_name => $field) {
2563
      if ($field['type'] == $field_type) {
2564
        $return[$field_name] = $field_name;
2565
      }
2566
    }
2567
  }
2568
  else {
2569
    foreach (field_info_fields() as $field_name => $field) {
2570
      if ($field['type'] == $field_type) {
2571
        $return[$field_name] = $field_name;
2572
      }
2573
    }
2574
  }
2575
  return $return;
2576
}
2577
2578
/**
2579
 * Implements hook_field_attach_load().
2580
 */
2581
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2582
  // Loop over all the entities looking for entities with attached images.
2583
  foreach ($entities as $entity) {
2584
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2585
    // Examine every image field instance attached to this entity's bundle.
2586
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2587
    foreach ($instances as $field_name => $instance) {
2588
      if (!empty($entity->{$field_name})) {
2589
        foreach ($entity->{$field_name} as $langcode => $items) {
2590
          foreach ($items as $delta => $item) {
2591
            // If alt and title text is not specified, fall back to alt and
2592
            // title text on the file.
2593 ca0757b9 Assos Assos
            if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
2594 85ad3d82 Assos Assos
              $file = file_load($item['fid']);
2595
              foreach (array('alt', 'title') as $key) {
2596
                if (empty($item[$key]) && !empty($file->{$key})) {
2597
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2598
                }
2599
              }
2600
            }
2601
          }
2602
        }
2603
      }
2604
    }
2605
  }
2606
}
2607
2608
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2609
  $wrappers = array();
2610
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2611
    if (empty($wrapper['private'])) {
2612
      $wrappers['public'][$key] = $wrapper['name'];
2613
    }
2614
    else {
2615
      $wrappers['private'][$key] = $wrapper['name'];
2616
    }
2617
  }
2618
  return $wrappers;
2619
}
2620 0ccfec7f Assos Assos
2621
/**
2622
 * Implements hook_features_pipe_alter() for the file type component.
2623
 */
2624
function file_entity_features_pipe_file_type_alter(&$pipe, $data, $export) {
2625
  foreach ($data as $file_type) {
2626
    $pipe['variable'][] = "pathauto_file_{$file_type}_pattern";
2627
  }
2628
}