Projet

Général

Profil

Paste
Télécharger (84,8 ko) Statistiques
| Branche: | Révision:

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

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