Projet

Général

Profil

Paste
Télécharger (91 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / file_entity / file_entity.module @ 7b2d1845

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 d1c64ea8 Assos Assos
        'controls_list' => array(
898
          'download' => 'download',
899
          'remote_playback' => 'remote_playback',
900
        ),
901 85ad3d82 Assos Assos
        'autoplay' => FALSE,
902
        'loop' => FALSE,
903 1f142f4f Florent Torregrosa
        'preload' => NULL,
904 85ad3d82 Assos Assos
      ),
905
      'file' => 'file_entity.theme.inc',
906
    ),
907
    'file_entity_file_video' => array(
908
      'variables' => array(
909
        'files' => array(),
910
        'controls' => TRUE,
911 d1c64ea8 Assos Assos
        'controls_list' => array(
912
          'fullscreen' => 'fullscreen',
913
          'download' => 'download',
914
          'remote_playback' => 'remote_playback',
915
        ),
916 85ad3d82 Assos Assos
        'autoplay' => FALSE,
917 a8cee257 Assos Assos
        'playsinline' => FALSE,
918 85ad3d82 Assos Assos
        'loop' => FALSE,
919
        'muted' => FALSE,
920
        'width' => NULL,
921
        'height' => NULL,
922 1f142f4f Florent Torregrosa
        'preload' => NULL,
923 85ad3d82 Assos Assos
      ),
924
      'file' => 'file_entity.theme.inc',
925
    ),
926
  );
927
}
928
929
/**
930
 * Implements hook_entity_info_alter().
931
 *
932
 * Extends the core file entity to be fieldable. The file type is used as the
933
 * bundle key. File types are implemented as CTools exportables, so modules can
934
 * define default file types via hook_file_default_types(), and the
935
 * administrator can override the default types or add custom ones via
936
 * admin/structure/file-types.
937
 */
938
function file_entity_entity_info_alter(&$entity_info) {
939
  $entity_info['file']['fieldable'] = TRUE;
940
  $entity_info['file']['entity keys']['bundle'] = 'type';
941
  $entity_info['file']['bundle keys']['bundle'] = 'type';
942
  $entity_info['file']['bundles'] = array();
943
  $entity_info['file']['uri callback'] = 'file_entity_uri';
944
  $entity_info['file']['view modes']['teaser'] = array(
945
    'label' => t('Teaser'),
946
    'custom settings' => TRUE,
947
  );
948
  $entity_info['file']['view modes']['full'] = array(
949
    'label' => t('Full content'),
950
    'custom settings' => FALSE,
951
  );
952
  $entity_info['file']['view modes']['preview'] = array(
953
    'label' => t('Preview'),
954
    'custom settings' => TRUE,
955
  );
956
  $entity_info['file']['view modes']['rss'] = array(
957
    'label' => t('RSS'),
958
    'custom settings' => FALSE,
959
  );
960
961
  // Search integration is provided by file_entity.module, so search-related
962
  // view modes for files are defined here and not in search.module.
963
  if (module_exists('search')) {
964
    $entity_info['file']['view modes']['search_index'] = array(
965
      'label' => t('Search index'),
966
      'custom settings' => FALSE,
967
    );
968
    $entity_info['file']['view modes']['search_result'] = array(
969
      'label' => t('Search result'),
970
      'custom settings' => FALSE,
971
    );
972
  }
973
974
  foreach (file_type_get_enabled_types() as $type) {
975
    $entity_info['file']['bundles'][$type->type] = array(
976
      'label' => $type->label,
977
      'admin' => array(
978
        'path' => 'admin/structure/file-types/manage/%file_type',
979
        'real path' => 'admin/structure/file-types/manage/' . $type->type,
980
        'bundle argument' => 4,
981 0ccfec7f Assos Assos
        'access arguments' => array('administer file types'),
982 85ad3d82 Assos Assos
      ),
983
    );
984
  }
985
986
  // Enable Metatag support.
987
  $entity_info['file']['metatags'] = TRUE;
988
989
  // Ensure some of the Entity API callbacks are supported.
990
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
991 1f142f4f Florent Torregrosa
  $entity_info['file']['view callback'] = 'file_entity_metadata_view_file';
992
  $entity_info['file']['form callback'] = 'file_entity_metadata_form_file';
993 85ad3d82 Assos Assos
  $entity_info['file']['access callback'] = 'file_entity_access';
994
995
  // Add integration with the Title module for file name replacement support.
996
  $entity_info['file']['field replacement'] = array(
997
    'filename' => array(
998
      'field' => array(
999
        'type' => 'text',
1000
        'cardinality' => 1,
1001
        'translatable' => TRUE,
1002
      ),
1003
      'instance' => array(
1004
        'label' => t('File name'),
1005
        'description' => t('A field replacing file name.'),
1006
        'required' => TRUE,
1007
        'settings' => array(
1008
          'text_processing' => 0,
1009
        ),
1010
        'widget' => array(
1011
          'weight' => -5,
1012
        ),
1013
        'display' => array(
1014
          'default' => array(
1015
            'type' => 'hidden',
1016
          ),
1017
        ),
1018
      ),
1019
      'preprocess_key' => 'filename',
1020
    ),
1021
  );
1022
}
1023
1024
/**
1025
 * Implements hook_entity_property_info().
1026
 */
1027
function file_entity_entity_property_info() {
1028
  $info['file']['properties']['type'] = array(
1029
    'label' => t('File type'),
1030
    'type' => 'token',
1031
    'description' => t('The type of the file.'),
1032
    'setter callback' => 'entity_property_verbatim_set',
1033
    'setter permission' => 'administer files',
1034
    'options list' => 'file_entity_type_get_names',
1035
    'required' => TRUE,
1036
    'schema field' => 'type',
1037
  );
1038
1039
  return $info;
1040
}
1041
1042
/**
1043
 * Implements hook_field_display_ENTITY_TYPE_alter().
1044
 */
1045
function file_entity_field_display_file_alter(&$display, $context) {
1046
  // Hide field labels in search index.
1047
  if ($context['view_mode'] == 'search_index') {
1048
    $display['label'] = 'hidden';
1049
  }
1050
}
1051
1052
/**
1053
 * URI callback for file entities.
1054
 */
1055
function file_entity_uri($file) {
1056
  $uri['path'] = 'file/' . $file->fid;
1057
  return $uri;
1058
}
1059
1060 1f142f4f Florent Torregrosa
/**
1061
 * Entity API callback to view files.
1062
 */
1063
function file_entity_metadata_view_file($entities, $view_mode = 'full', $langcode = NULL) {
1064
  $result = file_view_multiple($entities, $view_mode, 0, $langcode);
1065
  // Make sure to key the result with 'file' instead of 'files'.
1066
  return array('file' => reset($result));
1067
}
1068
1069 85ad3d82 Assos Assos
/**
1070
 * Entity API callback to get the form of a file entity.
1071
 */
1072
function file_entity_metadata_form_file($file) {
1073
  // Pre-populate the form-state with the right form include.
1074
  $form_state['build_info']['args'] = array($file);
1075
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
1076
  return drupal_build_form('file_entity_edit', $form_state);
1077
}
1078
1079
/**
1080
 * Implements hook_ctools_plugin_directory().
1081
 */
1082 1f142f4f Florent Torregrosa
function file_entity_ctools_plugin_directory($module, $plugin) {
1083
  if (in_array($module, array('panelizer', 'ctools', 'page_manager'))) {
1084
    return 'plugins/' . $plugin;
1085 85ad3d82 Assos Assos
  }
1086
}
1087
1088
/**
1089
 * Implements hook_field_extra_fields().
1090
 *
1091
 * Adds 'file' as an extra field, so that its display and form component can be
1092
 * weighted relative to the fields that are added to file entity bundles.
1093
 */
1094
function file_entity_field_extra_fields() {
1095
  $info = array();
1096
1097
  if ($file_type_names = file_entity_type_get_names()) {
1098
    foreach ($file_type_names as $type => $name) {
1099
      $info['file'][$type]['form']['filename'] = array(
1100
        'label' => t('File name'),
1101
        'description' => t('File name'),
1102
        'weight' => -10,
1103
      );
1104
      $info['file'][$type]['form']['preview'] = array(
1105
        'label' => t('File'),
1106
        'description' => t('File preview'),
1107
        'weight' => -5,
1108
      );
1109
      $info['file'][$type]['display']['file'] = array(
1110
        'label' => t('File'),
1111
        'description' => t('File display'),
1112
        'weight' => 0,
1113
      );
1114
    }
1115
  }
1116
1117
  return $info;
1118
}
1119
1120
/**
1121
 * Implements hook_file_formatter_info().
1122
 */
