Projet

Général

Profil

Paste
Télécharger (85,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / file_entity / file_entity.module @ 503b3f7b

1
<?php
2

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

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

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

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

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

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

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

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

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

    
87
/**
88
 * Implements hook_help().
89
 */
90
function file_entity_help($path, $arg) {
91
  switch ($path) {
92
    case 'admin/structure/file-types':
93
      $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>';
94
      return $output;
95
    case 'admin/structure/file-types/manage/%/display/preview':
96
    case 'admin/structure/file-types/manage/%/file-display/preview':
97
      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');
98
      break;
99
  }
100
}
101

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

    
161
  $items['admin/content/file'] = array(
162
    'title' => 'Files',
163
    'description' => 'Manage files used on your site.',
164
    'page callback' => 'drupal_get_form',
165
    'page arguments' => array('file_entity_admin_file'),
166
    'access arguments' => array('administer files'),
167
    'type' => MENU_LOCAL_TASK | MENU_NORMAL_ITEM,
168
    'file' => 'file_entity.admin.inc',
169
  );
170
  $items['admin/content/file/list'] = array(
171
    'title' => 'List',
172
    'type' => MENU_DEFAULT_LOCAL_TASK,
173
  );
174

    
175
  // general view, edit, delete for files
176
  $items['file/add'] = array(
177
    'title' => 'Add file',
178
    'page callback' => 'drupal_get_form',
179
    'page arguments' => array('file_entity_add_upload', array()),
180
    'access callback' => 'file_entity_access',
181
    'access arguments' => array('create'),
182
    'file' => 'file_entity.pages.inc',
183
  );
184
  if (module_exists('plupload') && module_exists('multiform')) {
185
    $items['file/add']['page arguments'] = array('file_entity_add_upload_multiple');
186
  }
187
  $items['file/add/upload'] = array(
188
    'title' => 'Upload',
189
    'type' => MENU_DEFAULT_LOCAL_TASK,
190
    'weight' => -10,
191
  );
192
  $items['file/%file'] = array(
193
    'title callback' => 'entity_label',
194
    'title arguments' => array('file', 1),
195
    // The page callback also invokes drupal_set_title() in case
196
    // the menu router's title is overridden by a menu link.
197
    'page callback' => 'file_entity_view_page',
198
    'page arguments' => array(1),
199
    'access callback' => 'file_entity_access',
200
    'access arguments' => array('view', 1),
201
    'file' => 'file_entity.pages.inc',
202
  );
203
  $items['file/%file/view'] = array(
204
    'title' => 'View',
205
    'type' => MENU_DEFAULT_LOCAL_TASK,
206
    'weight' => -10,
207
  );
208
  $items['file/%file/usage'] = array(
209
    'title' => 'Usage',
210
    'page callback' => 'file_entity_usage_page',
211
    'page arguments' => array(1),
212
    'access callback' => 'file_entity_access',
213
    'access arguments' => array('update', 1),
214
    'type' => MENU_LOCAL_TASK,
215
    'context' => MENU_CONTEXT_PAGE,
216
    'file' => 'file_entity.pages.inc',
217
  );
218
  $items['file/%file/download'] = array(
219
    'title' => 'Download',
220
    'page callback' => 'file_entity_download_page',
221
    'page arguments' => array(1),
222
    'access callback' => 'file_entity_access',
223
    'access arguments' => array('download', 1),
224
    'file' => 'file_entity.pages.inc',
225
    'type' => MENU_CALLBACK,
226
  );
227
  $items['file/%file/edit'] = array(
228
    'title' => 'Edit',
229
    'page callback' => 'drupal_get_form',
230
    'page arguments' => array('file_entity_edit', 1),
231
    'access callback' => 'file_entity_access',
232
    'access arguments' => array('update', 1),
233
    'weight' => 0,
234
    'type' => MENU_LOCAL_TASK,
235
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
236
    'file' => 'file_entity.pages.inc',
237
  );
238
  $items['file/%file/delete'] = array(
239
    'title' => 'Delete',
240
    'page callback' => 'drupal_get_form',
241
    'page arguments'  => array('file_entity_delete_form', 1),
242
    'access callback' => 'file_entity_access',
243
    'access arguments' => array('delete', 1),
244
    'weight' => 1,
245
    'type' => MENU_LOCAL_TASK,
246
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
247
    'file' => 'file_entity.pages.inc',
248
  );
249

    
250
  // Attach a "Manage file display" tab to each file type in the same way that
251
  // Field UI attaches "Manage fields" and "Manage display" tabs. Note that
252
  // Field UI does not have to be enabled; we're just using the same IA pattern
253
  // here for attaching the "Manage file display" page.
254
  $entity_info = entity_get_info('file');
255
  foreach ($entity_info['bundles'] as $file_type => $bundle_info) {
256
    if (isset($bundle_info['admin'])) {
257
      // Get the base path and access.
258
      $path = $bundle_info['admin']['path'];
259
      $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
260
      $access += array(
261
        'access callback' => 'user_access',
262
        'access arguments' => array('administer file types'),
263
      );
264

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

    
270
      $items[$path] = array(
271
        'title' => 'Edit file type',
272
        'title callback' => 'file_entity_type_get_name',
273
        'title arguments' => array(4),
274
        'page callback' => 'drupal_get_form',
275
        'page arguments' => array('file_entity_file_type_form', $file_type_argument),
276
        'file' => 'file_entity.admin.inc',
277
      ) + $access;
278

    
279
      // Add the 'File type settings' tab.
280
      $items["$path/edit"] = array(
281
        'title' => 'Edit',
282
        'type' => MENU_DEFAULT_LOCAL_TASK,
283
      );
284

    
285
      // Add the 'Manage file display' tab.
286
      $items["$path/file-display"] = array(
287
        'title' => 'Manage file display',
288
        'page callback' => 'drupal_get_form',
289
        'page arguments' => array('file_entity_file_display_form', $file_type_argument, 'default'),
290
        'type' => MENU_LOCAL_TASK,
291
        'weight' => 3,
292
        'file' => 'file_entity.admin.inc',
293
      ) + $access;
294

    
295
      // Add a secondary tab for each view mode.
296
      $weight = 0;
297
      $view_modes = array('default' => array('label' => t('Default'))) + $entity_info['view modes'];
298
      foreach ($view_modes as $view_mode => $view_mode_info) {
299
        $items["$path/file-display/$view_mode"] = array(
300
          'title' => $view_mode_info['label'],
301
          'page arguments' => array('file_entity_file_display_form', $file_type_argument, $view_mode),
302
          'type' => ($view_mode == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK),
303
          'weight' => ($view_mode == 'default' ? -10 : $weight++),
304
          'file' => 'file_entity.admin.inc',
305
          // View modes for which the 'custom settings' flag isn't TRUE are
306
          // disabled via this access callback. This needs to extend, rather
307
          // than override normal $access rules.
308
          'access callback' => '_file_entity_view_mode_menu_access',
309
          'access arguments' => array_merge(array($file_type_argument, $view_mode, $access['access callback']), $access['access arguments']),
310
        );
311
      }
312
    }
313
  }
314

    
315
  $items['admin/config/media/file-settings'] = array(
316
    'title' => 'File settings',
317
    'description' => 'Configure allowed file extensions, default alt and title sources, and the file upload wizard.',
318
    'page callback' => 'drupal_get_form',
319
    'page arguments' => array('file_entity_settings_form'),
320
    'access arguments' => array('administer site configuration'),
321
    'file' => 'file_entity.admin.inc',
322
  );
323

    
324
  // Optional devel module integration
325
  if (module_exists('devel')) {
326
    $items['file/%file/devel'] = array(
327
      'title' => 'Devel',
328
      'page callback' => 'devel_load_object',
329
      'page arguments' => array('file', 1),
330
      'access arguments' => array('access devel information'),
331
      'type' => MENU_LOCAL_TASK,
332
      'file' => 'devel.pages.inc',
333
      'file path' => drupal_get_path('module', 'devel'),
334
      'weight' => 100,
335
    );
336
    $items['file/%file/devel/load'] = array(
337
      'title' => 'Load',
338
      'type' => MENU_DEFAULT_LOCAL_TASK,
339
    );
340
    $items['file/%file/devel/render'] = array(
341
      'title' => 'Render',
342
      'page callback' => 'devel_render_object',
343
      'page arguments' => array('file', 1),
344
      'access arguments' => array('access devel information'),
345
      'file' => 'devel.pages.inc',
346
      'file path' => drupal_get_path('module', 'devel'),
347
      'type' => MENU_LOCAL_TASK,
348
      'weight' => 100,
349
    );
350
    if (module_exists('token')) {
351
      $items['file/%file/devel/token'] = array(
352
        'title' => 'Tokens',
353
        'page callback' => 'token_devel_token_object',
354
        'page arguments' => array('file', 1),
355
        'access arguments' => array('access devel information'),
356
        'type' => MENU_LOCAL_TASK,
357
        'file' => 'token.pages.inc',
358
        'file path' => drupal_get_path('module', 'token'),
359
        'weight' => 5,
360
      );
361
    }
362
  }
363

    
364
  return $items;
365
}
366

    
367
/**
368
 * Implements hook_menu_local_tasks_alter().
369
 */
370
function file_entity_menu_local_tasks_alter(&$data, $router_item, $root_path) {
371
  // Add action link to 'file/add' on 'admin/content/file' page.
372
  if ($root_path == 'admin/content/file') {
373
    $item = menu_get_item('file/add');
374
    if (!empty($item['access'])) {
375
      $data['actions']['output'][] = array(
376
        '#theme' => 'menu_local_action',
377
        '#link' => $item,
378
        '#weight' => $item['weight'],
379
      );
380
    }
381
  }
382
}
383

    
384
/**
385
 * Implement hook_permission().
386
 */
387
function file_entity_permission() {
388
  $permissions = array(
389
    'bypass file access' => array(
390
      'title' => t('Bypass file access control'),
391
      'description' => t('View, edit and delete all files regardless of permission restrictions.'),
392
      'restrict access' => TRUE,
393
    ),
394
    'administer file types' => array(
395
      'title' => t('Administer file types'),
396
      'restrict access' => TRUE,
397
    ),
398
    'administer files' => array(
399
      'title' => t('Administer files'),
400
      'restrict access' => TRUE,
401
    ),
402
    'create files' => array(
403
      'title' => t('Add and upload new files'),
404
    ),
405
    'view own private files' => array(
406
      'title' => t('View own private files'),
407
    ),
408
    'view own files' => array(
409
      'title' => t('View own files'),
410
    ),
411
    'view private files' => array(
412
      'title' => t('View private files'),
413
      'restrict access' => TRUE,
414
    ),
415
    'view files' => array(
416
      'title' => t('View files'),
417
    ),
418
  );
419

    
420
  // Add description for the 'View file details' and 'View own private file
421
  // details' permissions to show which stream wrappers they apply to.
422
  $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
423
  $wrappers += array('public' => array(t('None')), 'private' => array(t('None')));
424

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

    
428
  // Generate standard file permissions for all applicable file types.
429
  foreach (file_entity_permissions_get_configured_types() as $type) {
430
    $permissions += file_entity_list_permissions($type);
431
  }
432

    
433
  return $permissions;
434
}
435

    
436
/*
437
 * Implements hook_cron_queue_info().
438
 */