1123
function file_entity_file_formatter_info() {
1124
  $formatters = array();
1125
1126
  // Allow file field formatters to be reused for displaying the file entity's
1127
  // file pseudo-field.
1128
  foreach (field_info_formatter_types() as $key => $formatter) {
1129
    if (array_intersect($formatter['field types'], array('file', 'image'))) {
1130
      $key = 'file_field_' . $key;
1131
      $formatters[$key] = array(
1132
        'label' => $formatter['label'],
1133
        'description' => !empty($formatter['description']) ? $formatter['description'] : '',
1134
        'view callback' => 'file_entity_file_formatter_file_field_view',
1135
      );
1136
      if (!empty($formatter['settings'])) {
1137
        $formatters[$key] += array(
1138
          'default settings' => $formatter['settings'],
1139
          'settings callback' => 'file_entity_file_formatter_file_field_settings',
1140
        );
1141
      }
1142
      if (!empty($formatter['file formatter'])) {
1143
        $formatters[$key] += $formatter['file formatter'];
1144
      }
1145
    }
1146
  }
1147
1148
  // Add a simple file formatter for displaying an image in a chosen style.
1149
  if (module_exists('image')) {
1150
    $formatters['file_image'] = array(
1151
      'label' => t('Image'),
1152
      'default settings' => array(
1153
        'image_style' => '',
1154 02a0babc Assos Assos
        'alt' => '[file:field-file-image-alt-text]',
1155
        'title' => '[file:field-file-image-title-text]'
1156 85ad3d82 Assos Assos
      ),
1157
      'view callback' => 'file_entity_file_formatter_file_image_view',
1158
      'settings callback' => 'file_entity_file_formatter_file_image_settings',
1159
      'hidden' => TRUE,
1160
      'mime types' => array('image/*'),
1161
    );
1162
  }
1163
1164
  return $formatters;
1165
}
1166
1167
/**
1168
 * Implements hook_file_formatter_FORMATTER_view().
1169
 *
1170
 * This function provides a bridge to the field formatter API, so that file
1171
 * field formatters can be reused for displaying the file entity's file
1172
 * pseudo-field.
1173
 */
1174
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
1175
  if (strpos($display['type'], 'file_field_') === 0) {
1176
    $field_formatter_type = substr($display['type'], strlen('file_field_'));
1177
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1178
    if (isset($field_formatter_info['module'])) {
1179
      // Set $display['type'] to what hook_field_formatter_*() expects.
1180
      $display['type'] = $field_formatter_type;
1181
1182 ca0757b9 Assos Assos
      // Allow any attribute overrides (e.g. from the Media module) to be
1183
      // respected.
1184
      $item = (array) $file;
1185
      if (!empty($file->override['attributes'])) {
1186
        $item = array_merge($item, $file->override['attributes']);
1187
      }
1188
1189 85ad3d82 Assos Assos
      // Set $items to what file field formatters expect. See file_field_load(),
1190
      // and note that, here, $file is already a fully loaded entity.
1191 ca0757b9 Assos Assos
      $items = array($item);
1192 85ad3d82 Assos Assos
1193
      // Invoke hook_field_formatter_prepare_view() and
1194
      // hook_field_formatter_view(). Note that we are reusing field formatter
1195
      // functions, but we are not displaying a Field API field, so we set
1196
      // $field and $instance accordingly, and do not invoke
1197
      // hook_field_prepare_view(). This assumes that the formatter functions do
1198
      // not rely on $field or $instance. A module that implements formatter
1199
      // functions that rely on $field or $instance (and therefore, can only be
1200
      // used for real fields) can prevent this formatter from being used on the
1201
      // pseudo-field by removing it within hook_file_formatter_info_alter().
1202
      $field = $instance = NULL;
1203
      if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
1204
        $fid = $file->fid;
1205
        // hook_field_formatter_prepare_view() alters $items by reference.
1206
        $grouped_items = array($fid => &$items);
1207
        $function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
1208
      }
1209
      if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
1210
        $element = $function('file', $file, $field, $instance, $langcode, $items, $display);
1211
        // We passed the file as $items[0], so return the corresponding element.
1212
        if (isset($element[0])) {
1213
          return $element[0];
1214
        }
1215
      }
1216
    }
1217
  }
1218
}
1219
1220
/**
1221
 * Implements hook_file_formatter_FORMATTER_settings().
1222
 *
1223
 * This function provides a bridge to the field formatter API, so that file
1224
 * field formatters can be reused for displaying the file entity's file
1225
 * pseudo-field.
1226
 */
1227
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
1228
  if (strpos($formatter_type, 'file_field_') === 0) {
1229
    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
1230
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1231
1232
    // Invoke hook_field_formatter_settings_form(). We are reusing field
1233
    // formatter functions, but we are not working with a Field API field, so
1234
    // set $field accordingly. Unfortunately, the API is for $settings to be
1235 0ccfec7f Assos Assos
    // transferred via the $instance parameter, so we must mock it.
1236 85ad3d82 Assos Assos
    if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
1237
      $field = NULL;
1238
      $mock_instance = array(
1239
        'display' => array(
1240
          $view_mode => array(
1241
            'type' => $field_formatter_type,
1242
            'settings' => $settings,
1243
          ),
1244
        ),
1245
        'entity_type' => 'file',
1246
        'bundle' => $file_type,
1247
      );
1248
      return $function($field, $mock_instance, $view_mode, $form, $form_state);
1249
    }
1250
  }
1251
}
1252
1253 9a28ac3f Assos Assos
/**
1254
 * Replace file entity title text.
1255
 *
1256
 * @param $file
1257
 *   The file entity.
1258
 * @param $replace_options
1259
 *   (Optional) Options to pass to token_replace().
1260
 * @param $title
1261
 *   (Optional) The title text to use.
1262
 * @param string $langcode
1263
 *   (Optional) Language code
1264
 *
1265
 * @return string
1266
 *   Returns the replaced title text.
1267
 */
1268 7b2d1845 Assos Assos
function file_entity_replace_title($file, $replace_options = array(), $title = NULL, $langcode = NULL) {
1269
  $replace_options += array(
1270 9a28ac3f Assos Assos
    'clear' => TRUE,
1271
    'sanitize' => FALSE,
1272 7b2d1845 Assos Assos
  );
1273 9a28ac3f Assos Assos
1274
  $title_default = '[file:field_file_image_title_text]';
1275
  if (!isset($title)) {
1276
    $title = variable_get('file_entity_title', $title_default);
1277
  }
1278
  // If the defaults are not changed then inlining replacement is much faster
1279
  // than dealing with the token system.
1280
  if ($title === $title_default) {
1281
    $title_items = field_get_items('file', $file, 'field_file_image_title_text', $langcode);
1282
    return $title_items ? $title_items[0]['value'] : '';
1283
  }
1284
  elseif (!empty($title)) {
1285 7b2d1845 Assos Assos
    $token_replaced = token_replace($title, array('file' => $file), $replace_options);
1286 9a28ac3f Assos Assos
    return decode_entities($token_replaced); // Filter out possible XSS.
1287
  }
1288
1289
  return '';
1290
}
1291
1292
/**
1293
 * Replace file entity alt.
1294
 *
1295
 * @param $file
1296
 *   The file entity.
1297
 * @param array $replace_options
1298
 *   (Optional) Options to pass to token_replace().
1299
 * @param $alt
1300
 *   (Optional) The alt text to use.
1301
 * @param string $langcode
1302
 *   (Optional) Language code
1303
 *
1304
 * @return string
1305
 *   Returns the replaced alt text.
1306
 */