439
function file_entity_cron_queue_info() {
440
  $queues['file_entity_type_determine'] = array(
441
    'worker callback' => 'file_entity_type_determine',
442
  );
443
  return $queues;
444
}
445

    
446
/*
447
 * Determines file type for a given file ID and saves the file.
448
 *
449
 * @param $fid
450
 *   A file ID.
451
 */
452
function file_entity_type_determine($fid) {
453
  if ($file = file_load($fid)) {
454
    // The file type will be automatically determined when saving the file.
455
    file_save($file);
456
  }
457
}
458

    
459
/**
460
 * Gather the rankings from the the hook_ranking implementations.
461
 *
462
 * @param $query
463
 *   A query object that has been extended with the Search DB Extender.
464
 */
465
function _file_entity_rankings(SelectQueryExtender $query) {
466
  if ($ranking = module_invoke_all('file_ranking')) {
467
    $tables = &$query->getTables();
468
    foreach ($ranking as $rank => $values) {
469
      if ($file_rank = variable_get('file_entity_rank_' . $rank, 0)) {
470
        // If the table defined in the ranking isn't already joined, then add it.
471
        if (isset($values['join']) && !isset($tables[$values['join']['alias']])) {
472
          $query->addJoin($values['join']['type'], $values['join']['table'], $values['join']['alias'], $values['join']['on']);
473
        }
474
        $arguments = isset($values['arguments']) ? $values['arguments'] : array();
475
        $query->addScore($values['score'], $arguments, $file_rank);
476
      }
477
    }
478
  }
479
}
480

    
481
/**
482
 * Implements hook_search_info().
483
 */
484
function file_entity_search_info() {
485
  return array(
486
    'title' => 'Files',
487
    'path' => 'file',
488
  );
489
}
490

    
491
/**
492
 * Implements hook_search_access().
493
 */
494
function file_entity_search_access() {
495
  return user_access('view own private files') || user_access('view own files') || user_access('view private files') || user_access('view files');
496
}
497

    
498
/**
499
 * Implements hook_search_reset().
500
 */
501
function file_entity_search_reset() {
502
  db_update('search_dataset')
503
    ->fields(array('reindex' => REQUEST_TIME))
504
    ->condition('type', 'file')
505
    ->execute();
506
}
507

    
508
/**
509
 * Implements hook_search_status().
510
 */
511
function file_entity_search_status() {
512
  $total = db_query('SELECT COUNT(*) FROM {file_managed}')->fetchField();
513
  $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();
514
  return array('remaining' => $remaining, 'total' => $total);
515
}
516

    
517
/**
518
 * Implements hook_search_admin().
519
 */
520
function file_entity_search_admin() {
521
  // Output form for defining rank factor weights.
522
  $form['file_ranking'] = array(
523
    '#type' => 'fieldset',
524
    '#title' => t('File ranking'),
525
  );
526
  $form['file_ranking']['#theme'] = 'file_entity_search_admin';
527
  $form['file_ranking']['info'] = array(
528
    '#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>'
529
  );
530

    
531
  // Note: reversed to reflect that higher number = higher ranking.
532
  $options = drupal_map_assoc(range(0, 10));
533
  foreach (module_invoke_all('file_ranking') as $var => $values) {
534
    $form['file_ranking']['factors']['file_entity_rank_' . $var] = array(
535
      '#title' => $values['title'],
536
      '#type' => 'select',
537
      '#options' => $options,
538
      '#default_value' => variable_get('file_entity_rank_' . $var, 0),
539
    );
540
  }
541
  return $form;
542
}
543

    
544
/**
545
 * Implements hook_search_execute().
546
 */
547
function file_entity_search_execute($keys = NULL, $conditions = NULL) {
548
  global $user;
549

    
550
  // Build matching conditions
551
  $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
552
  $query->join('file_managed', 'fm', 'fm.fid = i.sid');
553
  $query->searchExpression($keys, 'file');
554

    
555
  // Insert special keywords.
556
  $query->setOption('type', 'fm.type');
557
  if ($query->setOption('term', 'ti.tid')) {
558
    $query->join('taxonomy_index', 'ti', 'fm.fid = ti.fid');
559
  }
560
  // Only continue if the first pass query matches.
561
  if (!$query->executeFirstPass()) {
562
    return array();
563
  }
564

    
565
  // Add the ranking expressions.
566
  _file_entity_rankings($query);
567

    
568
  // Load results.
569
  $find = $query
570
    ->limit(10)
571
    ->addTag('file_access')
572
    ->execute();
573
  $results = array();
574
  foreach ($find as $item) {
575
    // Render the file.
576
    $file = file_load($item->sid);
577
    $build = file_view($file, 'search_result');
578
    unset($build['#theme']);
579
    $file->rendered = drupal_render($build);
580

    
581
    $extra = module_invoke_all('file_entity_search_result', $file);
582

    
583
    $types = file_entity_type_get_names();
584

    
585
    $uri = entity_uri('file', $file);
586
    $results[] = array(
587
      'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE))),
588
      'type' => check_plain($types[$file->type]),
589
      'title' => $file->filename,
590
      'user' => theme('username', array('account' => user_load($file->uid))),
591
      'date' => $file->timestamp,
592
      'file' => $file,
593
      'extra' => $extra,
594
      'score' => $item->calculated_score,
595
      'snippet' => search_excerpt($keys, $file->rendered),
596
      'language' => function_exists('entity_language') ? entity_language('file', $file) : NULL,
597
    );
598
  }
599
  return $results;
600
}
601

    
602
/**
603
 * Implements hook_file_ranking().
604
 */
605
function file_entity_file_ranking() {
606
  // Create the ranking array and add the basic ranking options.
607
  $ranking = array(
608
    'relevance' => array(
609
      'title' => t('Keyword relevance'),
610
      // Average relevance values hover around 0.15
611
      'score' => 'i.relevance',
612
    ),
613
  );
614

    
615
  // Add relevance based on creation date.
616
  if ($file_cron_last = variable_get('file_entity_cron_last', 0)) {
617
    $ranking['timestamp'] = array(
618
      'title' => t('Recently posted'),
619
      // Exponential decay with half-life of 6 months, starting at last indexed file
620
      'score' => 'POW(2.0, (fm.timestamp - :file_cron_last) * 6.43e-8)',
621
      'arguments' => array(':file_cron_last' => $file_cron_last),
622
    );
623
  }
624
  return $ranking;
625
}
626

    
627
/**
628
 * Returns HTML for the file ranking part of the search settings admin page.
629
 *
630
 * @param $variables
631
 *   An associative array containing:
632
 *   - form: A render element representing the form.
633
 *
634
 * @ingroup themeable
635
 */
636
function theme_file_entity_search_admin($variables) {
637
  $form = $variables['form'];
638

    
639
  $output = drupal_render($form['info']);
640

    
641
  $header = array(t('Factor'), t('Weight'));
642
  foreach (element_children($form['factors']) as $key) {
643
    $row = array();
644
    $row[] = $form['factors'][$key]['#title'];
645
    $form['factors'][$key]['#title_display'] = 'invisible';
646
    $row[] = drupal_render($form['factors'][$key]);
647
    $rows[] = $row;
648
  }
649
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
650

    
651
  $output .= drupal_render_children($form);
652
  return $output;
653
}
654

    
655
/**
656
 * Implements hook_update_index().
657
 */
658
function file_entity_update_index() {
659
  $limit = (int)variable_get('search_cron_limit', 100);
660

    
661
  $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'));
662

    
663
  foreach ($result as $file) {
664
    _file_entity_index_file($file);
665
  }
666
}
667

    
668
/**
669
 * Index a single file.
670
 *
671
 * @param $file
672
 *   The file to index.
673
 */
674
function _file_entity_index_file($file) {
675
  $file = file_load($file->fid);
676

    
677
  // Save the creation time of the most recent indexed file, for the search
678
  // results half-life calculation.
679
  variable_set('file_entity_cron_last', $file->timestamp);
680

    
681
  // Render the file.
682
  $build = file_view($file, 'search_index');
683
  unset($build['#theme']);
684
  $file->rendered = drupal_render($build);
685

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

    
688
  // Fetch extra data normally not visible
689
  $extra = module_invoke_all('file_entity_update_index', $file);
690
  foreach ($extra as $t) {
691
    $text .= $t;
692
  }
693

    
694
  // Update index
695
  search_index($file->fid, 'file', $text);
696
}
697

    
698
/**
699
 * Implements hook_form_FORM_ID_alter().
700
 */
701
function file_entity_form_search_form_alter(&$form, $form_state) {
702
  if (isset($form['module']) && $form['module']['#value'] == 'file_entity' && user_access('use advanced search')) {
703
    // Keyword boxes:
704
    $form['advanced'] = array(
705
      '#type' => 'fieldset',
706
      '#title' => t('Advanced search'),
707
      '#collapsible' => TRUE,
708
      '#collapsed' => TRUE,
709
      '#attributes' => array('class' => array('search-advanced')),
710
    );
711
    $form['advanced']['keywords'] = array(
712
      '#prefix' => '<div class="criterion">',
713
      '#suffix' => '</div>',
714
    );
715
    $form['advanced']['keywords']['or'] = array(
716
      '#type' => 'textfield',
717
      '#title' => t('Containing any of the words'),
718
      '#size' => 30,
719
      '#maxlength' => 255,
720
    );
721
    $form['advanced']['keywords']['phrase'] = array(
722
      '#type' => 'textfield',
723
      '#title' => t('Containing the phrase'),
724
      '#size' => 30,
725
      '#maxlength' => 255,
726
    );
727
    $form['advanced']['keywords']['negative'] = array(
728
      '#type' => 'textfield',
729
      '#title' => t('Containing none of the words'),
730
      '#size' => 30,
731
      '#maxlength' => 255,
732
    );
733

    
734
    // File types:
735
    $types = array_map('check_plain', file_entity_type_get_names());
736
    $form['advanced']['type'] = array(
737
      '#type' => 'checkboxes',
738
      '#title' => t('Only of the type(s)'),
739
      '#prefix' => '<div class="criterion">',
740
      '#suffix' => '</div>',
741
      '#options' => $types,
742
    );
743
    $form['advanced']['submit'] = array(
744
      '#type' => 'submit',
745
      '#value' => t('Advanced search'),
746
      '#prefix' => '<div class="action">',
747
      '#suffix' => '</div>',
748
      '#weight' => 100,
749
    );
750

    
751
    $form['#validate'][] = 'file_entity_search_validate';
752
  }
753
}
754

    
755
/**
756
 * Form API callback for the search form. Registered in file_entity_form_alter().
757
 */
758
function file_entity_search_validate($form, &$form_state) {
759
  // Initialize using any existing basic search keywords.
760
  $keys = $form_state['values']['processed_keys'];
761

    
762
  // Insert extra restrictions into the search keywords string.
763
  if (isset($form_state['values']['type']) && is_array($form_state['values']['type'])) {
764
    // Retrieve selected types - Form API sets the value of unselected
765
    // checkboxes to 0.
766
    $form_state['values']['type'] = array_filter($form_state['values']['type']);
767
    if (count($form_state['values']['type'])) {
768
      $keys = search_expression_insert($keys, 'type', implode(',', array_keys($form_state['values']['type'])));
769
    }
770
  }
771

    
772
  if (isset($form_state['values']['term']) && is_array($form_state['values']['term']) && count($form_state['values']['term'])) {
773
    $keys = search_expression_insert($keys, 'term', implode(',', $form_state['values']['term']));
774
  }
775
  if ($form_state['values']['or'] != '') {
776
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['or'], $matches)) {
777
      $keys .= ' ' . implode(' OR ', $matches[1]);
778
    }
779
  }
780
  if ($form_state['values']['negative'] != '') {
781
    if (preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' . $form_state['values']['negative'], $matches)) {
782
      $keys .= ' -' . implode(' -', $matches[1]);
783
    }
784
  }
785
  if ($form_state['values']['phrase'] != '') {
786
    $keys .= ' "' . str_replace('"', ' ', $form_state['values']['phrase']) . '"';
787
  }
788
  if (!empty($keys)) {
789
    form_set_value($form['basic']['processed_keys'], trim($keys), $form_state);
790
  }
791
}
792

    
793
/**
794
 * Implements hook_admin_paths().
795
 */
796
function file_entity_admin_paths() {
797
  $paths = array(
798
    'file/add' => TRUE,
799
    'file/add/*' => TRUE,
800
    'file/*/edit' => TRUE,
801
    'file/*/usage' => TRUE,
802
    'file/*/delete' => TRUE,
803
  );
804
  return $paths;
805
}
806

    
807
/**
808
 * Implements hook_action_info_alter().
809
 */
810
function file_entity_action_info_alter(&$actions) {
811
  if (module_exists('pathauto')) {
812
    $actions['pathauto_file_update_action'] = array(
813
      'type' => 'file',
814
      'label' => t('Update file alias'),
815
      'configurable' => FALSE,
816
    );
817
  }
818
}
819

    
820
/**
821
 * Implements hook_theme().
822
 */
823
function file_entity_theme() {
824
  return array(
825
    'file_entity' => array(
826
      'render element' => 'elements',
827
      'template' => 'file_entity',
828
    ),
829
    'file_entity_search_admin' => array(
830
      'render element' => 'form',
831
    ),
832
    'file_entity_file_type_overview' => array(
833
      'variables' => array('label' => NULL, 'description' => NULL),
834
      'file' => 'file_entity.admin.inc',
835
    ),
836
    'file_entity_file_display_order' => array(
837
      'render element' => 'element',
838
      'file' => 'file_entity.admin.inc',
839
    ),
840
    'file_entity_file_link' => array(
841
      'variables' => array('file' => NULL, 'icon_directory' => NULL),
842
      'file' => 'file_entity.theme.inc',
843
    ),
844
    'file_entity_download_link' => array(
845
      'variables' => array('file' => NULL, 'icon_directory' => NULL, 'text' => NULL),
846
      'file' => 'file_entity.theme.inc',
847
    ),
848
    'file_entity_file_audio' => array(
849
      'variables' => array(
850
        'files' => array(),
851
        'controls' => TRUE,
852
        'autoplay' => FALSE,
853
        'loop' => FALSE,
854
      ),
855
      'file' => 'file_entity.theme.inc',
856
    ),
857
    'file_entity_file_video' => array(
858
      'variables' => array(
859
        'files' => array(),
860
        'controls' => TRUE,
861
        'autoplay' => FALSE,
862
        'loop' => FALSE,
863
        'muted' => FALSE,
864
        'width' => NULL,
865
        'height' => NULL,
866
      ),
867
      'file' => 'file_entity.theme.inc',
868
    ),
869
  );
870
}
871

    
872
/**
873
 * Implements hook_entity_info_alter().
874
 *
875
 * Extends the core file entity to be fieldable. The file type is used as the
876
 * bundle key. File types are implemented as CTools exportables, so modules can
877
 * define default file types via hook_file_default_types(), and the
878
 * administrator can override the default types or add custom ones via
879
 * admin/structure/file-types.
880
 */
881
function file_entity_entity_info_alter(&$entity_info) {
882
  $entity_info['file']['fieldable'] = TRUE;
883
  $entity_info['file']['entity keys']['bundle'] = 'type';
884
  $entity_info['file']['bundle keys']['bundle'] = 'type';
885
  $entity_info['file']['bundles'] = array();
886
  $entity_info['file']['uri callback'] = 'file_entity_uri';
887
  $entity_info['file']['view modes']['teaser'] = array(
888
    'label' => t('Teaser'),
889
    'custom settings' => TRUE,
890
  );
891
  $entity_info['file']['view modes']['full'] = array(
892
    'label' => t('Full content'),
893
    'custom settings' => FALSE,
894
  );
895
  $entity_info['file']['view modes']['preview'] = array(
896
    'label' => t('Preview'),
897
    'custom settings' => TRUE,
898
  );
899
  $entity_info['file']['view modes']['rss'] = array(
900
    'label' => t('RSS'),
901
    'custom settings' => FALSE,
902
  );
903

    
904
  // Search integration is provided by file_entity.module, so search-related
905
  // view modes for files are defined here and not in search.module.
906
  if (module_exists('search')) {
907
    $entity_info['file']['view modes']['search_index'] = array(
908
      'label' => t('Search index'),
909
      'custom settings' => FALSE,
910
    );
911
    $entity_info['file']['view modes']['search_result'] = array(
912
      'label' => t('Search result'),
913
      'custom settings' => FALSE,
914
    );
915
  }
916

    
917
  foreach (file_type_get_enabled_types() as $type) {
918
    $entity_info['file']['bundles'][$type->type] = array(
919
      'label' => $type->label,
920
      'admin' => array(
921
        'path' => 'admin/structure/file-types/manage/%file_type',
922
        'real path' => 'admin/structure/file-types/manage/' . $type->type,
923
        'bundle argument' => 4,
924
      ),
925
    );
926
  }
927

    
928
  // Enable Metatag support.
929
  $entity_info['file']['metatags'] = TRUE;
930

    
931
  // Ensure some of the Entity API callbacks are supported.
932
  $entity_info['file']['creation callback'] = 'entity_metadata_create_object';
933
  $entity_info['file']['view callback'] = 'file_view_multiple';
934
  $entity_info['file']['edit callback'] = 'file_entity_metadata_form_file';
935
  $entity_info['file']['access callback'] = 'file_entity_access';
936

    
937
  // Add integration with the Title module for file name replacement support.
938
  $entity_info['file']['field replacement'] = array(
939
    'filename' => array(
940
      'field' => array(
941
        'type' => 'text',
942
        'cardinality' => 1,
943
        'translatable' => TRUE,
944
      ),
945
      'instance' => array(
946
        'label' => t('File name'),
947
        'description' => t('A field replacing file name.'),
948
        'required' => TRUE,
949
        'settings' => array(
950
          'text_processing' => 0,
951
        ),
952
        'widget' => array(
953
          'weight' => -5,
954
        ),
955
        'display' => array(
956
          'default' => array(
957
            'type' => 'hidden',
958
          ),
959
        ),
960
      ),
961
      'preprocess_key' => 'filename',
962
    ),
963
  );
964
}
965

    
966
/**
967
 * Implements hook_entity_property_info().
968
 */
969
function file_entity_entity_property_info() {
970
  $info['file']['properties']['type'] = array(
971
    'label' => t('File type'),
972
    'type' => 'token',
973
    'description' => t('The type of the file.'),
974
    'setter callback' => 'entity_property_verbatim_set',
975
    'setter permission' => 'administer files',
976
    'options list' => 'file_entity_type_get_names',
977
    'required' => TRUE,
978
    'schema field' => 'type',
979
  );
980

    
981
  return $info;
982
}
983

    
984
/**
985
 * Implements hook_field_display_ENTITY_TYPE_alter().
986
 */
987
function file_entity_field_display_file_alter(&$display, $context) {
988
  // Hide field labels in search index.
989
  if ($context['view_mode'] == 'search_index') {
990
    $display['label'] = 'hidden';
991
  }
992
}
993

    
994
/**
995
 * URI callback for file entities.
996
 */
997
function file_entity_uri($file) {
998
  $uri['path'] = 'file/' . $file->fid;
999
  return $uri;
1000
}
1001

    
1002
/**
1003
 * Entity API callback to get the form of a file entity.
1004
 */
1005
function file_entity_metadata_form_file($file) {
1006
  // Pre-populate the form-state with the right form include.
1007
  $form_state['build_info']['args'] = array($file);
1008
  form_load_include($form_state, 'inc', 'file_entity', 'file_entity.pages');
1009
  return drupal_build_form('file_entity_edit', $form_state);
1010
}
1011

    
1012
/**
1013
 * Implements hook_ctools_plugin_directory().
1014
 */
1015

    
1016
function file_entity_ctools_plugin_directory($module, $type) {
1017
  if ($module == 'ctools' && $type == 'content_types') {
1018
    return 'plugins/' . $type;
1019
  }
1020
}
1021

    
1022
/**
1023
 * Implements hook_field_extra_fields().
1024
 *
1025
 * Adds 'file' as an extra field, so that its display and form component can be
1026
 * weighted relative to the fields that are added to file entity bundles.
1027
 */
1028
function file_entity_field_extra_fields() {
1029
  $info = array();
1030

    
1031
  if ($file_type_names = file_entity_type_get_names()) {
1032
    foreach ($file_type_names as $type => $name) {
1033
      $info['file'][$type]['form']['filename'] = array(
1034
        'label' => t('File name'),
1035
        'description' => t('File name'),
1036
        'weight' => -10,
1037
      );
1038
      $info['file'][$type]['form']['preview'] = array(
1039
        'label' => t('File'),
1040
        'description' => t('File preview'),
1041
        'weight' => -5,
1042
      );
1043
      $info['file'][$type]['display']['file'] = array(
1044
        'label' => t('File'),
1045
        'description' => t('File display'),
1046
        'weight' => 0,
1047
      );
1048
    }
1049
  }
1050

    
1051
  return $info;
1052
}
1053

    
1054
/**
1055
 * Implements hook_file_formatter_info().
1056
 */
1057
function file_entity_file_formatter_info() {
1058
  $formatters = array();
1059

    
1060
  // Allow file field formatters to be reused for displaying the file entity's
1061
  // file pseudo-field.
1062
  foreach (field_info_formatter_types() as $key => $formatter) {
1063
    if (array_intersect($formatter['field types'], array('file', 'image'))) {
1064
      $key = 'file_field_' . $key;
1065
      $formatters[$key] = array(
1066
        'label' => $formatter['label'],
1067
        'description' => !empty($formatter['description']) ? $formatter['description'] : '',
1068
        'view callback' => 'file_entity_file_formatter_file_field_view',
1069
      );
1070
      if (!empty($formatter['settings'])) {
1071
        $formatters[$key] += array(
1072
          'default settings' => $formatter['settings'],
1073
          'settings callback' => 'file_entity_file_formatter_file_field_settings',
1074
        );
1075
      }
1076
      if (!empty($formatter['file formatter'])) {
1077
        $formatters[$key] += $formatter['file formatter'];
1078
      }
1079
    }
1080
  }
1081

    
1082
  // Add a simple file formatter for displaying an image in a chosen style.
1083
  if (module_exists('image')) {
1084
    $formatters['file_image'] = array(
1085
      'label' => t('Image'),
1086
      'default settings' => array(
1087
        'image_style' => '',
1088
        'alt' => '[file:field_file_image_alt_text]',
1089
        'title' => '[file:field_file_image_title_text]'
1090
      ),
1091
      'view callback' => 'file_entity_file_formatter_file_image_view',
1092
      'settings callback' => 'file_entity_file_formatter_file_image_settings',
1093
      'hidden' => TRUE,
1094
      'mime types' => array('image/*'),
1095
    );
1096
  }
1097

    
1098
  return $formatters;
1099
}
1100

    
1101
/**
1102
 * Implements hook_file_formatter_FORMATTER_view().
1103
 *
1104
 * This function provides a bridge to the field formatter API, so that file
1105
 * field formatters can be reused for displaying the file entity's file
1106
 * pseudo-field.
1107
 */