1307 7b2d1845 Assos Assos
function file_entity_replace_alt($file, $replace_options = array(), $alt = NULL, $langcode = NULL) {
1308
  $replace_options += array(
1309 9a28ac3f Assos Assos
    'clear' => TRUE,
1310
    'sanitize' => FALSE,
1311 7b2d1845 Assos Assos
  );
1312 9a28ac3f Assos Assos
1313
  $alt_default = '[file:field_file_image_alt_text]';
1314
1315
  if (!isset($alt)) {
1316
    $alt = variable_get('file_entity_alt', $alt_default);
1317
  }
1318
1319
  // If the defaults are not changed then inlining replacement is much faster
1320
  // than dealing with the token system.
1321
  if ($alt === $alt_default) {
1322
    $alt_items = field_get_items('file', $file, 'field_file_image_alt_text', $langcode);
1323
    return $alt_items ? $alt_items[0]['value'] : '';
1324
  }
1325
  elseif (!empty($alt)) {
1326 7b2d1845 Assos Assos
    $token_replaced = token_replace($alt, array('file' => $file), $replace_options);
1327 9a28ac3f Assos Assos
    return decode_entities($token_replaced); // Filter out possible XSS.
1328
  }
1329
1330
  return '';
1331
}
1332
1333 85ad3d82 Assos Assos
/**
1334
 * Implements hook_file_formatter_FORMATTER_view().
1335
 *
1336
 * Returns a drupal_render() array to display an image of the chosen style.
1337
 *
1338
 * This formatter is only capable of displaying local images. If the passed in
1339
 * file is either not local or not an image, nothing is returned, so that
1340
 * file_view_file() can try another formatter.
1341
 */
1342
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
1343
  // Prevent PHP notices when trying to read empty files.
1344
  // @see http://drupal.org/node/681042
1345
  if (!$file->filesize) {
1346
    return;
1347
  }
1348
1349
  // Do not bother proceeding if this file does not have an image mime type.
1350
  if (file_entity_file_get_mimetype_type($file) != 'image') {
1351
    return;
1352
  }
1353
1354
  if (file_entity_file_is_readable($file)) {
1355
    // We don't sanitize here.
1356
    // @see http://drupal.org/node/1553094#comment-6257382
1357
    // Theme function will take care of escaping.
1358
    if (!isset($file->metadata)) {
1359
      $file->metadata = array();
1360
    }
1361
    $file->metadata += array('width' => NULL, 'height' => NULL);
1362
    $replace_options = array(
1363
      'clear' => TRUE,
1364
      'sanitize' => FALSE,
1365
    );
1366
    if (!empty($display['settings']['image_style'])) {
1367
      $element = array(
1368
        '#theme' => 'image_style',
1369
        '#style_name' => $display['settings']['image_style'],
1370
        '#path' => $file->uri,
1371
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1372
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1373 9a28ac3f Assos Assos
        '#alt' => file_entity_replace_alt($file, $replace_options, $display['settings']['alt'], $langcode),
1374
        '#title' => file_entity_replace_title($file, $replace_options, $display['settings']['title'], $langcode),
1375 85ad3d82 Assos Assos
      );
1376
    }
1377
    else {
1378
      $element = array(
1379
        '#theme' => 'image',
1380
        '#path' => $file->uri,
1381
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1382
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1383 9a28ac3f Assos Assos
        '#alt' => file_entity_replace_alt($file, $replace_options, $display['settings']['alt'], $langcode),
1384
        '#title' => file_entity_replace_title($file, $replace_options, $display['settings']['title'], $langcode),
1385 85ad3d82 Assos Assos
      );
1386
    }
1387
    return $element;
1388
  }
1389
}
1390
1391
/**
1392
 * Check if a file entity is readable or not.
1393
 *
1394
 * @param object $file
1395
 *   A file entity object from file_load().
1396
 *
1397
 * @return boolean
1398
 *   TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
1399
 */
1400
function file_entity_file_is_readable($file) {
1401
  $scheme = file_uri_scheme($file->uri);
1402
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
1403
  return !empty($wrappers[$scheme]);
1404
}
1405
1406
/**
1407
 * Implements hook_file_formatter_FORMATTER_settings().
1408
 *
1409
 * Returns form elements for configuring the 'file_image' formatter.
1410
 */
1411
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
1412
  $element = array();
1413
  $element['image_style'] = array(
1414
    '#title' => t('Image style'),
1415
    '#type' => 'select',
1416
    '#options' => image_style_options(FALSE),
1417
    '#default_value' => $settings['image_style'],
1418
    '#empty_option' => t('None (original image)'),
1419
  );
1420
1421
  // For image files we allow the alt attribute (required in HTML).
1422
  $element['alt'] = array(
1423
    '#title' => t('Alt attribute'),
1424
    '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
1425
    '#type' => 'textfield',
1426
    '#default_value' => $settings['alt'],
1427
  );
1428
1429
  // Allow the setting of the title attribute.
1430
  $element['title'] = array(
1431
    '#title' => t('Title attribute'),
1432
    '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
1433
    '#type' => 'textfield',
1434
    '#default_value' => $settings['title'],
1435
  );
1436
1437
  if (module_exists('token')) {
1438
    $element['alt']['#description'] .= t('This field supports tokens.');
1439
    $element['title']['#description'] .= t('This field supports tokens.');
1440
    $element['tokens'] = array(
1441
      '#theme' => 'token_tree',
1442
      '#token_types' => array('file'),
1443
      '#dialog' => TRUE,
1444
    );
1445
  }
1446
1447
  return $element;
1448
}
1449
1450
/**
1451
 * Menu access callback for the 'view mode file display settings' pages.
1452
 *
1453
 * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
1454
 * be enabled.
1455
 */
1456
function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
1457
  // Deny access if the view mode isn't configured to use custom display
1458
  // settings.
1459
  $view_mode_settings = field_view_mode_settings('file', $file_type->type);
1460
  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
1461
  if (!$visibility) {
1462
    return FALSE;
1463
  }
1464
1465
  // Otherwise, continue to an $access_callback check.
1466
  $args = array_slice(func_get_args(), 3);
1467
  $callback = empty($access_callback) ? 0 : trim($access_callback);
1468
  if (is_numeric($callback)) {
1469
    return (bool) $callback;
1470
  }
1471
  elseif (function_exists($access_callback)) {
1472
    return call_user_func_array($access_callback, $args);
1473
  }
1474
}
1475
1476
/**
1477
 * Implements hook_modules_enabled().
1478
 */
1479
function file_entity_modules_enabled($modules) {
1480
  file_info_cache_clear();
1481
}
1482
1483
/**
1484
 * Implements hook_modules_disabled().
1485
 */
1486
function file_entity_modules_disabled($modules) {
1487
  file_info_cache_clear();
1488
}
1489
1490
/**
1491
 * Implements hook_views_api().
1492
 */
1493
function file_entity_views_api() {
1494
  return array(
1495
    'api' => 3,
1496
  );
1497
}
1498
1499
/**
1500
 * Returns whether the current page is the full page view of the passed-in file.
1501
 *
1502
 * @param $file
1503
 *   A file object.
1504
 */
1505
function file_entity_is_page($file) {
1506
  $page_file = menu_get_object('file', 1);
1507
  return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
1508
}
1509
1510
/**
1511
 * Process variables for file_entity.tpl.php
1512
 *
1513
 * The $variables array contains the following arguments:
1514
 * - $file
1515
 * - $view_mode
1516
 *
1517
 * @see file_entity.tpl.php
1518
 */
1519
function template_preprocess_file_entity(&$variables) {
1520
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
1521
  $variables['file'] = $variables['elements']['#file'];
1522
  $file = $variables['file'];
1523
1524
  $variables['id'] = drupal_html_id('file-'. $file->fid);
1525
  $variables['date']      = format_date($file->timestamp);
1526
  $account = user_load($file->uid);
1527
  $variables['name']      = theme('username', array('account' => $account));
1528
1529
  $uri = entity_uri('file', $file);
1530
  $variables['file_url']  = url($uri['path'], $uri['options']);
1531
  $label = entity_label('file', $file);
1532
  $variables['label']     = check_plain($label);
1533
  $variables['page']      = $view_mode == 'full' && file_entity_is_page($file);
1534
1535
  // Hide the file name from being displayed until we can figure out a better
1536
  // way to control this. We cannot simply not output the title since
1537
  // contextual links require $title_suffix to be output in the template.
1538
  // @see http://drupal.org/node/1245266
1539
  if (!$variables['page']) {
1540
    $variables['title_attributes_array']['class'][] = 'element-invisible';
1541
  }
1542
1543
  // Flatten the file object's member fields.
1544
  $variables = array_merge((array) $file, $variables);
1545
1546
  // Helpful $content variable for templates.
1547
  $variables += array('content' => array());
1548
  foreach (element_children($variables['elements']) as $key) {
1549
    $variables['content'][$key] = $variables['elements'][$key];
1550
  }
1551
1552
  // Make the field variables available with the appropriate language.
1553
  field_attach_preprocess('file', $file, $variables['content'], $variables);
1554
1555
  // Attach the file object to the content element.
1556
  $variables['content']['file']['#file'] = $file;
1557
1558
  // Display post information only on certain file types.
1559
  if (variable_get('file_submitted_' . $file->type, FALSE)) {
1560
    $variables['display_submitted'] = TRUE;
1561
    $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1562
    $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
1563
  }
1564
  else {
1565
    $variables['display_submitted'] = FALSE;
1566
    $variables['submitted'] = '';
1567
    $variables['user_picture'] = '';
1568
  }
1569
1570
  // Gather file classes.
1571
  $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
1572
  $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
1573
  if ($file->status != FILE_STATUS_PERMANENT) {
1574
    $variables['classes_array'][] = 'file-temporary';
1575
  }
1576
1577
  // Change the 'file-entity' class into 'file'
1578
  if ($variables['classes_array'][0] == 'file-entity') {
1579
    $variables['classes_array'][0] = 'file';
1580
  }
1581
1582
  // Clean up name so there are no underscores.
1583
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
1584
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
1585
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
1586
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
1587
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
1588
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
1589
}
1590
1591
/**
1592
 * Returns the file type name of the passed file or file type string.
1593
 *
1594
 * @param $file
1595
 *   A file object or string that indicates the file type to return.
1596
 *
1597
 * @return
1598
 *   The file type name or FALSE if the file type is not found.
1599
 */
1600
function file_entity_type_get_name($file) {
1601
  $type = is_object($file) ? $file->type : $file;
1602
  $info = entity_get_info('file');
1603
  return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
1604
}
1605
1606
/**
1607
 * Returns a list of available file type names.
1608
 *
1609
 * @return
1610
 *   An array of file type names, keyed by the type.
1611
 */
1612
function file_entity_type_get_names() {
1613
  $names = &drupal_static(__FUNCTION__);
1614
1615
  if (!isset($names)) {
1616 1f142f4f Florent Torregrosa
    $names = array();
1617 85ad3d82 Assos Assos
    $info = entity_get_info('file');
1618
    foreach ($info['bundles'] as $bundle => $bundle_info) {
1619
      $names[$bundle] = $bundle_info['label'];
1620
    }
1621
  }
1622
1623
  return $names;
1624
}
1625
1626
/**
1627
 * Return an array of available view modes for file entities.
1628
 */
1629
function file_entity_view_mode_labels() {
1630
  $labels = &drupal_static(__FUNCTION__);
1631
1632
  if (!isset($options)) {
1633
    $entity_info = entity_get_info('file');
1634
    $labels = array('default' => t('Default'));
1635
    foreach ($entity_info['view modes'] as $machine_name => $mode) {
1636
      $labels[$machine_name] = $mode['label'];
1637
    }
1638
  }
1639
1640
  return $labels;
1641
}
1642
1643
/**
1644
 * Return the label for a specific file entity view mode.
1645
 */
1646
function file_entity_view_mode_label($view_mode, $default = FALSE) {
1647
  $labels = file_entity_view_mode_labels();
1648
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
1649
}
1650
1651
/**
1652
 * Helper function to get a list of hidden stream wrappers.
1653
 *
1654
 * This is used in several places to filter queries for media so that files in
1655
 * temporary:// don't show up.
1656
 */
1657
function file_entity_get_hidden_stream_wrappers() {
1658
  return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
1659
}
1660
1661
/**
1662
 * Return a specific stream wrapper's registry information.
1663
 *
1664
 * @param $scheme
1665
 *   A URI scheme, a stream is referenced as "scheme://target".
1666
 *
1667
 * @see file_get_stream_wrappers()
1668
 */
1669
function file_entity_get_stream_wrapper($scheme) {
1670
  $wrappers = file_get_stream_wrappers();
1671
  return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
1672
}
1673
1674
/**
1675
 * Implements hook_stream_wrappers_alter().
1676
 */
1677
function file_entity_stream_wrappers_alter(&$wrappers) {
1678
  if (isset($wrappers['private'])) {
1679
    $wrappers['private']['private'] = TRUE;
1680
  }
1681
  if (isset($wrappers['temporary'])) {
1682
    $wrappers['temporary']['private'] = TRUE;
1683
  }
1684
}
1685
1686
/**
1687
 * Implements hook_ctools_plugin_api().
1688
 */
1689 1f142f4f Florent Torregrosa
function file_entity_ctools_plugin_api($module, $api) {
1690
  if (($module == 'file_entity' && $api == 'file_type') || ($module == 'page_manager' && $api == 'pages_default') || $module == 'panelizer') {
1691 85ad3d82 Assos Assos
    return array('version' => 1);
1692
  }
1693 1f142f4f Florent Torregrosa
  if ($module == 'file_entity' && $api == 'file_default_displays') {
1694 85ad3d82 Assos Assos
    return array('version' => 1);
1695
  }
1696
}
1697
1698
/**
1699
 * @defgroup file_entity_access File access rights
1700
 * @{
1701
 * The file access system determines who can do what to which files.
1702
 *
1703
 * In determining access rights for a file, file_entity_access() first checks
1704
 * whether the user has the "bypass file access" permission. Such users have
1705
 * unrestricted access to all files. user 1 will always pass this check.
1706
 *
1707
 * Next, all implementations of hook_file_entity_access() will be called. Each
1708
 * implementation may explicitly allow, explicitly deny, or ignore the access
1709
 * request. If at least one module says to deny the request, it will be rejected.
1710
 * If no modules deny the request and at least one says to allow it, the request
1711
 * will be permitted.
1712
 *
1713
 * There is no access grant system for files.
1714
 *
1715
 * In file listings, the process above is followed except that
1716
 * hook_file_entity_access() is not called on each file for performance reasons
1717
 * and for proper functioning of the pager system. When adding a filelisting to
1718
 * your module, be sure to use a dynamic query created by db_select()
1719
 * and add a tag of "file_entity_access". This will allow modules dealing
1720
 * with file access to ensure only files to which the user has access
1721
 * are retrieved, through the use of hook_query_TAG_alter().
1722
 *
1723
 * Note: Even a single module returning FILE_ENTITY_ACCESS_DENY from
1724
 * hook_file_entity_access() will block access to the file. Therefore,
1725
 * implementers should take care to not deny access unless they really intend to.
1726
 * Unless a module wishes to actively deny access it should return
1727
 * FILE_ENTITY_ACCESS_IGNORE (or simply return nothing)
1728
 * to allow other modules to control access.
1729
 *
1730
 * Stream wrappers that are considered private should implement a 'private'
1731
 * flag equal to TRUE in hook_stream_wrappers().
1732
 */
1733
1734
/**
1735
 * Determine if a user may perform the given operation on the specified file.
1736
 *
1737
 * @param $op
1738
 *   The operation to be performed on the file. Possible values are:
1739
 *   - "view"
1740
 *   - "download"
1741
 *   - "update"
1742
 *   - "delete"
1743
 *   - "create"
1744
 * @param $file
1745
 *   The file object on which the operation is to be performed, or file type
1746
 *   (e.g. 'image') for "create" operation.
1747
 * @param $account
1748
 *   Optional, a user object representing the user for whom the operation is to
1749
 *   be performed. Determines access for a user other than the current user.
1750
 *
1751
 * @return
1752
 *   TRUE if the operation may be performed, FALSE otherwise.
1753
 */
1754
function file_entity_access($op, $file = NULL, $account = NULL) {
1755
  $rights = &drupal_static(__FUNCTION__, array());
1756
1757
  if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
1758
    // If there was no file to check against, and the $op was not one of the
1759
    // supported ones, we return access denied.
1760
    return FALSE;
1761
  }
1762
1763
  // If no user object is supplied, the access check is for the current user.
1764
  if (empty($account)) {
1765
    $account = $GLOBALS['user'];
1766
  }