1108
function file_entity_file_formatter_file_field_view($file, $display, $langcode) {
1109
  if (strpos($display['type'], 'file_field_') === 0) {
1110
    $field_formatter_type = substr($display['type'], strlen('file_field_'));
1111
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1112
    if (isset($field_formatter_info['module'])) {
1113
      // Set $display['type'] to what hook_field_formatter_*() expects.
1114
      $display['type'] = $field_formatter_type;
1115

    
1116
      // Set $items to what file field formatters expect. See file_field_load(),
1117
      // and note that, here, $file is already a fully loaded entity.
1118
      $items = array((array) $file);
1119

    
1120
      // Invoke hook_field_formatter_prepare_view() and
1121
      // hook_field_formatter_view(). Note that we are reusing field formatter
1122
      // functions, but we are not displaying a Field API field, so we set
1123
      // $field and $instance accordingly, and do not invoke
1124
      // hook_field_prepare_view(). This assumes that the formatter functions do
1125
      // not rely on $field or $instance. A module that implements formatter
1126
      // functions that rely on $field or $instance (and therefore, can only be
1127
      // used for real fields) can prevent this formatter from being used on the
1128
      // pseudo-field by removing it within hook_file_formatter_info_alter().
1129
      $field = $instance = NULL;
1130
      if (($function = ($field_formatter_info['module'] . '_field_formatter_prepare_view')) && function_exists($function)) {
1131
        $fid = $file->fid;
1132
        // hook_field_formatter_prepare_view() alters $items by reference.
1133
        $grouped_items = array($fid => &$items);
1134
        $function('file', array($fid => $file), $field, array($fid => $instance), $langcode, $grouped_items, array($fid => $display));
1135
      }
1136
      if (($function = ($field_formatter_info['module'] . '_field_formatter_view')) && function_exists($function)) {
1137
        $element = $function('file', $file, $field, $instance, $langcode, $items, $display);
1138
        // We passed the file as $items[0], so return the corresponding element.
1139
        if (isset($element[0])) {
1140
          return $element[0];
1141
        }
1142
      }
1143
    }
1144
  }
1145
}
1146

    
1147
/**
1148
 * Implements hook_file_formatter_FORMATTER_settings().
1149
 *
1150
 * This function provides a bridge to the field formatter API, so that file
1151
 * field formatters can be reused for displaying the file entity's file
1152
 * pseudo-field.
1153
 */
1154
function file_entity_file_formatter_file_field_settings($form, &$form_state, $settings, $formatter_type, $file_type, $view_mode) {
1155
  if (strpos($formatter_type, 'file_field_') === 0) {
1156
    $field_formatter_type = substr($formatter_type, strlen('file_field_'));
1157
    $field_formatter_info = field_info_formatter_types($field_formatter_type);
1158

    
1159
    // Invoke hook_field_formatter_settings_form(). We are reusing field
1160
    // formatter functions, but we are not working with a Field API field, so
1161
    // set $field accordingly. Unfortunately, the API is for $settings to be
1162
    // transfered via the $instance parameter, so we must mock it.
1163
    if (isset($field_formatter_info['module']) && ($function = ($field_formatter_info['module'] . '_field_formatter_settings_form')) && function_exists($function)) {
1164
      $field = NULL;
1165
      $mock_instance = array(
1166
        'display' => array(
1167
          $view_mode => array(
1168
            'type' => $field_formatter_type,
1169
            'settings' => $settings,
1170
          ),
1171
        ),
1172
        'entity_type' => 'file',
1173
        'bundle' => $file_type,
1174
      );
1175
      return $function($field, $mock_instance, $view_mode, $form, $form_state);
1176
    }
1177
  }
1178
}
1179

    
1180
/**
1181
 * Implements hook_file_formatter_FORMATTER_view().
1182
 *
1183
 * Returns a drupal_render() array to display an image of the chosen style.
1184
 *
1185
 * This formatter is only capable of displaying local images. If the passed in
1186
 * file is either not local or not an image, nothing is returned, so that
1187
 * file_view_file() can try another formatter.
1188
 */
1189
function file_entity_file_formatter_file_image_view($file, $display, $langcode) {
1190
  // Prevent PHP notices when trying to read empty files.
1191
  // @see http://drupal.org/node/681042
1192
  if (!$file->filesize) {
1193
    return;
1194
  }
1195

    
1196
  // Do not bother proceeding if this file does not have an image mime type.
1197
  if (file_entity_file_get_mimetype_type($file) != 'image') {
1198
    return;
1199
  }
1200

    
1201
  if (file_entity_file_is_readable($file)) {
1202
    // We don't sanitize here.
1203
    // @see http://drupal.org/node/1553094#comment-6257382
1204
    // Theme function will take care of escaping.
1205
    if (!isset($file->metadata)) {
1206
      $file->metadata = array();
1207
    }
1208
    $file->metadata += array('width' => NULL, 'height' => NULL);
1209
    $replace_options = array(
1210
      'clear' => TRUE,
1211
      'sanitize' => FALSE,
1212
    );
1213
    if (!empty($display['settings']['image_style'])) {
1214
      $element = array(
1215
        '#theme' => 'image_style',
1216
        '#style_name' => $display['settings']['image_style'],
1217
        '#path' => $file->uri,
1218
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1219
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1220
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1221
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1222
      );
1223
    }
1224
    else {
1225
      $element = array(
1226
        '#theme' => 'image',
1227
        '#path' => $file->uri,
1228
        '#width' => isset($file->override['attributes']['width']) ? $file->override['attributes']['width'] : $file->metadata['width'],
1229
        '#height' => isset($file->override['attributes']['height']) ? $file->override['attributes']['height'] : $file->metadata['height'],
1230
        '#alt' => token_replace($display['settings']['alt'], array('file' => $file), $replace_options),
1231
        '#title' => token_replace($display['settings']['title'], array('file' => $file), $replace_options),
1232
      );
1233
    }
1234
    return $element;
1235
  }
1236
}
1237

    
1238
/**
1239
 * Check if a file entity is readable or not.
1240
 *
1241
 * @param object $file
1242
 *   A file entity object from file_load().
1243
 *
1244
 * @return boolean
1245
 *   TRUE if the file is using a readable stream wrapper, or FALSE otherwise.
1246
 */
1247
function file_entity_file_is_readable($file) {
1248
  $scheme = file_uri_scheme($file->uri);
1249
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_READ);
1250
  return !empty($wrappers[$scheme]);
1251
}
1252

    
1253
/**
1254
 * Implements hook_file_formatter_FORMATTER_settings().
1255
 *
1256
 * Returns form elements for configuring the 'file_image' formatter.
1257
 */
1258
function file_entity_file_formatter_file_image_settings($form, &$form_state, $settings) {
1259
  $element = array();
1260
  $element['image_style'] = array(
1261
    '#title' => t('Image style'),
1262
    '#type' => 'select',
1263
    '#options' => image_style_options(FALSE),
1264
    '#default_value' => $settings['image_style'],
1265
    '#empty_option' => t('None (original image)'),
1266
  );
1267

    
1268
  // For image files we allow the alt attribute (required in HTML).
1269
  $element['alt'] = array(
1270
    '#title' => t('Alt attribute'),
1271
    '#description' => t('The text to use as value for the <em>img</em> tag <em>alt</em> attribute.'),
1272
    '#type' => 'textfield',
1273
    '#default_value' => $settings['alt'],
1274
  );
1275

    
1276
  // Allow the setting of the title attribute.
1277
  $element['title'] = array(
1278
    '#title' => t('Title attribute'),
1279
    '#description' => t('The text to use as value for the <em>img</em> tag <em>title</em> attribute.'),
1280
    '#type' => 'textfield',
1281
    '#default_value' => $settings['title'],
1282
  );
1283

    
1284
  if (module_exists('token')) {
1285
    $element['alt']['#description'] .= t('This field supports tokens.');
1286
    $element['title']['#description'] .= t('This field supports tokens.');
1287
    $element['tokens'] = array(
1288
      '#theme' => 'token_tree',
1289
      '#token_types' => array('file'),
1290
      '#dialog' => TRUE,
1291
    );
1292
  }
1293

    
1294
  return $element;
1295
}
1296

    
1297
/**
1298
 * Menu access callback for the 'view mode file display settings' pages.
1299
 *
1300
 * Based on _field_ui_view_mode_menu_access(), but the Field UI module might not
1301
 * be enabled.
1302
 */
1303
function _file_entity_view_mode_menu_access($file_type, $view_mode, $access_callback) {
1304
  // Deny access if the view mode isn't configured to use custom display
1305
  // settings.
1306
  $view_mode_settings = field_view_mode_settings('file', $file_type->type);
1307
  $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']);
1308
  if (!$visibility) {
1309
    return FALSE;
1310
  }
1311

    
1312
  // Otherwise, continue to an $access_callback check.
1313
  $args = array_slice(func_get_args(), 3);
1314
  $callback = empty($access_callback) ? 0 : trim($access_callback);
1315
  if (is_numeric($callback)) {
1316
    return (bool) $callback;
1317
  }
1318
  elseif (function_exists($access_callback)) {
1319
    return call_user_func_array($access_callback, $args);
1320
  }
1321
}
1322

    
1323
/**
1324
 * Implements hook_modules_enabled().
1325
 */
1326
function file_entity_modules_enabled($modules) {
1327
  file_info_cache_clear();
1328
}
1329

    
1330
/**
1331
 * Implements hook_modules_disabled().
1332
 */
1333
function file_entity_modules_disabled($modules) {
1334
  file_info_cache_clear();
1335
}
1336

    
1337
/**
1338
 * Implements hook_views_api().
1339
 */
1340
function file_entity_views_api() {
1341
  return array(
1342
    'api' => 3,
1343
  );
1344
}
1345

    
1346
/**
1347
 * Returns whether the current page is the full page view of the passed-in file.
1348
 *
1349
 * @param $file
1350
 *   A file object.
1351
 */
1352
function file_entity_is_page($file) {
1353
  $page_file = menu_get_object('file', 1);
1354
  return (!empty($page_file) ? $page_file->fid == $file->fid : FALSE);
1355
}
1356

    
1357
/**
1358
 * Process variables for file_entity.tpl.php
1359
 *
1360
 * The $variables array contains the following arguments:
1361
 * - $file
1362
 * - $view_mode
1363
 *
1364
 * @see file_entity.tpl.php
1365
 */