1767
1768
  // $file may be either an object or a file type. Since file types cannot be
1769
  // an integer, use either fid or type as the static cache id.
1770 02a0babc Assos Assos
  $cache_id = NULL;
1771 d1c64ea8 Assos Assos
  if (is_object($file) && !empty($file->fid)) {
1772
    $cache_id = $file->fid;
1773 02a0babc Assos Assos
  }
1774 d1c64ea8 Assos Assos
  elseif ($op == 'create' && is_string($file)) {
1775 02a0babc Assos Assos
    $cache_id = $file;
1776
  }
1777 d1c64ea8 Assos Assos
  elseif ($op == 'create' && is_object($file) && !empty($file->type)) {
1778
    $cache_id = $file->type;
1779
  }
1780 02a0babc Assos Assos
  else {
1781
    $cache_id = drupal_hash_base64(serialize($file));
1782
  }
1783 85ad3d82 Assos Assos
1784
  // If we've already checked access for this file, user and op, return from
1785
  // cache.
1786
  if (isset($rights[$account->uid][$cache_id][$op])) {
1787
    return $rights[$account->uid][$cache_id][$op];
1788
  }
1789
1790
  if (user_access('bypass file access', $account)) {
1791
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1792
  }
1793
1794
  // We grant access to the file if both of the following conditions are met:
1795
  // - No modules say to deny access.
1796
  // - At least one module says to grant access.
1797
  $access = module_invoke_all('file_entity_access', $op, $file, $account);
1798
  if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
1799
    return $rights[$account->uid][$cache_id][$op] = FALSE;
1800
  }
1801
  elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
1802
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1803
  }
1804
1805
1806
  // Fall back to default behaviors on view.
1807
  if ($op == 'view' && is_object($file)) {
1808
    $scheme = file_uri_scheme($file->uri);
1809
    $wrapper = file_entity_get_stream_wrapper($scheme);
1810
1811
    if (!empty($wrapper['private'])) {
1812
      // For private files, users can view private files if the
1813
      // user has the 'view private files' permission.
1814
      if (user_access('view private files', $account)) {
1815
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1816
      }
1817
1818
      // For private files, users can view their own private files if the
1819
      // user is not anonymous, and has the 'view own private files' permission.
1820
      if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
1821
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1822
      }
1823
    }
1824
    elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
1825
      // For non-private files, allow to see if user owns the file.
1826
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1827
    }
1828
    elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
1829
      // For non-private files, users can view if they have the 'view files'
1830
      // permission.
1831
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1832
    }
1833
  }
1834
1835
  return FALSE;
1836
}
1837
1838
/**
1839
 * Implements hook_file_entity_access().
1840
 */
1841
function file_entity_file_entity_access($op, $file, $account) {
1842 d1c64ea8 Assos Assos
  // If the op is "create," all that's needed is to check the create permission.
1843
  if ($op == 'create') {
1844
    if (user_access('create files')) {
1845
      return FILE_ENTITY_ACCESS_ALLOW;
1846
    }
1847
  }
1848 85ad3d82 Assos Assos
  // If the file URI is invalid, deny access.
1849 02a0babc Assos Assos
  if (is_object($file) && isset($file->uri) && !file_valid_uri($file->uri)) {
1850
    if(isset($file->is_new) && $file->is_new == true && user_access('create files')) {
1851
      return FILE_ENTITY_ACCESS_ALLOW;
1852
    }
1853 85ad3d82 Assos Assos
    return FILE_ENTITY_ACCESS_DENY;
1854
  }
1855
1856
  if (!empty($file)) {
1857
    $type = is_string($file) ? $file : $file->type;
1858
1859
    if (in_array($type, file_entity_permissions_get_configured_types())) {
1860
      if ($op == 'download') {
1861
        if (user_access('download any ' . $type . ' files', $account) || is_object($file) && user_access('download own ' . $type . ' files', $account) && ($account->uid == $file->uid)) {
1862
          return FILE_ENTITY_ACCESS_ALLOW;
1863
        }
1864
      }
1865
1866
      if ($op == 'update') {
1867
        if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1868
          return FILE_ENTITY_ACCESS_ALLOW;
1869
        }
1870
      }
1871
1872
      if ($op == 'delete') {
1873
        if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1874
          return FILE_ENTITY_ACCESS_ALLOW;
1875
        }
1876
      }
1877
    }
1878
  }
1879
1880
  return FILE_ENTITY_ACCESS_IGNORE;
1881
}
1882
1883
/**
1884
 * Implements hook_query_TAG_alter().
1885
 *
1886
 * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
1887
 * file access checks for the user account given by the 'account' meta-data (or
1888
 * global $user if not provided).
1889
 */
1890
function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
1891
  _file_entity_query_file_entity_access_alter($query, 'file');
1892
}
1893
1894
/**
1895
 * Implements hook_query_TAG_alter().
1896
 *
1897
 * This function implements the same functionality as
1898
 * file_entity_query_file_access_alter() for the SQL field storage engine. File
1899
 * access conditions are added for field values belonging to files only.
1900
 */
1901
function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
1902 082b75eb Assos Assos
  //_file_entity_query_file_entity_access_alter($query, 'entity');
1903 85ad3d82 Assos Assos
}
1904
1905
/**
1906
 * Helper for file entity access functions.
1907
 *
1908
 * @param $query
1909
 *   The query to add conditions to.
1910
 * @param $type
1911
 *   Either 'file' or 'entity' depending on what sort of query it is. See
1912
 *   file_entity_query_file_entity_access_alter() and
1913
 *   file_entity_query_entity_field_access_alter() for more.
1914
 */
1915
function _file_entity_query_file_entity_access_alter($query, $type) {
1916
  global $user;
1917
1918
  // Read meta-data from query, if provided.
1919
  if (!$account = $query->getMetaData('account')) {
1920
    $account = $user;
1921
  }
1922
1923
  // If $account can bypass file access, we don't need to alter the query.
1924
  if (user_access('bypass file access', $account)) {
1925
    return;
1926
  }
1927
1928 7b974d86 Assos Assos
  // A conflict with og_query_og_membership_alter() causes a fatal error
1929
  // if both hooks alter the query.
1930
  if (module_exists('og') && $query->hasTag('og_membership')) {
1931
    foreach($query->getMetaData('entity_field_query')->fields as $field) {
1932
      if (og_is_group_audience_field($field['field_name'])) {
1933
        return;
1934
      }
1935
    }
1936
  }
1937
1938 85ad3d82 Assos Assos
  $tables = $query->getTables();
1939
  $base_table = $query->getMetaData('base_table');
1940
  // If no base table is specified explicitly, search for one.
1941
  if (!$base_table) {
1942
    $fallback = '';
1943
    foreach ($tables as $alias => $table_info) {
1944 02a0babc Assos Assos
      if (!($table_info instanceof SelectQueryInterface || $table_info['table'] instanceof SelectQueryInterface)) {
1945 85ad3d82 Assos Assos
        $table = $table_info['table'];
1946
        // If the file_managed table is in the query, it wins immediately.
1947
        if ($table == 'file_managed') {
1948
          $base_table = $table;
1949
          break;
1950
        }
1951
        // Check whether the table has a foreign key to file_managed.fid. If it
1952
        // does, do not run this check again as we found a base table and only
1953
        // file_managed can triumph that.
1954
        if (!$base_table) {
1955
          // The schema is cached.
1956
          $schema = drupal_get_schema($table);
1957
          if (isset($schema['fields']['fid'])) {
1958
            if (isset($schema['foreign keys'])) {
1959
              foreach ($schema['foreign keys'] as $relation) {
1960
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
1961
                  $base_table = $table;
1962
                }
1963
              }
1964
            }
1965
            else {
1966
              // At least it's a fid. A table with a field called fid is very
1967
              // very likely to be a file_managed.fid in a file access query.
1968
              $fallback = $table;
1969
            }
1970
          }
1971
        }
1972
      }
1973
    }
1974
    // If there is nothing else, use the fallback.
1975
    if (!$base_table) {
1976
      if ($fallback) {
1977
        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);
1978
        $base_table = $fallback;
1979
      }
1980
      else {
1981
        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.'));
1982
      }
1983
    }
1984
  }
1985
1986
  if ($type == 'entity') {
1987
    // The original query looked something like:
1988
    // @code
1989
    //  SELECT fid FROM sometable s
1990
    //  WHERE ($file_access_conditions)
1991
    // @endcode
1992
    //
1993
    // Our query will look like:
1994
    // @code
1995
    //  SELECT entity_type, entity_id
1996
    //  FROM field_data_something s
1997
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1998
    // @endcode
1999
    //
2000
    // So instead of directly adding to the query object, we need to collect
2001
    // all of the file access conditions in a separate db_and() object and
2002
    // then add it to the query at the end.
2003
    $file_conditions = db_and();
2004
  }
2005
  foreach ($tables as $falias => $tableinfo) {
2006
    $table = $tableinfo['table'];
2007
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
2008
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
2009
      $subquery_conditions = db_or();
2010
2011
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
2012
      if (!empty($wrappers['public'])) {
2013
        if (user_access('view files', $account)) {
2014
          foreach (array_keys($wrappers['public']) as $wrapper) {
2015
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
2016
          }
2017
        }
2018
        elseif (user_access('view own files', $account)) {
2019
          foreach (array_keys($wrappers['public']) as $wrapper) {
2020
            $subquery_conditions->condition(db_and()
2021
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
2022
              ->condition('fm_access.uid', $account->uid)
2023
            );
2024
          }
2025
        }
2026
      }
2027
      if (!empty($wrappers['private'])) {
2028
        if (user_access('view private files', $account)) {
2029
          foreach (array_keys($wrappers['private']) as $wrapper) {
2030
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
2031
          }
2032
        }
2033
        elseif (user_access('view own private files', $account)) {
2034
          foreach (array_keys($wrappers['private']) as $wrapper) {
2035
            $subquery_conditions->condition(db_and()
2036
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
2037
              ->condition('fm_access.uid', $account->uid)
2038
            );
2039
          }
2040
        }
2041
      }
2042
2043 082b75eb Assos Assos
      if ($subquery_conditions->count()) {
2044 85ad3d82 Assos Assos
        $subquery->condition($subquery_conditions);
2045
2046 082b75eb Assos Assos
        $field = 'fid';
2047
        // Now handle entities.
2048
        if ($type == 'entity') {
2049
          // Set a common alias for entities.
2050
          $base_alias = $falias;
2051
          $field = 'entity_id';
2052
        }
2053
        $subquery->where("$falias.$field = fm_access.fid");
2054 85ad3d82 Assos Assos
2055 082b75eb Assos Assos
        // For an entity query, attach the subquery to entity conditions.
2056
        if ($type == 'entity') {
2057
          $file_conditions->exists($subquery);
2058
        }
2059
        // Otherwise attach it to the node query itself.
2060
        elseif ($table == 'file_managed') {
2061
          // Fix for https://drupal.org/node/2073085
2062
          $db_or = db_or();
2063
          $db_or->exists($subquery);
2064
          $db_or->isNull($falias . '.' . $field);
2065
          $query->condition($db_or);
2066
        }
2067
        else {
2068
          $query->exists($subquery);
2069
        }
2070 85ad3d82 Assos Assos
      }
2071
    }
2072
  }
2073
2074
  if ($type == 'entity' && $file_conditions->count()) {
2075
    // All the file access conditions are only for field values belonging to
2076
    // files.
2077 082b75eb Assos Assos
    $file_conditions->condition("$base_alias.entity_type", 'file');
2078
    $or = db_or();
2079
    $or->condition($file_conditions);
2080
    // If the field value belongs to a non-file entity type then this function
2081
    // does not do anything with it.
2082
    $or->condition("$base_alias.entity_type", 'file', '<>');
2083
    // Add the compiled set of rules to the query.
2084
    $query->condition($or);
2085 85ad3d82 Assos Assos
  }
2086
}
2087
2088
/**
2089
 * Implements hook_file_download().
2090
 */
2091
function file_entity_file_download($uri) {
2092
  // Load the file from the URI.
2093
  $file = file_uri_to_object($uri);
2094
2095
  // An existing file wasn't found, so we don't control access.
2096
  // E.g. image derivatives will fall here.
2097
  if (empty($file->fid)) {
2098
    return NULL;
2099
  }
2100
2101
  // Allow the user to download the file if they have appropriate permissions.
2102
  if (file_entity_access('view', $file)) {
2103
    return file_get_content_headers($file);
2104
  }
2105
2106 1f142f4f Florent Torregrosa
  return NULL;
2107 85ad3d82 Assos Assos
}
2108
2109
/**
2110
 * Helper function to generate standard file permission list for a given type.
2111
 *
2112
 * @param $type
2113
 *   The machine-readable name of the file type.
2114
 * @return array
2115
 *   An array of permission names and descriptions.
2116
 */
2117
function file_entity_list_permissions($type) {
2118
  $info = file_type_load($type);
2119
2120
  // Build standard list of file permissions for this type.
2121
  $permissions = array(
2122
    "edit own $type files" => array(
2123
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
2124
    ),
2125
    "edit any $type files" => array(
2126
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
2127
    ),
2128
    "delete own $type files" => array(
2129
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
2130
    ),
2131
    "delete any $type files" => array(
2132
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
2133
    ),
2134
    "download own $type files" => array(
2135
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
2136
    ),
2137
    "download any $type files" => array(
2138
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
2139
    ),
2140
  );
2141
2142
  return $permissions;
2143
}
2144
2145
/**
2146
 * Returns an array of file types that should be managed by permissions.
2147
 *
2148
 * By default, this will include all file types in the system. To exclude a
2149
 * specific file from getting permissions defined for it, set the
2150
 * file_entity_permissions_$type variable to 0. File entity does not provide an
2151
 * interface for doing so, however, contrib modules may exclude their own files
2152
 * in hook_install(). Alternatively, contrib modules may configure all file
2153
 * types at once, or decide to apply some other hook_file_entity_access()
2154
 * implementation to some or all file types.
2155
 *
2156
 * @return
2157
 *   An array of file types managed by this module.
2158
 */
2159
function file_entity_permissions_get_configured_types() {
2160
2161
  $configured_types = array();
2162
2163
  foreach (file_type_get_enabled_types() as $type => $info) {
2164
    if (variable_get('file_entity_permissions_' . $type, 1)) {
2165
      $configured_types[] = $type;
2166
    }
2167
  }
2168
2169
  return $configured_types;
2170
}
2171
2172
/**
2173
 * @} End of "defgroup file_entity_access".
2174
 *
2175
 * Implements hook_file_default_types().
2176
 */
2177
function file_entity_file_default_types() {
2178
  $types = array();
2179
2180
  // Image.
2181
  $types['image'] = (object) array(
2182
    'api_version' => 1,
2183
    'type' => 'image',
2184
    'label' => t('Image'),
2185
    'description' => t('An <em>Image</em> file is a still visual.'),
2186
    'mimetypes' => array(
2187
      'image/*',
2188
    ),
2189
  );
2190
2191
  // Video.
2192
  $types['video'] = (object) array(
2193
    'api_version' => 1,
2194
    'type' => 'video',
2195
    'label' => t('Video'),
2196
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2197
    'mimetypes' => array(
2198
      'video/*',
2199
    ),
2200
  );
2201
2202
  // Audio.
2203
  $types['audio'] = (object) array(
2204
    'api_version' => 1,
2205
    'type' => 'audio',
2206
    'label' => t('Audio'),
2207
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2208
    'mimetypes' => array(
2209
      'audio/*',
2210
    ),
2211
  );
2212
2213
  // Document.
2214
  $types['document'] = (object) array(
2215
    'api_version' => 1,
2216
    'type' => 'document',
2217
    'label' => t('Document'),
2218
    'description' => t('A <em>Document</em> file is written information.'),
2219
    'mimetypes' => array(
2220 d1c64ea8 Assos Assos
      'text/html',
2221 85ad3d82 Assos Assos
      'text/plain',
2222 d1c64ea8 Assos Assos
      'application/acad',
2223 85ad3d82 Assos Assos
      'application/msword',
2224
      'application/vnd.ms-excel',
2225
      'application/pdf',
2226
      'application/vnd.ms-powerpoint',
2227
      'application/vnd.oasis.opendocument.text',
2228
      'application/vnd.oasis.opendocument.spreadsheet',
2229
      'application/vnd.oasis.opendocument.presentation',
2230
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2231
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2232
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2233 e013fa40 Assos Assos
      'application/zip',
2234
      'application/x-7z-compressed',
2235
      'application/x-tar',
2236
      'application/gzip',
2237 85ad3d82 Assos Assos
    ),
2238
  );
2239
2240
  return $types;
2241
}
2242
2243
/**
2244
 * Implements hook_file_operations().
2245
 */