1366
function template_preprocess_file_entity(&$variables) {
1367
  $view_mode = $variables['view_mode'] = $variables['elements']['#view_mode'];
1368
  $variables['file'] = $variables['elements']['#file'];
1369
  $file = $variables['file'];
1370

    
1371
  $variables['id'] = drupal_html_id('file-'. $file->fid);
1372
  $variables['date']      = format_date($file->timestamp);
1373
  $account = user_load($file->uid);
1374
  $variables['name']      = theme('username', array('account' => $account));
1375

    
1376
  $uri = entity_uri('file', $file);
1377
  $variables['file_url']  = url($uri['path'], $uri['options']);
1378
  $label = entity_label('file', $file);
1379
  $variables['label']     = check_plain($label);
1380
  $variables['page']      = $view_mode == 'full' && file_entity_is_page($file);
1381

    
1382
  // Hide the file name from being displayed until we can figure out a better
1383
  // way to control this. We cannot simply not output the title since
1384
  // contextual links require $title_suffix to be output in the template.
1385
  // @see http://drupal.org/node/1245266
1386
  if (!$variables['page']) {
1387
    $variables['title_attributes_array']['class'][] = 'element-invisible';
1388
  }
1389

    
1390
  // Flatten the file object's member fields.
1391
  $variables = array_merge((array) $file, $variables);
1392

    
1393
  // Helpful $content variable for templates.
1394
  $variables += array('content' => array());
1395
  foreach (element_children($variables['elements']) as $key) {
1396
    $variables['content'][$key] = $variables['elements'][$key];
1397
  }
1398

    
1399
  // Make the field variables available with the appropriate language.
1400
  field_attach_preprocess('file', $file, $variables['content'], $variables);
1401

    
1402
  // Attach the file object to the content element.
1403
  $variables['content']['file']['#file'] = $file;
1404

    
1405
  // Display post information only on certain file types.
1406
  if (variable_get('file_submitted_' . $file->type, FALSE)) {
1407
    $variables['display_submitted'] = TRUE;
1408
    $variables['submitted'] = t('Uploaded by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
1409
    $variables['user_picture'] = theme_get_setting('toggle_file_user_picture') ? theme('user_picture', array('account' => $account)) : '';
1410
  }
1411
  else {
1412
    $variables['display_submitted'] = FALSE;
1413
    $variables['submitted'] = '';
1414
    $variables['user_picture'] = '';
1415
  }
1416

    
1417
  // Gather file classes.
1418
  $variables['classes_array'][] = drupal_html_class('file-' . $file->type);
1419
  $variables['classes_array'][] = drupal_html_class('file-' . $file->filemime);
1420
  if ($file->status != FILE_STATUS_PERMANENT) {
1421
    $variables['classes_array'][] = 'file-temporary';
1422
  }
1423

    
1424
  // Change the 'file-entity' class into 'file'
1425
  if ($variables['classes_array'][0] == 'file-entity') {
1426
    $variables['classes_array'][0] = 'file';
1427
  }
1428

    
1429
  // Clean up name so there are no underscores.
1430
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type;
1431
  $variables['theme_hook_suggestions'][] = 'file__' . $file->type . '__' . $view_mode;
1432
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime);
1433
  $variables['theme_hook_suggestions'][] = 'file__' . str_replace(array('/', '-'), array('__', '_'), $file->filemime) . '__' . $view_mode;
1434
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid;
1435
  $variables['theme_hook_suggestions'][] = 'file__' . $file->fid . '__' . $view_mode;
1436
}
1437

    
1438
/**
1439
 * Returns the file type name of the passed file or file type string.
1440
 *
1441
 * @param $file
1442
 *   A file object or string that indicates the file type to return.
1443
 *
1444
 * @return
1445
 *   The file type name or FALSE if the file type is not found.
1446
 */
1447
function file_entity_type_get_name($file) {
1448
  $type = is_object($file) ? $file->type : $file;
1449
  $info = entity_get_info('file');
1450
  return isset($info['bundles'][$type]['label']) ? $info['bundles'][$type]['label'] : FALSE;
1451
}
1452

    
1453
/**
1454
 * Returns a list of available file type names.
1455
 *
1456
 * @return
1457
 *   An array of file type names, keyed by the type.
1458
 */
1459
function file_entity_type_get_names() {
1460
  $names = &drupal_static(__FUNCTION__);
1461

    
1462
  if (!isset($names)) {
1463
    $info = entity_get_info('file');
1464
    foreach ($info['bundles'] as $bundle => $bundle_info) {
1465
      $names[$bundle] = $bundle_info['label'];
1466
    }
1467
  }
1468

    
1469
  return $names;
1470
}
1471

    
1472
/**
1473
 * Return an array of available view modes for file entities.
1474
 */
1475
function file_entity_view_mode_labels() {
1476
  $labels = &drupal_static(__FUNCTION__);
1477

    
1478
  if (!isset($options)) {
1479
    $entity_info = entity_get_info('file');
1480
    $labels = array('default' => t('Default'));
1481
    foreach ($entity_info['view modes'] as $machine_name => $mode) {
1482
      $labels[$machine_name] = $mode['label'];
1483
    }
1484
  }
1485

    
1486
  return $labels;
1487
}
1488

    
1489
/**
1490
 * Return the label for a specific file entity view mode.
1491
 */
1492
function file_entity_view_mode_label($view_mode, $default = FALSE) {
1493
  $labels = file_entity_view_mode_labels();
1494
  return isset($labels[$view_mode]) ? $labels[$view_mode] : $default;
1495
}
1496

    
1497
/**
1498
 * Helper function to get a list of hidden stream wrappers.
1499
 *
1500
 * This is used in several places to filter queries for media so that files in
1501
 * temporary:// don't show up.
1502
 */
1503
function file_entity_get_hidden_stream_wrappers() {
1504
  return array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_VISIBLE));
1505
}
1506

    
1507
/**
1508
 * Return a specific stream wrapper's registry information.
1509
 *
1510
 * @param $scheme
1511
 *   A URI scheme, a stream is referenced as "scheme://target".
1512
 *
1513
 * @see file_get_stream_wrappers()
1514
 */
1515
function file_entity_get_stream_wrapper($scheme) {
1516
  $wrappers = file_get_stream_wrappers();
1517
  return isset($wrappers[$scheme]) ? $wrappers[$scheme] : FALSE;
1518
}
1519

    
1520
/**
1521
 * Implements hook_stream_wrappers_alter().
1522
 */
1523
function file_entity_stream_wrappers_alter(&$wrappers) {
1524
  if (isset($wrappers['private'])) {
1525
    $wrappers['private']['private'] = TRUE;
1526
  }
1527
  if (isset($wrappers['temporary'])) {
1528
    $wrappers['temporary']['private'] = TRUE;
1529
  }
1530
}
1531

    
1532
/**
1533
 * Implements hook_ctools_plugin_api().
1534
 */
1535
function file_entity_ctools_plugin_api($owner, $api) {
1536
  if ($owner == 'file_entity' && $api == 'file_type') {
1537
    return array('version' => 1);
1538
  }
1539
  if ($owner == 'file_entity' && $api == 'file_default_displays') {
1540
    return array('version' => 1);
1541
  }
1542
}
1543

    
1544
/**
1545
 * @defgroup file_entity_access File access rights
1546
 * @{
1547
 * The file access system determines who can do what to which files.
1548
 *
1549
 * In determining access rights for a file, file_entity_access() first checks
1550
 * whether the user has the "bypass file access" permission. Such users have
1551
 * unrestricted access to all files. user 1 will always pass this check.
1552
 *
1553
 * Next, all implementations of hook_file_entity_access() will be called. Each
1554
 * implementation may explicitly allow, explicitly deny, or ignore the access
1555
 * request. If at least one module says to deny the request, it will be rejected.
1556
 * If no modules deny the request and at least one says to allow it, the request
1557
 * will be permitted.
1558
 *
1559
 * There is no access grant system for files.
1560
 *
1561
 * In file listings, the process above is followed except that
1562
 * hook_file_entity_access() is not called on each file for performance reasons
1563
 * and for proper functioning of the pager system. When adding a filelisting to
1564
 * your module, be sure to use a dynamic query created by db_select()
1565
 * and add a tag of "file_entity_access". This will allow modules dealing
1566
 * with file access to ensure only files to which the user has access
1567
 * are retrieved, through the use of hook_query_TAG_alter().
1568
 *
1569
 * Note: Even a single module returning FILE_ENTITY_ACCESS_DENY from
1570
 * hook_file_entity_access() will block access to the file. Therefore,
1571
 * implementers should take care to not deny access unless they really intend to.
1572
 * Unless a module wishes to actively deny access it should return
1573
 * FILE_ENTITY_ACCESS_IGNORE (or simply return nothing)
1574
 * to allow other modules to control access.
1575
 *
1576
 * Stream wrappers that are considered private should implement a 'private'
1577
 * flag equal to TRUE in hook_stream_wrappers().
1578
 */
1579

    
1580
/**
1581
 * Determine if a user may perform the given operation on the specified file.
1582
 *
1583
 * @param $op
1584
 *   The operation to be performed on the file. Possible values are:
1585
 *   - "view"
1586
 *   - "download"
1587
 *   - "update"
1588
 *   - "delete"
1589
 *   - "create"
1590
 * @param $file
1591
 *   The file object on which the operation is to be performed, or file type
1592
 *   (e.g. 'image') for "create" operation.
1593
 * @param $account
1594
 *   Optional, a user object representing the user for whom the operation is to
1595
 *   be performed. Determines access for a user other than the current user.
1596
 *
1597
 * @return
1598
 *   TRUE if the operation may be performed, FALSE otherwise.
1599
 */
1600
function file_entity_access($op, $file = NULL, $account = NULL) {
1601
  $rights = &drupal_static(__FUNCTION__, array());
1602

    
1603
  if (!$file && !in_array($op, array('view', 'download', 'update', 'delete', 'create'), TRUE)) {
1604
    // If there was no file to check against, and the $op was not one of the
1605
    // supported ones, we return access denied.
1606
    return FALSE;
1607
  }
1608

    
1609
  // If no user object is supplied, the access check is for the current user.
1610
  if (empty($account)) {
1611
    $account = $GLOBALS['user'];
1612
  }
1613

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

    
1618
  // If we've already checked access for this file, user and op, return from
1619
  // cache.
1620
  if (isset($rights[$account->uid][$cache_id][$op])) {
1621
    return $rights[$account->uid][$cache_id][$op];
1622
  }
1623

    
1624
  if (user_access('bypass file access', $account)) {
1625
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1626
  }
1627

    
1628
  // We grant access to the file if both of the following conditions are met:
1629
  // - No modules say to deny access.
1630
  // - At least one module says to grant access.
1631
  $access = module_invoke_all('file_entity_access', $op, $file, $account);
1632
  if (in_array(FILE_ENTITY_ACCESS_DENY, $access, TRUE)) {
1633
    return $rights[$account->uid][$cache_id][$op] = FALSE;
1634
  }
1635
  elseif (in_array(FILE_ENTITY_ACCESS_ALLOW, $access, TRUE)) {
1636
    return $rights[$account->uid][$cache_id][$op] = TRUE;
1637
  }
1638

    
1639

    
1640
  // Fall back to default behaviors on view.
1641
  if ($op == 'view' && is_object($file)) {
1642
    $scheme = file_uri_scheme($file->uri);
1643
    $wrapper = file_entity_get_stream_wrapper($scheme);
1644

    
1645
    if (!empty($wrapper['private'])) {
1646
      // For private files, users can view private files if the
1647
      // user has the 'view private files' permission.
1648
      if (user_access('view private files', $account)) {
1649
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1650
      }
1651

    
1652
      // For private files, users can view their own private files if the
1653
      // user is not anonymous, and has the 'view own private files' permission.
1654
      if (!empty($account->uid) && $file->uid == $account->uid && user_access('view own private files', $account)) {
1655
        return $rights[$account->uid][$cache_id][$op] = TRUE;
1656
      }
1657
    }
1658
    elseif ($file->status == FILE_STATUS_PERMANENT && $file->uid == $account->uid && user_access('view own files', $account)) {
1659
      // For non-private files, allow to see if user owns the file.
1660
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1661
    }
1662
    elseif ($file->status == FILE_STATUS_PERMANENT && user_access('view files', $account)) {
1663
      // For non-private files, users can view if they have the 'view files'
1664
      // permission.
1665
      return $rights[$account->uid][$cache_id][$op] = TRUE;
1666
    }
1667
  }
1668

    
1669
  return FALSE;
1670
}
1671

    
1672
/**
1673
 * Implements hook_file_entity_access().
1674
 */