2246
function file_entity_file_operations() {
2247
  $operations = array(
2248
    'permanent' => array(
2249
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2250
      'callback' => 'file_entity_mass_update',
2251
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2252
    ),
2253
    'temporary' => array(
2254
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2255
      'callback' => 'file_entity_mass_update',
2256
      'callback arguments' => array('updates' => array('status' => 0)),
2257
    ),
2258
    'delete' => array(
2259
      'label' => t('Delete selected files'),
2260
      'callback' => NULL,
2261
    ),
2262
  );
2263
  return $operations;
2264
}
2265
2266
/**
2267
 * Clear the field cache for any entities referencing a specific file.
2268
 *
2269
 * @param object $file
2270
 *   A file object.
2271
 */
2272
function file_entity_invalidate_field_caches($file) {
2273
  $entity_types = &drupal_static(__FUNCTION__);
2274
2275
  // Gather the list of entity types which support field caching.
2276
  if (!isset($entity_types)) {
2277
    $entity_types = array();
2278
    foreach (entity_get_info() as $entity_type => $entity_info) {
2279
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2280
        $entity_types[] = $entity_type;
2281
      }
2282
    }
2283
  }
2284
2285
  // If no entity types support field caching, then there is no work to be done.
2286
  if (empty($entity_types)) {
2287
    return;
2288
  }
2289
2290
  $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();
2291
  if (!empty($records)) {
2292
    $cids = array();
2293
    foreach ($records as $record) {
2294
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2295
    }
2296
    cache_clear_all($cids, 'cache_field');
2297
  }
2298
}
2299
2300
/**
2301
 * Check if a file entity is considered local or not.
2302
 *
2303
 * @param object $file
2304
 *   A file entity object from file_load().
2305
 *
2306
 * @return
2307
 *   TRUE if the file is using a local stream wrapper, or FALSE otherwise.
2308
 */
2309
function file_entity_file_is_local($file) {
2310
  $scheme = file_uri_scheme($file->uri);
2311
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
2312
  return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
2313
}
2314
2315
/**
2316
 * Check if a file entity is considered writeable or not.
2317
 *
2318
 * @param object $file
2319
 *   A file entity object from file_load().
2320
 *
2321
 * @return
2322
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2323
 *   or FALSE otherwise.
2324
 */
2325
function file_entity_file_is_writeable($file) {
2326
  $scheme = file_uri_scheme($file->uri);
2327
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2328
  return !empty($wrappers[$scheme]);
2329
}
2330
2331
/**
2332
 * Pre-render callback for adding validation descriptions to file upload fields.
2333
 */
2334
function file_entity_upload_validators_pre_render($element) {
2335
  if (!empty($element['#upload_validators'])) {
2336
    if (!isset($element['#description'])) {
2337
      $element['#description'] = '';
2338
    }
2339
    if ($element['#description'] !== FALSE) {
2340
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2341
    }
2342
  }
2343
  return $element;
2344
}
2345
2346
/**
2347
 * @name pathauto_file Pathauto integration for the core file module.
2348
 * @{
2349
 */
2350
2351
/**
2352
 * Implements hook_file_insert() on behalf of pathauto.module.
2353
 */
2354
function pathauto_file_insert($file) {
2355
  pathauto_file_update_alias($file, 'insert');
2356
}
2357
2358
/**
2359
 * Implements hook_file_update() on behalf of pathauto.module.
2360
 */
2361
function pathauto_file_update($file) {
2362
  pathauto_file_update_alias($file, 'update');
2363
}
2364
2365
/**
2366
 * Implements hook_file_delete() on behalf of pathauto.module.
2367
 */
2368
function pathauto_file_delete($file) {
2369
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2370
}
2371
2372
/**
2373
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2374
 *
2375
 * Add the Pathauto settings to the file form.
2376
 */
2377
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2378
  $file = $form_state['file'];
2379
  $langcode = pathauto_entity_language('file', $file);
2380
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2381
}
2382
2383
/**
2384
 * Implements hook_file_operations() on behalf of pathauto.module.
2385
 */
2386
function pathauto_file_operations() {
2387
  $operations['pathauto_update_alias'] = array(
2388
    'label' => t('Update URL alias'),
2389
    'callback' => 'pathauto_file_update_alias_multiple',
2390
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2391
  );
2392
  return $operations;
2393
}
2394
2395
/**
2396
 * Update the URL aliases for an individual file.
2397
 *
2398
 * @param $file
2399
 *   A file object.
2400
 * @param $op
2401
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2402
 * @param $options
2403
 *   An optional array of additional options.
2404
 */
2405
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2406
  // Skip processing if the user has disabled pathauto for the file.
2407 ca0757b9 Assos Assos
  if (isset($file->path['pathauto']) && empty($file->path['pathauto']) && empty($options['force'])) {
2408 85ad3d82 Assos Assos
    return;
2409
  }
2410
2411
  $options += array('language' => pathauto_entity_language('file', $file));
2412
2413
  // Skip processing if the file has no pattern.
2414
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2415
    return;
2416
  }
2417
2418 c0bac9d6 Assos Assos
  // Skip processing if pathauto_entity module is enabled.
2419
  if (module_exists('pathauto_entity')) {
2420
    return;
2421
  }
2422
2423 85ad3d82 Assos Assos
  module_load_include('inc', 'pathauto');
2424
  $uri = entity_uri('file', $file);
2425
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2426
}
2427
2428
/**
2429
 * Update the URL aliases for multiple files.
2430
 *
2431
 * @param $fids
2432
 *   An array of file IDs.
2433
 * @param $op
2434
 *   Operation being performed on the files ('insert', 'update' or
2435
 *   'bulkupdate').
2436
 * @param $options
2437
 *   An optional array of additional options.
2438
 */
2439
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2440
  $options += array('message' => FALSE);
2441
2442
  $files = file_load_multiple($fids);
2443
  foreach ($files as $file) {
2444
    pathauto_file_update_alias($file, $op, $options);
2445
  }
2446
2447
  if (!empty($options['message'])) {
2448
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2449
  }
2450
}
2451
2452
/**
2453
 * Update action wrapper for pathauto_file_update_alias().
2454
 */
2455
function pathauto_file_update_action($file, $context = array()) {
2456
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2457
}
2458
2459
/**
2460
 * @} End of "name pathauto_file".
2461
 */
2462
2463
/**
2464
 * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
2465
 */
2466
function path_form_file_entity_edit_alter(&$form, $form_state) {
2467
  // Make sure this does not show up on the delete confirmation form.
2468
  if (empty($form_state['confirm_delete'])) {
2469
    $file = $form_state['file'];
2470
    $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
2471
    $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2472
    $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
2473
    $path = (isset($file->fid) ? path_load($conditions) : array());
2474
    if ($path === FALSE) {
2475
      $path = array();
2476
    }
2477
    $path += array(
2478
      'pid' => NULL,
2479
      'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
2480
      'alias' => '',
2481
      'language' => $langcode,
2482
    );
2483
    $form['path'] = array(
2484
      '#type' => 'fieldset',
2485
      '#title' => t('URL path settings'),
2486
      '#collapsible' => TRUE,
2487
      '#collapsed' => empty($path['alias']),
2488
      '#group' => 'additional_settings',
2489
      '#attributes' => array(
2490
        'class' => array('path-form'),
2491
      ),
2492
      '#attached' => array(
2493
        'js' => array(drupal_get_path('module', 'path') . '/path.js'),
2494
      ),
2495
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
2496
      '#weight' => 30,
2497
      '#tree' => TRUE,
2498
      '#element_validate' => array('path_form_element_validate'),
2499
    );
2500
    $form['path']['alias'] = array(
2501
      '#type' => 'textfield',
2502
      '#title' => t('URL alias'),
2503
      '#default_value' => $path['alias'],
2504
      '#maxlength' => 255,
2505
      '#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.'),
2506
    );
2507
    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
2508
    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
2509
    $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
2510
  }