1675
function file_entity_file_entity_access($op, $file, $account) {
1676
  // If the file URI is invalid, deny access.
1677
  if (is_object($file) && !file_valid_uri($file->uri)) {
1678
    return FILE_ENTITY_ACCESS_DENY;
1679
  }
1680

    
1681
  if ($op == 'create') {
1682
    if (user_access('create files')) {
1683
      return FILE_ENTITY_ACCESS_ALLOW;
1684
    }
1685
  }
1686

    
1687
  if (!empty($file)) {
1688
    $type = is_string($file) ? $file : $file->type;
1689

    
1690
    if (in_array($type, file_entity_permissions_get_configured_types())) {
1691
      if ($op == 'download') {
1692
        if (user_access('download any ' . $type . ' files', $account) || is_object($file) && user_access('download own ' . $type . ' files', $account) && ($account->uid == $file->uid)) {
1693
          return FILE_ENTITY_ACCESS_ALLOW;
1694
        }
1695
      }
1696

    
1697
      if ($op == 'update') {
1698
        if (user_access('edit any ' . $type . ' files', $account) || (is_object($file) && user_access('edit own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1699
          return FILE_ENTITY_ACCESS_ALLOW;
1700
        }
1701
      }
1702

    
1703
      if ($op == 'delete') {
1704
        if (user_access('delete any ' . $type . ' files', $account) || (is_object($file) && user_access('delete own ' . $type . ' files', $account) && ($account->uid == $file->uid))) {
1705
          return FILE_ENTITY_ACCESS_ALLOW;
1706
        }
1707
      }
1708
    }
1709
  }
1710

    
1711
  return FILE_ENTITY_ACCESS_IGNORE;
1712
}
1713

    
1714
/**
1715
 * Implements hook_query_TAG_alter().
1716
 *
1717
 * This is the hook_query_alter() for queries tagged with 'file_access'. It adds
1718
 * file access checks for the user account given by the 'account' meta-data (or
1719
 * global $user if not provided).
1720
 */
1721
function file_entity_query_file_access_alter(QueryAlterableInterface $query) {
1722
  _file_entity_query_file_entity_access_alter($query, 'file');
1723
}
1724

    
1725
/**
1726
 * Implements hook_query_TAG_alter().
1727
 *
1728
 * This function implements the same functionality as
1729
 * file_entity_query_file_access_alter() for the SQL field storage engine. File
1730
 * access conditions are added for field values belonging to files only.
1731
 */
1732
function file_entity_query_entity_field_access_alter(QueryAlterableInterface $query) {
1733
  //_file_entity_query_file_entity_access_alter($query, 'entity');
1734
}
1735

    
1736
/**
1737
 * Helper for file entity access functions.
1738
 *
1739
 * @param $query
1740
 *   The query to add conditions to.
1741
 * @param $type
1742
 *   Either 'file' or 'entity' depending on what sort of query it is. See
1743
 *   file_entity_query_file_entity_access_alter() and
1744
 *   file_entity_query_entity_field_access_alter() for more.
1745
 */
1746
function _file_entity_query_file_entity_access_alter($query, $type) {
1747
  global $user;
1748

    
1749
  // Read meta-data from query, if provided.
1750
  if (!$account = $query->getMetaData('account')) {
1751
    $account = $user;
1752
  }
1753

    
1754
  // If $account can bypass file access, we don't need to alter the query.
1755
  if (user_access('bypass file access', $account)) {
1756
    return;
1757
  }
1758

    
1759
  $tables = $query->getTables();
1760
  $base_table = $query->getMetaData('base_table');
1761
  // If no base table is specified explicitly, search for one.
1762
  if (!$base_table) {
1763
    $fallback = '';
1764
    foreach ($tables as $alias => $table_info) {
1765
      if (!($table_info instanceof SelectQueryInterface)) {
1766
        $table = $table_info['table'];
1767
        // If the file_managed table is in the query, it wins immediately.
1768
        if ($table == 'file_managed') {
1769
          $base_table = $table;
1770
          break;
1771
        }
1772
        // Check whether the table has a foreign key to file_managed.fid. If it
1773
        // does, do not run this check again as we found a base table and only
1774
        // file_managed can triumph that.
1775
        if (!$base_table) {
1776
          // The schema is cached.
1777
          $schema = drupal_get_schema($table);
1778
          if (isset($schema['fields']['fid'])) {
1779
            if (isset($schema['foreign keys'])) {
1780
              foreach ($schema['foreign keys'] as $relation) {
1781
                if ($relation['table'] === 'file_managed' && $relation['columns'] === array('fid' => 'fid')) {
1782
                  $base_table = $table;
1783
                }
1784
              }
1785
            }
1786
            else {
1787
              // At least it's a fid. A table with a field called fid is very
1788
              // very likely to be a file_managed.fid in a file access query.
1789
              $fallback = $table;
1790
            }
1791
          }
1792
        }
1793
      }
1794
    }
1795
    // If there is nothing else, use the fallback.
1796
    if (!$base_table) {
1797
      if ($fallback) {
1798
        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);
1799
        $base_table = $fallback;
1800
      }
1801
      else {
1802
        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.'));
1803
      }
1804
    }
1805
  }
1806

    
1807
  if ($type == 'entity') {
1808
    // The original query looked something like:
1809
    // @code
1810
    //  SELECT fid FROM sometable s
1811
    //  WHERE ($file_access_conditions)
1812
    // @endcode
1813
    //
1814
    // Our query will look like:
1815
    // @code
1816
    //  SELECT entity_type, entity_id
1817
    //  FROM field_data_something s
1818
    //  WHERE (entity_type = 'file' AND $file_access_conditions) OR (entity_type <> 'file')
1819
    // @endcode
1820
    //
1821
    // So instead of directly adding to the query object, we need to collect
1822
    // all of the file access conditions in a separate db_and() object and
1823
    // then add it to the query at the end.
1824
    $file_conditions = db_and();
1825
  }
1826
  foreach ($tables as $falias => $tableinfo) {
1827
    $table = $tableinfo['table'];
1828
    if (!($table instanceof SelectQueryInterface) && $table == $base_table) {
1829
      $subquery = db_select('file_managed', 'fm_access')->fields('fm_access', array('fid'));
1830
      $subquery_conditions = db_or();
1831

    
1832
      $wrappers = file_entity_get_public_and_private_stream_wrapper_names();
1833
      if (!empty($wrappers['public'])) {
1834
        if (user_access('view files', $account)) {
1835
          foreach (array_keys($wrappers['public']) as $wrapper) {
1836
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1837
          }
1838
        }
1839
        elseif (user_access('view own files', $account)) {
1840
          foreach (array_keys($wrappers['public']) as $wrapper) {
1841
            $subquery_conditions->condition(db_and()
1842
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1843
              ->condition('fm_access.uid', $account->uid)
1844
            );
1845
          }
1846
        }
1847
      }
1848
      if (!empty($wrappers['private'])) {
1849
        if (user_access('view private files', $account)) {
1850
          foreach (array_keys($wrappers['private']) as $wrapper) {
1851
            $subquery_conditions->condition('fm_access.uri', $wrapper . '%', 'LIKE');
1852
          }
1853
        }
1854
        elseif (user_access('view own private files', $account)) {
1855
          foreach (array_keys($wrappers['private']) as $wrapper) {
1856
            $subquery_conditions->condition(db_and()
1857
              ->condition('fm_access.uri', $wrapper . '%', 'LIKE')
1858
              ->condition('fm_access.uid', $account->uid)
1859
            );
1860
          }
1861
        }
1862
      }
1863

    
1864
      if ($subquery_conditions->count()) {
1865
        $subquery->condition($subquery_conditions);
1866

    
1867
        $field = 'fid';
1868
        // Now handle entities.
1869
        if ($type == 'entity') {
1870
          // Set a common alias for entities.
1871
          $base_alias = $falias;
1872
          $field = 'entity_id';
1873
        }
1874
        $subquery->where("$falias.$field = fm_access.fid");
1875

    
1876
        // For an entity query, attach the subquery to entity conditions.
1877
        if ($type == 'entity') {
1878
          $file_conditions->exists($subquery);
1879
        }
1880
        // Otherwise attach it to the node query itself.
1881
        else {
1882
          $query->exists($subquery);
1883
        }
1884
      }
1885
    }
1886
  }
1887

    
1888
  if ($type == 'entity' && $file_conditions->count()) {
1889
    // All the file access conditions are only for field values belonging to
1890
    // files.
1891
    $file_conditions->condition("$base_alias.entity_type", 'file');
1892
    $or = db_or();
1893
    $or->condition($file_conditions);
1894
    // If the field value belongs to a non-file entity type then this function
1895
    // does not do anything with it.
1896
    $or->condition("$base_alias.entity_type", 'file', '<>');
1897
    // Add the compiled set of rules to the query.
1898
    $query->condition($or);
1899
  }
1900
}
1901

    
1902
/**
1903
 * Implements hook_file_download().
1904
 */
1905
function file_entity_file_download($uri) {
1906
  // Load the file from the URI.
1907
  $file = file_uri_to_object($uri);
1908

    
1909
  // An existing file wasn't found, so we don't control access.
1910
  // E.g. image derivatives will fall here.
1911
  if (empty($file->fid)) {
1912
    return NULL;
1913
  }
1914

    
1915
  // Allow the user to download the file if they have appropriate permissions.
1916
  if (file_entity_access('view', $file)) {
1917
    return file_get_content_headers($file);
1918
  }
1919

    
1920
  return NULL;
1921
}
1922

    
1923
/**
1924
 * Helper function to generate standard file permission list for a given type.
1925
 *
1926
 * @param $type
1927
 *   The machine-readable name of the file type.
1928
 * @return array
1929
 *   An array of permission names and descriptions.
1930
 */
1931
function file_entity_list_permissions($type) {
1932
  $info = file_type_load($type);
1933

    
1934
  // Build standard list of file permissions for this type.
1935
  $permissions = array(
1936
    "edit own $type files" => array(
1937
      'title' => t('%type_name: Edit own files', array('%type_name' => $info->label)),
1938
    ),
1939
    "edit any $type files" => array(
1940
      'title' => t('%type_name: Edit any files', array('%type_name' => $info->label)),
1941
    ),
1942
    "delete own $type files" => array(
1943
      'title' => t('%type_name: Delete own files', array('%type_name' => $info->label)),
1944
    ),
1945
    "delete any $type files" => array(
1946
      'title' => t('%type_name: Delete any files', array('%type_name' => $info->label)),
1947
    ),
1948
    "download own $type files" => array(
1949
      'title' => t('%type_name: Download own files', array('%type_name' => $info->label)),
1950
    ),
1951
    "download any $type files" => array(
1952
      'title' => t('%type_name: Download any files', array('%type_name' => $info->label)),
1953
    ),
1954
  );
1955

    
1956
  return $permissions;
1957
}
1958

    
1959
/**
1960
 * Returns an array of file types that should be managed by permissions.
1961
 *
1962
 * By default, this will include all file types in the system. To exclude a
1963
 * specific file from getting permissions defined for it, set the
1964
 * file_entity_permissions_$type variable to 0. File entity does not provide an
1965
 * interface for doing so, however, contrib modules may exclude their own files
1966
 * in hook_install(). Alternatively, contrib modules may configure all file
1967
 * types at once, or decide to apply some other hook_file_entity_access()
1968
 * implementation to some or all file types.
1969
 *
1970
 * @return
1971
 *   An array of file types managed by this module.
1972
 */
1973
function file_entity_permissions_get_configured_types() {
1974

    
1975
  $configured_types = array();
1976

    
1977
  foreach (file_type_get_enabled_types() as $type => $info) {
1978
    if (variable_get('file_entity_permissions_' . $type, 1)) {
1979
      $configured_types[] = $type;
1980
    }
1981
  }
1982

    
1983
  return $configured_types;
1984
}
1985

    
1986
/**
1987
 * @} End of "defgroup file_entity_access".
1988
 *
1989
 * Implements hook_file_default_types().
1990
 */
1991
function file_entity_file_default_types() {
1992
  $types = array();
1993

    
1994
  // Image.
1995
  $types['image'] = (object) array(
1996
    'api_version' => 1,
1997
    'type' => 'image',
1998
    'label' => t('Image'),
1999
    'description' => t('An <em>Image</em> file is a still visual.'),
2000
    'mimetypes' => array(
2001
      'image/*',
2002
    ),
2003
  );
2004

    
2005
  // Video.
2006
  $types['video'] = (object) array(
2007
    'api_version' => 1,
2008
    'type' => 'video',
2009
    'label' => t('Video'),
2010
    'description' => t('A <em>Video</em> file is a moving visual recording.'),
2011
    'mimetypes' => array(
2012
      'video/*',
2013
    ),
2014
  );
2015

    
2016
  // Audio.
2017
  $types['audio'] = (object) array(
2018
    'api_version' => 1,
2019
    'type' => 'audio',
2020
    'label' => t('Audio'),
2021
    'description' => t('An <em>Audio</em> file is a sound recording.'),
2022
    'mimetypes' => array(
2023
      'audio/*',
2024
    ),
2025
  );
2026

    
2027
  // Document.
2028
  $types['document'] = (object) array(
2029
    'api_version' => 1,
2030
    'type' => 'document',
2031
    'label' => t('Document'),
2032
    'description' => t('A <em>Document</em> file is written information.'),
2033
    'mimetypes' => array(
2034
      'text/plain',
2035
      'application/msword',
2036
      'application/vnd.ms-excel',
2037
      'application/pdf',
2038
      'application/vnd.ms-powerpoint',
2039
      'application/vnd.oasis.opendocument.text',
2040
      'application/vnd.oasis.opendocument.spreadsheet',
2041
      'application/vnd.oasis.opendocument.presentation',
2042
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
2043
      'application/vnd.openxmlformats-officedocument.presentationml.presentation',
2044
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
2045
    ),
2046
  );
2047

    
2048
  return $types;
2049
}
2050

    
2051
/**
2052
 * Implements hook_file_operations().
2053
 */
2054
function file_entity_file_operations() {
2055
  $operations = array(
2056
    'permanent' => array(
2057
      'label' => t('Indicate that the selected files are permanent and should not be deleted'),
2058
      'callback' => 'file_entity_mass_update',
2059
      'callback arguments' => array('updates' => array('status' => FILE_STATUS_PERMANENT)),
2060
    ),
2061
    'temporary' => array(
2062
      'label' => t('Indicate that the selected files are temporary and should be removed during cron runs'),
2063
      'callback' => 'file_entity_mass_update',
2064
      'callback arguments' => array('updates' => array('status' => 0)),
2065
    ),
2066
    'delete' => array(
2067
      'label' => t('Delete selected files'),
2068
      'callback' => NULL,
2069
    ),
2070
  );
2071
  return $operations;
2072
}
2073

    
2074
/**
2075
 * Clear the field cache for any entities referencing a specific file.
2076
 *
2077
 * @param object $file
2078
 *   A file object.
2079
 */
2080
function file_entity_invalidate_field_caches($file) {
2081
  $entity_types = &drupal_static(__FUNCTION__);
2082

    
2083
  // Gather the list of entity types which support field caching.
2084
  if (!isset($entity_types)) {
2085
    $entity_types = array();
2086
    foreach (entity_get_info() as $entity_type => $entity_info) {
2087
      if (!empty($entity_info['fieldable']) && !empty($entity_info['field cache'])) {
2088
        $entity_types[] = $entity_type;
2089
      }
2090
    }
2091
  }
2092

    
2093
  // If no entity types support field caching, then there is no work to be done.
2094
  if (empty($entity_types)) {
2095
    return;
2096
  }
2097

    
2098
  $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();
2099
  if (!empty($records)) {
2100
    $cids = array();
2101
    foreach ($records as $record) {
2102
      $cids[] = 'field:' . $record->type . ':' . $record->id;
2103
    }
2104
    cache_clear_all($cids, 'cache_field');
2105
  }
2106
}
2107

    
2108
/**
2109
 * Check if a file entity is considered local or not.
2110
 *
2111
 * @param object $file
2112
 *   A file entity object from file_load().
2113
 *
2114
 * @return
2115
 *   TRUE if the file is using a local stream wrapper, or FALSE otherwise.
2116
 */
2117
function file_entity_file_is_local($file) {
2118
  $scheme = file_uri_scheme($file->uri);
2119
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
2120
  return !empty($wrappers[$scheme]) && empty($wrappers[$scheme]['remote']);
2121
}
2122

    
2123
/**
2124
 * Check if a file entity is considered writeable or not.
2125
 *
2126
 * @param object $file
2127
 *   A file entity object from file_load().
2128
 *
2129
 * @return
2130
 *   TRUE if the file is using a visible, readable and writeable stream wrapper,
2131
 *   or FALSE otherwise.
2132
 */
2133
function file_entity_file_is_writeable($file) {
2134
  $scheme = file_uri_scheme($file->uri);
2135
  $wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
2136
  return !empty($wrappers[$scheme]);
2137
}
2138

    
2139
/**
2140
 * Pre-render callback for adding validation descriptions to file upload fields.
2141
 */
2142
function file_entity_upload_validators_pre_render($element) {
2143
  if (!empty($element['#upload_validators'])) {
2144
    if (!isset($element['#description'])) {
2145
      $element['#description'] = '';
2146
    }
2147
    if ($element['#description'] !== FALSE) {
2148
      $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
2149
    }
2150
  }
2151
  return $element;
2152
}
2153

    
2154
/**
2155
 * Implements hook_file_default_displays_alter() on behalf of image.module.
2156
 */
2157
function image_file_default_displays_alter(&$file_displays) {
2158
  // Images should be displayed as unstyled images by default.
2159
  if (isset($file_displays['image__default__file_field_file_default'])) {
2160
    $file_displays['image__default__file_field_file_default']->status = FALSE;
2161
  }
2162

    
2163
  $file_display = new stdClass();
2164
  $file_display->api_version = 1;
2165
  $file_display->name = 'image__default__file_field_image';
2166
  $file_display->weight = 50;
2167
  $file_display->status = TRUE;
2168
  $file_display->settings = array(
2169
    'image_style' => '',
2170
    'image_link' => '',
2171
  );
2172
  $file_displays['image__default__file_field_image'] = $file_display;
2173

    
2174
  // Image previews should be displayed as image thumbnails by default.
2175
  if (isset($file_displays['image__preview__file_field_file_default'])) {
2176
    $file_displays['image__preview__file_field_file_default']->status = FALSE;
2177
  }
2178

    
2179
  $file_display = new stdClass();
2180
  $file_display->api_version = 1;
2181
  $file_display->name = 'image__preview__file_field_image';
2182
  $file_display->weight = 50;
2183
  $file_display->status = TRUE;
2184
  $file_display->settings = array(
2185
    'image_style' => 'thumbnail',
2186
    'image_link' => '',
2187
  );
2188
  $file_displays['image__preview__file_field_image'] = $file_display;
2189

    
2190
  // Image teasers should be displayed as medium images by default.
2191
  if (isset($file_displays['image__teaser__file_field_file_default'])) {
2192
    $file_displays['image__teaser__file_field_file_default']->status = FALSE;
2193
  }
2194

    
2195
  $file_display = new stdClass();
2196
  $file_display->api_version = 1;
2197
  $file_display->name = 'image__teaser__file_field_image';
2198
  $file_display->weight = 50;
2199
  $file_display->status = TRUE;
2200
  $file_display->settings = array(
2201
    'image_style' => 'medium',
2202
    'image_link' => 'content',
2203
  );
2204
  $file_displays['image__teaser__file_field_image'] = $file_display;
2205
}
2206

    
2207
/**
2208
 * @name pathauto_file Pathauto integration for the core file module.
2209
 * @{
2210
 */
2211

    
2212
/**
2213
 * Implements hook_file_insert() on behalf of pathauto.module.
2214
 */
2215
function pathauto_file_insert($file) {
2216
  pathauto_file_update_alias($file, 'insert');
2217
}
2218

    
2219
/**
2220
 * Implements hook_file_update() on behalf of pathauto.module.
2221
 */
2222
function pathauto_file_update($file) {
2223
  pathauto_file_update_alias($file, 'update');
2224
}
2225

    
2226
/**
2227
 * Implements hook_file_delete() on behalf of pathauto.module.
2228
 */
2229
function pathauto_file_delete($file) {
2230
  pathauto_entity_path_delete_all('file', $file, "file/{$file->fid}");
2231
}
2232

    
2233
/**
2234
 * Implements hook_form_FORM_ID_alter() on behalf of pathauto.module.
2235
 *
2236
 * Add the Pathauto settings to the file form.
2237
 */
2238
function pathauto_form_file_entity_edit_alter(&$form, &$form_state, $form_id) {
2239
  $file = $form_state['file'];
2240
  $langcode = pathauto_entity_language('file', $file);
2241
  pathauto_field_attach_form('file', $file, $form, $form_state, $langcode);
2242
}
2243

    
2244
/**
2245
 * Implements hook_file_operations() on behalf of pathauto.module.
2246
 */
2247
function pathauto_file_operations() {
2248
  $operations['pathauto_update_alias'] = array(
2249
    'label' => t('Update URL alias'),
2250
    'callback' => 'pathauto_file_update_alias_multiple',
2251
    'callback arguments' => array('bulkupdate', array('message' => TRUE)),
2252
  );
2253
  return $operations;
2254
}
2255

    
2256
/**
2257
 * Update the URL aliases for an individual file.
2258
 *
2259
 * @param $file
2260
 *   A file object.
2261
 * @param $op
2262
 *   Operation being performed on the file ('insert', 'update' or 'bulkupdate').
2263
 * @param $options
2264
 *   An optional array of additional options.
2265
 */
2266
function pathauto_file_update_alias(stdClass $file, $op, array $options = array()) {
2267
  // Skip processing if the user has disabled pathauto for the file.
2268
  if (isset($file->path['pathauto']) && empty($file->path['pathauto'])) {
2269
    return;
2270
  }
2271

    
2272
  $options += array('language' => pathauto_entity_language('file', $file));
2273

    
2274
  // Skip processing if the file has no pattern.
2275
  if (!pathauto_pattern_load_by_entity('file', $file->type, $options['language'])) {
2276
    return;
2277
  }
2278

    
2279
  module_load_include('inc', 'pathauto');
2280
  $uri = entity_uri('file', $file);
2281
  pathauto_create_alias('file', $op, $uri['path'], array('file' => $file), $file->type, $options['language']);
2282
}
2283

    
2284
/**
2285
 * Update the URL aliases for multiple files.
2286
 *
2287
 * @param $fids
2288
 *   An array of file IDs.
2289
 * @param $op
2290
 *   Operation being performed on the files ('insert', 'update' or
2291
 *   'bulkupdate').
2292
 * @param $options
2293
 *   An optional array of additional options.
2294
 */
2295
function pathauto_file_update_alias_multiple(array $fids, $op, array $options = array()) {
2296
  $options += array('message' => FALSE);
2297

    
2298
  $files = file_load_multiple($fids);
2299
  foreach ($files as $file) {
2300
    pathauto_file_update_alias($file, $op, $options);
2301
  }
2302

    
2303
  if (!empty($options['message'])) {
2304
    drupal_set_message(format_plural(count($fids), 'Updated URL alias for 1 file.', 'Updated URL aliases for @count files.'));
2305
  }
2306
}
2307

    
2308
/**
2309
 * Update action wrapper for pathauto_file_update_alias().
2310
 */
2311
function pathauto_file_update_action($file, $context = array()) {
2312
  pathauto_file_update_alias($file, 'bulkupdate', array('message' => TRUE));
2313
}
2314

    
2315
/**
2316
 * @} End of "name pathauto_file".
2317
 */
2318

    
2319
/**
2320
 * Implements hook_form_FORM_ID_alter() for file_entity_edit() on behalf of path.module.
2321
 */
2322
function path_form_file_entity_edit_alter(&$form, $form_state) {
2323
  // Make sure this does not show up on the delete confirmation form.
2324
  if (empty($form_state['confirm_delete'])) {
2325
    $file = $form_state['file'];
2326
    $langcode = function_exists('entity_language') ? entity_language('file', $file) : NULL;
2327
    $langcode = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2328
    $conditions = array('source' => 'file/' . $file->fid, 'language' => $langcode);
2329
    $path = (isset($file->fid) ? path_load($conditions) : array());
2330
    if ($path === FALSE) {
2331
      $path = array();
2332
    }
2333
    $path += array(
2334
      'pid' => NULL,
2335
      'source' => isset($file->fid) ? 'file/' . $file->fid : NULL,
2336
      'alias' => '',
2337
      'language' => $langcode,
2338
    );
2339
    $form['path'] = array(
2340
      '#type' => 'fieldset',
2341
      '#title' => t('URL path settings'),
2342
      '#collapsible' => TRUE,
2343
      '#collapsed' => empty($path['alias']),
2344
      '#group' => 'additional_settings',
2345
      '#attributes' => array(
2346
        'class' => array('path-form'),
2347
      ),
2348
      '#attached' => array(
2349
        'js' => array(drupal_get_path('module', 'path') . '/path.js'),
2350
      ),
2351
      '#access' => user_access('create url aliases') || user_access('administer url aliases'),
2352
      '#weight' => 30,
2353
      '#tree' => TRUE,
2354
      '#element_validate' => array('path_form_element_validate'),
2355
    );
2356
    $form['path']['alias'] = array(
2357
      '#type' => 'textfield',
2358
      '#title' => t('URL alias'),
2359
      '#default_value' => $path['alias'],
2360
      '#maxlength' => 255,
2361
      '#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.'),
2362
    );
2363
    $form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
2364
    $form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
2365
    $form['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
2366
  }
2367
}
2368

    
2369
/**
2370
 * Implements hook_file_insert() on behalf of path.module.
2371
 */
2372
function path_file_insert($file) {
2373
  if (isset($file->path)) {
2374
    $path = $file->path;
2375
    $path['alias'] = trim($path['alias']);
2376
    // Only save a non-empty alias.
2377
    if (!empty($path['alias'])) {
2378
      // Ensure fields for programmatic executions.
2379
      $path['source'] = 'file/' . $file->fid;
2380
      // Core does not provide a way to store the file language but contrib
2381
      // modules can do it so we need to take this into account.
2382
      $langcode = entity_language('file', $file);
2383
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2384
      path_save($path);
2385
    }
2386
  }
2387
}
2388

    
2389
/**
2390
 * Implements hook_file_update() on behalf of path.module.
2391
 */
2392
function path_file_update($file) {
2393
  if (isset($file->path)) {
2394
    $path = $file->path;
2395
    $path['alias'] = trim($path['alias']);
2396
    // Delete old alias if user erased it.
2397
    if (!empty($path['fid']) && empty($path['alias'])) {
2398
      path_delete($path['fid']);
2399
    }
2400
    // Only save a non-empty alias.
2401
    if (!empty($path['alias'])) {
2402
      // Ensure fields for programmatic executions.
2403
      $path['source'] = 'file/' . $file->fid;
2404
      // Core does not provide a way to store the file language but contrib
2405
      // modules can do it so we need to take this into account.
2406
      $langcode = entity_language('file', $file);
2407
      $path['language'] = !empty($langcode) ? $langcode : LANGUAGE_NONE;
2408
      path_save($path);
2409
    }
2410
  }
2411
}
2412

    
2413
/**
2414
 * Implements hook_file_delete() on behalf of path.module.
2415
 */
2416
function path_file_delete($file) {
2417
  // Delete all aliases associated with this file.
2418
  path_delete(array('source' => 'file/' . $file->fid));
2419
}
2420

    
2421
/**
2422
 * Checks if pattern(s) match mimetype(s).
2423
 */
2424
function file_entity_match_mimetypes($needle, $haystack) {
2425
  $needle = is_array($needle) ? $needle : array($needle);
2426
  $haystack = is_array($haystack) ? $haystack : array($haystack);
2427

    
2428
  foreach ($haystack as $mimetype) {
2429
    foreach ($needle as $search) {
2430
      if (file_entity_fnmatch($search, $mimetype) || file_entity_fnmatch($mimetype, $search)) {
2431
        return TRUE;
2432
      }
2433
    }
2434
  }
2435

    
2436
  return FALSE;
2437
}
2438

    
2439
/**
2440
 * A wrapper function for the PHP function fnmatch().
2441
 *
2442
 * We include this, because Windows servers do not implement fnmatch() until
2443
 * PHP Version 5.3. See: http://php.net/manual/en/function.fnmatch.php
2444
 */
2445
function file_entity_fnmatch($pattern, $string) {
2446
  if (!function_exists('fnmatch')) {
2447
    return preg_match("#^" . strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.', '\[' => '[', '\]' => ']')) . "$#", $string);
2448
  }
2449
  return fnmatch($pattern, $string);
2450
}
2451

    
2452
/**
2453
 * Return an URI for a file download.
2454
 */
2455
function file_entity_download_uri($file) {
2456
  $uri = array('path' => "file/{$file->fid}/download", 'options' => array());
2457
  if (!variable_get('file_entity_allow_insecure_download', FALSE)) {
2458
    $uri['options']['query']['token'] = file_entity_get_download_token($file);
2459
  }
2460
  return $uri;
2461
}
2462

    
2463
function file_entity_file_get_mimetype_type($file) {
2464
  list($type, $subtype) = explode('/', $file->filemime, 2);
2465
  return $type;
2466
}
2467

    
2468
/**
2469
 * Implements hook_admin_menu_map().
2470
 */
2471
function file_entity_admin_menu_map() {
2472
  if (!user_access('administer file types')) {
2473
    return;
2474
  }
2475
  $map['admin/structure/file-types/manage/%file_type'] = array(
2476
    'parent' => 'admin/structure/file-types',
2477
    'arguments' => array(
2478
      array('%file_type' => array_keys(file_entity_type_get_names())),
2479
    ),
2480
  );
2481
  return $map;
2482
}
2483

    
2484
/*
2485
 * Generate a file download CSRF token.
2486
 *
2487
 * This is essentially a duplicate of drupal_get_token, that attempts to still
2488
 * work if the user is anonymous, by using ip_address() as the identifier
2489
 * rather than session_id().
2490
 *
2491
 * @param object $file
2492
 *   A file entity object.
2493
 *
2494
 * @return string
2495
 *   A CSRF token string.
2496
 */
2497
function file_entity_get_download_token($file) {
2498
  $identifier = !empty($GLOBALS['user']->uid) ? session_id() : ip_address();
2499
  return drupal_hmac_base64("file/$file->fid/download", $identifier . drupal_get_private_key() . drupal_get_hash_salt());
2500
}
2501

    
2502
/**
2503
 * Find all fields that are of a certain field type.
2504
 *
2505
 * @param string $field_type
2506
 *   A field type.
2507
 *
2508
 * @return array
2509
 *   An array of field names that match the type $field_type.
2510
 */
2511
function _file_entity_get_fields_by_type($field_type) {
2512
  $return = array();
2513
  if (function_exists('field_info_field_map')) {
2514
    foreach (field_info_field_map() as $field_name => $field) {
2515
      if ($field['type'] == $field_type) {
2516
        $return[$field_name] = $field_name;
2517
      }
2518
    }
2519
  }
2520
  else {
2521
    foreach (field_info_fields() as $field_name => $field) {
2522
      if ($field['type'] == $field_type) {
2523
        $return[$field_name] = $field_name;
2524
      }
2525
    }
2526
  }
2527
  return $return;
2528
}
2529

    
2530
/**
2531
 * Implements hook_field_attach_load().
2532
 */
2533
function file_entity_field_attach_load($entity_type, $entities, $age, $options) {
2534
  // Loop over all the entities looking for entities with attached images.
2535
  foreach ($entities as $entity) {
2536
    list(, , $bundle) = entity_extract_ids($entity_type, $entity);
2537
    // Examine every image field instance attached to this entity's bundle.
2538
    $instances = array_intersect_key(field_info_instances($entity_type, $bundle), _file_entity_get_fields_by_type('image'));
2539
    foreach ($instances as $field_name => $instance) {
2540
      if (!empty($entity->{$field_name})) {
2541
        foreach ($entity->{$field_name} as $langcode => $items) {
2542
          foreach ($items as $delta => $item) {
2543
            // If alt and title text is not specified, fall back to alt and
2544
            // title text on the file.
2545
            if (empty($item['alt']) || empty($item['title'])) {
2546
              $file = file_load($item['fid']);
2547
              foreach (array('alt', 'title') as $key) {
2548
                if (empty($item[$key]) && !empty($file->{$key})) {
2549
                  $entity->{$field_name}[$langcode][$delta][$key] = $file->{$key};
2550
                }
2551
              }
2552
            }
2553
          }
2554
        }
2555
      }
2556
    }
2557
  }
2558
}
2559

    
2560
function file_entity_get_public_and_private_stream_wrapper_names($flag = STREAM_WRAPPERS_VISIBLE) {
2561
  $wrappers = array();
2562
  foreach (file_get_stream_wrappers($flag) as $key => $wrapper) {
2563
    if (empty($wrapper['private'])) {
2564
      $wrappers['public'][$key] = $wrapper['name'];
2565
    }
2566
    else {
2567
      $wrappers['private'][$key] = $wrapper['name'];
2568
    }
2569
  }
2570
  return $wrappers;
2571
}