2511
}
2512
2513
/**
2514
 * Implements hook_file_insert() on behalf of path.module.
2515
 */
2516
function path_file_insert($file) {
2517
  if (isset($file->path)) {
2518
    $path = $file->path;
2519
    $path['alias'] = trim($path['alias']);
2520
    // Only save a non-empty alias.
2521
    if (!empty($path['alias'])) {
2522
      // Ensure fields for programmatic executions.
2523
      $path['source'] = 'file/' . $file->fid;
2524
      // Core does not provide a way to store the file language but contrib
2525
      // modules can do it so we need to take this into account.
2526
      $langcode = entity_language('file', $file);
2527
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2528
      path_save($path);
2529
    }
2530
  }
2531
}
2532
2533
/**
2534
 * Implements hook_file_update() on behalf of path.module.
2535
 */
2536
function path_file_update($file) {
2537
  if (isset($file->path)) {
2538
    $path = $file->path;
2539
    $path['alias'] = trim($path['alias']);
2540
    // Delete old alias if user erased it.
2541
    if (!empty($path['fid']) && empty($path['alias'])) {
2542
      path_delete($path['fid']);
2543
    }
2544
    // Only save a non-empty alias.
2545
    if (!empty($path['alias'])) {
2546
      // Ensure fields for programmatic executions.
2547
      $path['source'] = 'file/' . $file->fid;
2548
      // Core does not provide a way to store the file language but contrib
2549
      // modules can do it so we need to take this into account.
2550
      $langcode = entity_language('file', $file);
2551
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2552
      path_save($path);
2553
    }
2554
  }
2555
}
2556
2557
/**
2558
 * Implements hook_file_delete() on behalf of path.module.
2559
 */
2560
function path_file_delete($file) {
2561
  // Delete all aliases associated with this file.
2562
  path_delete(array('source' => 'file/' . $file->fid));
2563
}
2564
2565
/**
2566
 * Checks if pattern(s) match mimetype(s).
2567
 */
2568
function file_entity_match_mimetypes($needle, $haystack) {
2569
  $needle = is_array($needle) ? $needle : array($needle);
2570
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2571
2572
  foreach ($haystack as $mimetype) {
2573
    foreach ($needle as $search) {
2574
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2575
        return TRUE;
2576
      }
2577
    }
2578
  }
2579
2580
  return FALSE;
2581
}
2582
2583
/**
2584
 * A wrapper function for the PHP function fnmatch().
2585
 *
2586
 * We include this, because Windows servers do not implement fnmatch() until
2587
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2588
 */
2589
function file_entity_fnmatch($pattern, $string) {
2590
  if (!function_exists('fnmatch')) {
2591
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2592
  }
2593
  return fnmatch($pattern, $string);
2594
}
2595
2596
/**
2597
 * Return an URI for a file download.
2598
 */
2599
function file_entity_download_uri($file) {
2600
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2601
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2602
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2603
  }
2604
  return $uri;
2605
}
2606
2607
function file_entity_file_get_mimetype_type($file) {
2608 59ae487e Assos Assos
  if (is_array($file)) {
2609
    $file = (object) $file;
2610
  }
2611 85ad3d82 Assos Assos
  list($type, $subtype) = explode('/', $file->filemime, 2);
2612
  return $type;
2613
}
2614
2615
/**
2616
 * Implements hook_admin_menu_map().
2617
 */
2618
function file_entity_admin_menu_map() {
2619
  if (!user_access('administer file types')) {
2620
    return;
2621
  }
2622
  $map['admin/structure/file-types/manage/%file_type'] = array(
2623
    'parent' => 'admin/structure/file-types',
2624
    'arguments' => array(
2625
      array('%file_type' => array_keys(file_entity_type_get_names())),
2626
    ),
2627
  );
2628
  return $map;
2629
}
2630
2631
/*
2632 ca0757b9 Assos Assos
 * Generates a token to protect a file download URL.
2633
 *
2634
 * This prevents unauthorized crawling of all file download URLs since the
2635
 * {file_managed}.fid column is an auto-incrementing serial field and is easy
2636
 * to guess or attempt many at once. This can be costly both in CPU time
2637
 * and bandwidth.
2638 85ad3d82 Assos Assos
 *
2639 ca0757b9 Assos Assos
 * @see image_style_path_token()
2640 85ad3d82 Assos Assos
 *
2641
 * @param object $file
2642
 *   A file entity object.
2643
 *
2644
 * @return string
2645 ca0757b9 Assos Assos
 *   An eight-character token which can be used to protect file downloads
2646
 *   against denial-of-service attacks.
2647 85ad3d82 Assos Assos
 */
2648
function file_entity_get_download_token($file) {
2649 ca0757b9 Assos Assos
  // Return the first eight characters.
2650
  return substr(drupal_hmac_base64("file/$file->fid/download:" . $file->uri, drupal_get_private_key() . drupal_get_hash_salt()), 0, 8);
2651 85ad3d82 Assos Assos
}
2652
2653
/**
2654
 * Find all fields that are of a certain field type.
2655
 *
2656
 * @param string $field_type
2657
 *   A field type.
2658
 *
2659
 * @return array
2660
 *   An array of field names that match the type $field_type.
2661
 */
2662
function _file_entity_get_fields_by_type($field_type) {
2663
  $return = array();
2664
  if (function_exists('field_info_field_map')) {
2665
    foreach (field_info_field_map() as $field_name => $field) {
2666
      if ($field['type'] == $field_type) {
2667
        $return[$field_name] = $field_name;
2668
      }
2669
    }
2670
  }
2671
  else {
2672
    foreach (field_info_fields() as $field_name => $field) {
2673
      if ($field['type'] == $field_type) {
2674
        $return[$field_name] = $field_name;
2675
      }
2676
    }
2677
  }
2678
  return $return;
2679
}
2680
2681
/**
2682
 * Implements hook_field_attach_load().
2683
 */
2684
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2685
  // Loop over all the entities looking for entities with attached images.
2686
  foreach ($entities as $entity) {
2687
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2688
    // Examine every image field instance attached to this entity's bundle.
2689
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2690
    foreach ($instances as $field_name => $instance) {
2691
      if (!empty($entity->{$field_name})) {
2692
        foreach ($entity->{$field_name} as $langcode => $items) {
2693
          foreach ($items as $delta => $item) {
2694
            // If alt and title text is not specified, fall back to alt and
2695
            // title text on the file.
2696 ca0757b9 Assos Assos
            if (!empty($item['fid']) && (empty($item['alt']) || empty($item['title']))) {
2697 85ad3d82 Assos Assos
              $file = file_load($item['fid']);
2698
              foreach (array('alt', 'title') as $key) {
2699
                if (empty($item[$key]) && !empty($file->{$key})) {
2700
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2701
                }
2702
              }
2703
            }
2704
          }
2705
        }
2706
      }
2707
    }
2708
  }
2709
}
2710
2711
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2712
  $wrappers = array();
2713
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2714
    if (empty($wrapper['private'])) {
2715
      $wrappers['public'][$key] = $wrapper['name'];
2716
    }
2717
    else {
2718
      $wrappers['private'][$key] = $wrapper['name'];
2719
    }
2720
  }
2721
  return $wrappers;
2722
}
2723 0ccfec7f Assos Assos
2724
/**
2725
 * Implements hook_features_pipe_alter() for the file type component.
2726
 */
2727
function file_entity_features_pipe_file_type_alter(&$pipe, $data, $export) {
2728
  foreach ($data as $file_type) {
2729
    $pipe['variable'][] = "pathauto_file_{$file_type}_pattern";
2730
  }
2731
}
2732 59ae487e Assos Assos
2733 3acd948f Assos Assos
/**
2734
 * Implements hook_FORM_ID_alter().
2735
 */
2736
function file_entity_form_system_performance_settings_alter(&$form, &$form_state) {
2737
  $form['bandwidth_optimization']['file_entity_total_count_optimization'] = array(
2738
    '#type' => 'checkbox',
2739
    '#title' => t('Optimize the calculation of the total usage count of files in the files overview.'),
2740
    '#default_value' => variable_get('file_entity_total_count_optimization', FALSE),
2741
    '#description' => t('Recommended if the files admin page loads too slowly due to a high number of files.'),
2742
  );
2743
}