Projet

Général

Profil

Paste
Télécharger (49,9 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / media / media.module @ e4215af7

1 85ad3d82 Assos Assos
<?php
2
3
/**
4
 * @file
5
 * Media API
6
 *
7
 * The core Media API.
8
 * See http://drupal.org/project/media for more details.
9
 */
10
11
// Code relating to using media as a field.
12
require_once dirname(__FILE__) . '/includes/media.fields.inc';
13
14
/**
15
 * Implements hook_hook_info().
16
 */
17
function media_hook_info() {
18
  $hooks = array(
19
    'media_parse',
20
    'media_browser_plugin_info',
21
    'media_browser_plugin_info_alter',
22
    'media_browser_plugins_alter',
23
    'media_browser_params_alter',
24
    'query_media_browser_alter',
25
  );
26
27
  return array_fill_keys($hooks, array('group' => 'media'));
28
}
29
30
/**
31
 * Implements hook_help().
32
 */
33
function media_help($path, $arg) {
34
  switch ($path) {
35
    case 'admin/help#media':
36
      $output = '';
37
      $output .= '<h3>' . t('About') . '</h3>';
38
      $output .= '<p>' . t('The Media module is a File Browser to the Internet, media provides a framework for managing files and multimedia assets, regardless of whether they are hosted on your own site or a 3rd party site. It replaces the Drupal core upload field with a unified User Interface where editors and administrators can upload, manage, and reuse files and multimedia assets. Media module also provides rich integration with WYSIWYG module to let content creators access media assets in rich text editor. Javascript is required to use the Media module.  For more information check <a href="@media_faq">Media Module page</a>', array('@media_faq' => 'http://drupal.org/project/media')) . '.</p>';
39
      $output .= '<h3>' . t('Uses') . '</h3>';
40
      $output .= '<dl>';
41
      $output .= '<dt>' . t('Media Repository') . '</dt>';
42
      $output .= '<dd>' . t('Media module allows you to maintain a <a href="@mediarepo">media asset repository</a> where in you can add, remove, reuse your media assets. You can add the media file using upload form or from a url and also do bulk operations on the media assets.', array('@mediarepo' => url('admin/content/media'))) . '</dd>';
43
      $output .= '<dt>' . t('Attaching media assets to content types') . '</dt>';
44
      $output .= '<dd>' . t('Media assets can be attached to content types as fields. To add a media field to a <a href="@content-type">content type</a>, go to the content type\'s <em>manage fields</em> page, and add a new field of type <em>Multimedia Asset</em>.', array('@content-type' => url('admin/structure/types'))) . '</dd>';
45
      $output .= '<dt>' . t('Using media assets in WYSIWYG') . '</dt>';
46
      $output .= '<dd>' . t('Media module provides rich integration with WYSIWYG editors, using Media Browser plugin you can select media asset from library to add to the rich text editor moreover you can add media asset from the media browser itself using either upload method or add from url method. To configure media with WYSIWYG you need two steps of configuration:');
47
      $output .= '<ul><li>' . t('Enable WYSIWYG plugin on your desired <a href="@wysiwyg-profile">WYSIWYG profile</a>. Please note that you will need to have <a href="@wysiwyg">WYSIWYG</a> module enabled.', array('@wysiwyg-profile' => url('admin/config/content/wysiwyg'), '@wysiwyg' => 'http://drupal.org/project/wysiwyg')) . '</li>';
48
      $output .= '<li>' . t('Enable the <em>Convert Media tags to markup</em> filter on the <a href="@input-format">Input format</a> you are using with the WYSIWYG profile.', array('@input-format' => url('admin/config/content/formats'))) . '</li></ul></dd>';
49
      return $output;
50
  }
51
}
52
53
/**
54
 * Implements hook_entity_info_alter().
55
 */
56
function media_entity_info_alter(&$entity_info) {
57
  // For sites that updated from Media 1.x, continue to provide these deprecated
58
  // view modes.
59
  // @see http://drupal.org/node/1051090
60 ca0757b9 Assos Assos
  if (variable_get('media_show_deprecated_view_modes', FALSE)) {
61
    $entity_info['file']['view modes']['media_link'] = array(
62
      'label' => t('Link'),
63
      'custom settings' => TRUE,
64
    );
65
    $entity_info['file']['view modes']['media_original'] = array(
66
      'label' => t('Original'),
67
      'custom settings' => TRUE,
68 85ad3d82 Assos Assos
    );
69
  }
70
  if (module_exists('entity_translation')) {
71
    $entity_info['file']['translation']['entity_translation']['class'] = 'MediaEntityTranslationHandler';
72 0ccfec7f Assos Assos
    $entity_info['file']['translation']['entity_translation']['path schemes']['media'] = array('edit path' => 'media/%file/edit/%ctools_js');
73 85ad3d82 Assos Assos
  }
74
}
75
76
/**
77
 * Implements hook_menu().
78
 */
79
function media_menu() {
80
  // For managing different types of media and the fields associated with them.
81
  $items['admin/config/media/browser'] = array(
82
    'title' => 'Media browser settings',
83
    'description' => 'Configure the behavior and display of the media browser.',
84
    'page callback' => 'drupal_get_form',
85
    'page arguments' => array('media_admin_config_browser'),
86
    'access arguments' => array('administer media browser'),
87
    'file' => 'includes/media.admin.inc',
88
  );
89
90
  // Administrative screens for managing media.
91
  $items['admin/content/file/thumbnails'] = array(
92
    'title' => 'Thumbnails',
93
    'description' => 'Manage files used on your site.',
94
    'page callback' => 'drupal_get_form',
95
    'page arguments' => array('file_entity_admin_file'),
96
    'access arguments' => array('administer files'),
97
    'type' => MENU_LOCAL_TASK,
98
    'file' => 'file_entity.admin.inc',
99
    'file path' => drupal_get_path('module', 'file_entity'),
100
    'weight' => 10,
101
  );
102
103 ca0757b9 Assos Assos
  $items['media/ajax'] = array(
104
    'page callback' => 'media_ajax_upload',
105
    'delivery callback' => 'ajax_deliver',
106
    'access arguments' => array('access content'),
107
    'theme callback' => 'ajax_base_page_theme',
108
    'type' => MENU_CALLBACK,
109 85ad3d82 Assos Assos
  );
110
111
  $items['media/browser'] = array(
112
    'title' => 'Media browser',
113
    'description' => 'Media Browser for picking media and uploading new media',
114
    'page callback' => 'media_browser',
115 ca0757b9 Assos Assos
    'access arguments' => array('access media browser'),
116 85ad3d82 Assos Assos
    'type' => MENU_CALLBACK,
117
    'file' => 'includes/media.browser.inc',
118
    'theme callback' => 'media_dialog_get_theme_name',
119
  );
120
121
  // A testbed to try out the media browser with different launch commands.
122
  $items['media/browser/testbed'] = array(
123
    'title' => 'Media Browser test',
124
    'description' => 'Make it easier to test media browser',
125
    'page callback' => 'drupal_get_form',
126
    'page arguments' => array('media_browser_testbed'),
127
    'access arguments' => array('administer files'),
128
    'type' => MENU_CALLBACK,
129
    'file' => 'includes/media.browser.inc',
130
  );
131
132
  // We could re-use the file/%file/edit path for the modal callback, but
133
  // it is just easier to use our own namespace here.
134
  $items['media/%file/edit/%ctools_js'] = array(
135
    'title' => 'Edit',
136
    'page callback' => 'drupal_get_form',
137
    'page arguments' => array('media_file_edit_modal', 1, 3),
138
    'access callback' => 'file_entity_access',
139
    'access arguments' => array('update', 1),
140 ca0757b9 Assos Assos
    'theme callback' => 'ajax_base_page_theme',
141 85ad3d82 Assos Assos
    'file' => 'includes/media.pages.inc',
142
    'type' => MENU_CALLBACK,
143
  );
144
145
  return $items;
146
}
147
148
/**
149
 * Implements hook_menu_local_tasks_alter().
150
 */
151
function media_menu_local_tasks_alter(&$data, $router_item, $root_path) {
152
  // Add action link to 'file/add' on 'admin/content/file/thumbnails' page.
153
  if ($root_path == 'admin/content/file/thumbnails') {
154
    $item = menu_get_item('file/add');
155
    if (!empty($item['access'])) {
156
      $data['actions']['output'][] = array(
157
        '#theme' => 'menu_local_action',
158
        '#link' => $item,
159
        '#weight' => $item['weight'],
160
      );
161
    }
162
  }
163
}
164
165
/**
166
 * Implements hook_admin_paths().
167
 */
168
function media_admin_paths() {
169
  $paths['media/*/edit/*'] = TRUE;
170 ca0757b9 Assos Assos
  $paths['media/*/format-form'] = TRUE;
171 d808fa20 Assos Assos
  if (module_exists('admin_language')) {
172
    $paths['media/ajax/*'] = TRUE; // For admin_language module compatibility.
173
  }
174 85ad3d82 Assos Assos
175
  // If the media browser theme is set to the admin theme, ensure it gets set
176
  // as an admin path as well.
177 ca0757b9 Assos Assos
  $dialog_theme = variable_get('media_dialog_theme', '');
178 85ad3d82 Assos Assos
  if (empty($dialog_theme) || $dialog_theme == variable_get('admin_theme')) {
179
    $paths['media/browser'] = TRUE;
180
    $paths['media/browser/*'] = TRUE;
181
  }
182
183
  return $paths;
184
}
185
186
/**
187
 * Implements hook_permission().
188
 */
189
function media_permission() {
190
  return array(
191
    'administer media browser' => array(
192
      'title' => t('Administer media browser'),
193
      'description' => t('Access media browser settings.'),
194
    ),
195 ca0757b9 Assos Assos
    'access media browser' => array(
196
      'title' => t('Use the media browser'),
197 85ad3d82 Assos Assos
    ),
198
  );
199
}
200
201
/**
202
 * Implements hook_theme().
203
 */
204
function media_theme() {
205
  return array(
206 ca0757b9 Assos Assos
    // media.module.
207
    'media_element' => array(
208
      'render element' => 'element',
209 85ad3d82 Assos Assos
    ),
210
211 ca0757b9 Assos Assos
    // media.field.inc.
212
    'media_widget' => array(
213 85ad3d82 Assos Assos
      'render element' => 'element',
214
    ),
215 ca0757b9 Assos Assos
    'media_widget_multiple' => array(
216
      'render element' => 'element',
217
    ),
218
    'media_upload_help' => array(
219
      'variables' => array('description' => NULL),
220 85ad3d82 Assos Assos
    ),
221
222 ca0757b9 Assos Assos
    // media.theme.inc.
223
    'media_thumbnail' => array(
224 85ad3d82 Assos Assos
      'render element' => 'element',
225
      'file' => 'includes/media.theme.inc',
226
    ),
227
    'media_formatter_large_icon' => array(
228
      'variables' => array('file' => NULL, 'attributes' => array(), 'style_name' => 'media_thumbnail'),
229
      'file' => 'includes/media.theme.inc',
230
    ),
231 ca0757b9 Assos Assos
    'media_dialog_page' => array(
232
      'render element' => 'page',
233
      'template' => 'templates/media-dialog-page',
234
      'file' => 'includes/media.theme.inc',
235
    ),
236 85ad3d82 Assos Assos
  );
237
}
238
239 ca0757b9 Assos Assos
/**
240
 * Menu callback; Shared Ajax callback for media attachment and deletions.
241
 *
242
 * This rebuilds the form element for a particular field item. As long as the
243
 * form processing is properly encapsulated in the widget element the form
244
 * should rebuild correctly using FAPI without the need for additional callbacks
245
 * or processing.
246
 */
247
function media_ajax_upload() {
248
  $form_parents = func_get_args();
249
  $form_build_id = (string) array_pop($form_parents);
250
251 01f36513 Assos Assos
  // Sanitize form parents before using them.
252
  $form_parents = array_filter($form_parents, 'element_child');
253
254 ca0757b9 Assos Assos
  if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
255
    // Invalid request.
256
    drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
257
    $commands = array();
258
    $commands[] = ajax_command_replace(NULL, theme('status_messages'));
259
    return array('#type' => 'ajax', '#commands' => $commands);
260
  }
261
262
  list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
263
264
  if (!$form) {
265
    // Invalid form_build_id.
266
    drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
267
    $commands = array();
268
    $commands[] = ajax_command_replace(NULL, theme('status_messages'));
269
    return array('#type' => 'ajax', '#commands' => $commands);
270
  }
271
272
  // Get the current element and count the number of files.
273
  $current_element = $form;
274
  foreach ($form_parents as $parent) {
275 da542b7b Assos Assos
    if (isset($current_element[$parent])) {
276
      $current_element = $current_element[$parent];
277
    }
278
    else {
279
      $current_element = NULL;
280
      break;
281
    }
282 ca0757b9 Assos Assos
  }
283
  $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0;
284
285
  // Process user input. $form and $form_state are modified in the process.
286
  drupal_process_form($form['#form_id'], $form, $form_state);
287
288
  // Retrieve the element to be rendered.
289
  foreach ($form_parents as $parent) {
290
    $form = $form[$parent];
291
  }
292
293
  // Add the special Ajax class if a new file was added.
294
  if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
295
    $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
296
  }
297
  // Otherwise just add the new content class on a placeholder.
298
  else {
299
    $form['#suffix'] .= '<span class="ajax-new-content"></span>';
300
  }
301
302
  $output = theme('status_messages') . drupal_render($form);
303
  $js = drupal_add_js();
304
  $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
305
306
  $commands[] = ajax_command_replace(NULL, $output, $settings);
307
  return array('#type' => 'ajax', '#commands' => $commands);
308
}
309
310 85ad3d82 Assos Assos
/**
311
 * Implements hook_image_default_styles().
312
 */
313
function media_image_default_styles() {
314
  $styles = array();
315
  $styles['media_thumbnail'] = array(
316
    'label' => 'Media thumbnail (100x100)',
317
    'effects' => array(
318
      array(
319
        'name' => 'image_scale_and_crop',
320
        'data' => array('width' => 100, 'height' => 100),
321
        'weight' => 0,
322
      ),
323
    ),
324
  );
325
  return $styles;
326
}
327
328
/**
329
 * Implements hook_page_alter().
330
 *
331
 * This is used to use our alternate template when ?render=media-popup is passed
332
 * in the URL.
333
 */
334
function media_page_alter(&$page) {
335
  if (isset($_GET['render']) && $_GET['render'] == 'media-popup') {
336
    $page['#theme'] = 'media_dialog_page';
337
338
    // Disable administration modules from adding output to the popup.
339
    // @see http://drupal.org/node/914786
340
    module_invoke_all('suppress', TRUE);
341
342
    foreach (element_children($page) as $key) {
343
      if ($key != 'content') {
344
        unset($page[$key]);
345
      }
346
    }
347
  }
348
}
349
350
/**
351
 * Implements hook_form_FIELD_UI_FIELD_EDIT_FORM_alter().
352
 *
353
 * @todo: Respect field settings in 7.x-2.x and handle them in the media widget
354
 * UI.
355
 */
356
function media_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
357
  // On file fields that use the media widget we need remove specific fields.
358
  if ($form['#field']['type'] == 'file' && $form['instance']['widget']['type']['#value'] == 'media_generic') {
359
    $form['instance']['settings']['file_extensions']['#title'] = t('Allowed file extensions for uploaded files');
360
    $form['instance']['settings']['file_extensions']['#maxlength'] = 255;
361 da542b7b Assos Assos
    $form['instance']['settings']['file_extensions']['#description'] .= '<br />' . t('If empty, the file extensions list will be composed automatically using the allowed file types.');
362
    $form['instance']['settings']['file_extensions']['#required'] = FALSE;
363 85ad3d82 Assos Assos
  }
364
365
  if ($form['#field']['type'] == 'image' && $form['instance']['widget']['type']['#value'] == 'media_generic') {
366
    $form['instance']['settings']['file_extensions']['#title'] = t('Allowed file extensions for uploaded files');
367
    // Do not increase maxlength of file extensions for image fields, since
368
    // presumably they will not need a long list of extensions.
369
  }
370 ca0757b9 Assos Assos
371
  // Add a validation function to any field instance which uses the media widget
372 bf3c8457 Florent Torregrosa
  // to ensure that the upload destination scheme is one of the allowed schemes
373
  // if any defined by settings.
374 388c412d Assos Assos
  if (isset($form['instance']['widget']) && $form['instance']['widget']['type']['#value'] == 'media_generic' && isset($form['#field']['settings']['uri_scheme'])) {
375 ca0757b9 Assos Assos
    $form['#validate'][] = 'media_field_instance_validate';
376
  }
377 85ad3d82 Assos Assos
}
378
379
/**
380 ca0757b9 Assos Assos
 * Validation handler; ensure that the upload destination scheme is one of the
381
 * allowed schemes.
382 85ad3d82 Assos Assos
 */
383 ca0757b9 Assos Assos
function media_field_instance_validate($form, &$form_state) {
384 bf3c8457 Florent Torregrosa
  $allowed_schemes = array_filter($form_state['values']['instance']['widget']['settings']['allowed_schemes']);
385 ca0757b9 Assos Assos
  $upload_destination = $form_state['values']['field']['settings']['uri_scheme'];
386 85ad3d82 Assos Assos
387 bf3c8457 Florent Torregrosa
  if (!empty($allowed_schemes) && !in_array($upload_destination, $allowed_schemes)) {
388 ca0757b9 Assos Assos
    form_set_error('allowed_schemes', t('The upload destination must be one of the allowed schemes.'));
389 85ad3d82 Assos Assos
  }
390
}
391
392
/**
393
 * Implements hook_form_alter().
394
 */
395
function media_form_alter(&$form, &$form_state, $form_id) {
396
  // If we're in the media browser, set the #media_browser key to true
397
  // so that if an ajax request gets sent to a different path, the form
398
  // still uses the media_browser_form_submit callback.
399 bf3c8457 Florent Torregrosa
  if (current_path() == 'media/browser') {
400
    if ($form_id == 'views_exposed_form') {
401
      $form['render'] = array('#type' => 'hidden', '#value' => 'media-popup');
402
      $form['#action'] = '/media/browser';
403
    } else {
404
      $form_state['#media_browser'] = TRUE;
405
    }
406 85ad3d82 Assos Assos
  }
407
408
  // If the #media_browser key isset and is true we are using the browser
409
  // popup, so add the media_browser submit handler.
410
  if (!empty($form_state['#media_browser'])) {
411
    $form['#submit'][] = 'media_browser_form_submit';
412
  }
413
}
414
415
/**
416
 * Submit handler; direction form submissions in the media browser.
417
 */
418
function media_browser_form_submit($form, &$form_state) {
419
  $url = NULL;
420
  $parameters = array();
421
422
  // Single upload.
423
  if (!empty($form_state['file'])) {
424
    $file = $form_state['file'];
425
    $url = 'media/browser';
426
    $parameters = array('query' => array('render' => 'media-popup', 'fid' => $file->fid));
427
  }
428
429
  // If $url is set, we had some sort of upload, so redirect the form.
430
  if (!empty($url)) {
431
    $form_state['redirect'] = array($url, $parameters);
432
  }
433
}
434
435
/**
436
 * Implements hook_library().
437
 */
438
function media_library() {
439
  $path = drupal_get_path('module', 'media');
440
  $info = system_get_info('module', 'media');
441
442
  $common = array(
443
    'website' => 'http://drupal.org/project/media',
444
    'version' => !empty($info['version']) ? $info['version'] : '7.x-2.x',
445
  );
446
447
  // Contains libraries common to other media modules.
448
  $libraries['media_base'] = array(
449
    'title' => 'Media base',
450
    'js' => array(
451
      $path . '/js/media.core.js' => array('group' => JS_LIBRARY, 'weight' => -5),
452
      $path . '/js/util/json2.js' => array('group' => JS_LIBRARY),
453
      $path . '/js/util/ba-debug.min.js' => array('group' => JS_LIBRARY),
454
    ),
455
    'css' => array(
456
      $path . '/css/media.css',
457
    ),
458
  );
459
460
  // Includes resources needed to launch the media browser.  Should be included
461
  // on pages where the media browser needs to be launched from.
462
  $libraries['media_browser'] = array(
463
    'title' => 'Media Browser popup libraries',
464
    'js' => array(
465
      $path . '/js/media.popups.js' => array('group' => JS_DEFAULT),
466
    ),
467
    'dependencies' => array(
468
      array('system', 'ui.resizable'),
469
      array('system', 'ui.draggable'),
470
      array('system', 'ui.dialog'),
471
      array('media', 'media_base'),
472
    ),
473
  );
474
475
  // Resources needed in the media browser itself.
476
  $libraries['media_browser_page'] = array(
477
    'title' => 'Media browser',
478
    'js' => array(
479
      $path . '/js/media.browser.js'  => array('group' => JS_DEFAULT),
480
    ),
481
    'dependencies' => array(
482
      array('system', 'ui.tabs'),
483
      array('system', 'ui.draggable'),
484
      array('system', 'ui.dialog'),
485
      array('media', 'media_base'),
486
    ),
487
  );
488
489 bf3c8457 Florent Torregrosa
  // Settings for the dialog etc.
490
  $settings = array(
491
    'browserUrl' => url('media/browser', array(
492
      'query' => array(
493
        'render' => 'media-popup'
494
      ))
495
    ),
496
    'styleSelectorUrl' => url('media/-media_id-/format-form', array(
497 29771811 Assos Assos
        'query' => array('render' => 'media-popup')
498
      )
499 bf3c8457 Florent Torregrosa
    ),
500
    'dialogOptions' => array(
501
      'dialogclass' => variable_get('media_dialogclass', 'media-wrapper'),
502 29771811 Assos Assos
      'modal' => (boolean) variable_get('media_modal', TRUE),
503
      'draggable' => (boolean) variable_get('media_draggable', FALSE),
504
      'resizable' => (boolean) variable_get('media_resizable', FALSE),
505
      'minwidth' => (int) variable_get('media_minwidth', 500),
506
      'width' => (int) variable_get('media_width', 670),
507
      'height' => (int) variable_get('media_height', 280),
508 bf3c8457 Florent Torregrosa
      'position' => variable_get('media_position', 'center'),
509
      'overlay' => array(
510
        'backgroundcolor' => variable_get('media_backgroundcolor', '#000000'),
511 29771811 Assos Assos
        'opacity' => (float) variable_get('media_opacity', 0.4),
512 bf3c8457 Florent Torregrosa
      ),
513 29771811 Assos Assos
      'zindex' => (int) variable_get('media_zindex', 10000),
514 bf3c8457 Florent Torregrosa
    ),
515
  );
516
517
  $libraries['media_browser_settings'] = array(
518
    'title' => 'Media browser settings',
519
    'js' => array(
520
      0 => array(
521
        'data' => array(
522
          'media' => $settings,
523
        ),
524
        'type' => 'setting',
525
      ),
526
    ),
527
  );
528
529 85ad3d82 Assos Assos
  foreach ($libraries as &$library) {
530
    $library += $common;
531
  }
532
  return $libraries;
533
}
534
535
/**
536
 * Theme callback used to identify when we are in a popup dialog.
537
 *
538
 * Generally the default theme will look terrible in the media browser. This
539
 * will default to the administration theme, unless set otherwise.
540
 */
541
function media_dialog_get_theme_name() {
542 ca0757b9 Assos Assos
  return variable_get('media_dialog_theme', variable_get('admin_theme'));
543 85ad3d82 Assos Assos
}
544
545
/**
546
 * This will parse a url or embedded code into a unique URI.
547
 *
548
 * The function will call all modules implementing hook_media_parse($url),
549
 * which should return either a string containing a parsed URI or NULL.
550
 *
551
 * @NOTE The implementing modules may throw an error, which will not be caught
552
 * here; it's up to the calling function to catch any thrown errors.
553
 *
554
 * @NOTE In emfield, we originally also accepted an array of regex patterns
555
 * to match against. However, that module used a registration for providers,
556
 * and simply stored the match in the database keyed to the provider object.
557
 * However, other than the stream wrappers, there is currently no formal
558
 * registration for media handling. Additionally, few, if any, stream wrappers
559
 * will choose to store a straight match from the parsed URL directly into
560
 * the URI. Thus, we leave both the matching and the final URI result to the
561
 * implementing module in this implementation.
562
 *
563
 * An alternative might be to do the regex pattern matching here, and pass a
564
 * successful match back to the implementing module. However, that would
565
 * require either an overloaded function or a new hook, which seems like more
566
 * overhead than it's worth at this point.
567
 *
568
 * @TODO Once hook_module_implements_alter() is in core (see the issue at
569
 * http://drupal.org/node/692950) we may want to implement media_media_parse()
570
 * to ensure we were passed a valid URL, rather than an unsupported or
571
 * malformed embed code that wasn't caught earlier. It will needed to be
572
 * weighted so it's called after all other streams have a go, as the fallback,
573
 * and will need to throw an error.
574
 *
575
 * @param string $url
576
 *   The original URL or embed code to parse.
577
 *
578
 * @return string
579
 *   The unique URI for the file, based on its stream wrapper, or NULL.
580
 *
581
 * @see media_parse_to_file()
582
 * @see media_add_from_url_validate()
583
 */
584
function media_parse_to_uri($url) {
585
  // Trim any whitespace before parsing.
586
  $url = trim($url);
587
  foreach (module_implements('media_parse') as $module) {
588
    $success = module_invoke($module, 'media_parse', $url);
589 bf3c8457 Florent Torregrosa
    $context = array(
590
      'url' => $url,
591
      'module' => $module,
592
    );
593
    drupal_alter('media_parse', $success, $context);
594 85ad3d82 Assos Assos
    if (isset($success)) {
595
      return $success;
596
    }
597
  }
598
}
599
600
/**
601
 * Parse a URL or embed code and return a file object.
602
 *
603
 * If a remote stream doesn't claim the parsed URL in media_parse_to_uri(),
604
 * then we'll copy the file locally.
605
 *
606
 * @NOTE The implementing modules may throw an error, which will not be caught
607
 * here; it's up to the calling function to catch any thrown errors.
608
 *
609
 * @see media_parse_to_uri()
610
 * @see media_add_from_url_submit()
611
 */
612 0125e073 Assos Assos
function media_parse_to_file($url, $params = array()) {
613 85ad3d82 Assos Assos
  try {
614
    $uri = media_parse_to_uri($url);
615
  }
616
  catch (Exception $e) {
617
    // Pass the error along.
618
    throw $e;
619
    return;
620
  }
621
622
  if (isset($uri)) {
623
    // Attempt to load an existing file from the unique URI.
624
    $select = db_select('file_managed', 'f')
625 29771811 Assos Assos
      ->extend('PagerDefault')
626
      ->fields('f', array('fid'))
627
      ->condition('uri', $uri);
628 85ad3d82 Assos Assos
629
    $fid = $select->execute()->fetchCol();
630
    if (!empty($fid)) {
631
      $file = file_load(array_pop($fid));
632
      return $file;
633
    }
634
  }
635
636
  if (isset($uri)) {
637
    // The URL was successfully parsed to a URI, but does not yet have an
638
    // associated file: save it!
639
    $file = file_uri_to_object($uri);
640
    file_save($file);
641
  }
642
  else {
643
    // The URL wasn't parsed. We'll try to save a remote file.
644
    // Copy to temporary first.
645
    $source_uri = file_stream_wrapper_uri_normalize('temporary://' . basename($url));
646
    if (!@copy(@$url, $source_uri)) {
647
      throw new Exception('Unable to add file ' . $url);
648
    }
649
    $source_file = file_uri_to_object($source_uri);
650 29771811 Assos Assos
    if (isset($params['to_directory'])) {
651 0125e073 Assos Assos
      $scheme = variable_get('file_default_scheme', 'public') . '://' . $params['to_directory'] . '/';
652
    }
653
    else{
654
      $scheme = variable_get('file_default_scheme', 'public') . '://';
655
    }
656 85ad3d82 Assos Assos
    $uri = file_stream_wrapper_uri_normalize($scheme . $source_file->filename);
657
    // Now to its new home.
658 0125e073 Assos Assos
    file_prepare_directory($scheme, FILE_CREATE_DIRECTORY || FILE_MODIFY_PERMISSIONS);
659 85ad3d82 Assos Assos
    $file = file_move($source_file, $uri, FILE_EXISTS_RENAME);
660
  }
661
662
  return $file;
663
}
664
665
/**
666 8fa03d70 Assos Assos
 * Custom implementation of array_walk_recursive() that works around a crash
667
 * some users have been experiencing with that function in PHP 7.
668 85ad3d82 Assos Assos
 *
669 8fa03d70 Assos Assos
 * @see https://www.drupal.org/project/media/issues/2998097
670 85ad3d82 Assos Assos
 */
671 8fa03d70 Assos Assos
function media_array_walk_recursive(&$array) {
672
  foreach ($array as $key => $value) {
673
    if (is_array($array[$key])) {
674
      media_array_walk_recursive($array[$key]);
675
    }
676
    else {
677
      $array[$key] = check_plain($array[$key]);
678
    }
679
  }
680 85ad3d82 Assos Assos
}
681
682
/**
683
 * Implements hook_element_info().
684
 */
685
function media_element_info() {
686
  $types['media'] = array(
687
    '#input' => TRUE,
688
    '#process' => array('media_element_process'),
689 ca0757b9 Assos Assos
    '#value_callback' => 'media_file_value',
690 85ad3d82 Assos Assos
    '#element_validate' => array('media_element_validate'),
691 ca0757b9 Assos Assos
    '#pre_render' => array('media_element_pre_render'),
692 a2bb1a14 Assos Assos
    '#theme' => 'media_widget',
693 ca0757b9 Assos Assos
    '#theme_wrappers' => array('form_element'),
694
    '#size' => 22,
695 85ad3d82 Assos Assos
    '#extended' => FALSE,
696
    '#media_options' => array(
697
      'global' => array(
698
        // Example: array('image', 'audio');
699
        'types' => array(),
700
        // Example: array('http', 'ftp', 'flickr');
701
        'schemes' => array(),
702
      ),
703
    ),
704
    '#attached' => array(
705
      'library' => array(
706
        array('media', 'media_browser'),
707
      ),
708
    ),
709
  );
710 ca0757b9 Assos Assos
711
  $setting = array();
712
  $setting['media']['global'] = $types['media']['#media_options'];
713
714
  $types['media']['#attached']['js'][] = array(
715
    'type' => 'setting',
716
    'data' => $setting,
717
  );
718
719 85ad3d82 Assos Assos
  return $types;
720
}
721
722
/**
723
 * Process callback for the media form element.
724
 */
725 ca0757b9 Assos Assos
function media_element_process($element, &$form_state, $form) {
726 85ad3d82 Assos Assos
  ctools_include('modal');
727
  ctools_include('ajax');
728
  ctools_modal_add_js();
729 bf3c8457 Florent Torregrosa
730
  // Append the '-upload' to the #id so the field label's 'for' attribute
731
  // corresponds with the textfield element.
732
  $original_id = $element['#id'];
733
  $element['#id'] .= '-upload';
734 ca0757b9 Assos Assos
  $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
735 85ad3d82 Assos Assos
736
  // Set some default element properties.
737 ca0757b9 Assos Assos
  $element['#file'] = $fid ? file_load($fid) : FALSE;
738
  $element['#tree'] = TRUE;
739
740
  $ajax_settings = array(
741
    'path' => 'media/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
742 bf3c8457 Florent Torregrosa
    'wrapper' => $original_id . '-ajax-wrapper',
743 ca0757b9 Assos Assos
    'effect' => 'fade',
744
  );
745
746
  // Set up the buttons first since we need to check if they were clicked.
747
  $element['attach_button'] = array(
748
    '#name' => implode('_', $element['#parents']) . '_attach_button',
749
    '#type' => 'submit',
750
    '#value' => t('Attach'),
751
    '#validate' => array(),
752
    '#submit' => array('media_file_submit'),
753
    '#limit_validation_errors' => array($element['#parents']),
754
    '#ajax' => $ajax_settings,
755
    '#attributes' => array('class' => array('attach')),
756
    '#weight' => -1,
757 85ad3d82 Assos Assos
  );
758
759
  $element['preview'] = array(
760 ca0757b9 Assos Assos
    'content' => array(),
761
    '#prefix' => '<div class="preview">',
762 85ad3d82 Assos Assos
    '#suffix' => '</div>',
763 ca0757b9 Assos Assos
    '#ajax' => $ajax_settings,
764
    '#weight' => -10,
765 85ad3d82 Assos Assos
  );
766
767 ca0757b9 Assos Assos
  // Substitute the JS preview for a true file thumbnail once the file is
768
  // attached.
769
  if ($fid && $element['#file']) {
770
    $element['preview']['content'] = media_get_thumbnail_preview($element['#file']);
771
  }
772
773
  // The file ID field itself.
774
  $element['upload'] = array(
775
    '#name' => 'media[' . implode('_', $element['#parents']) . ']',
776
    '#type' => 'textfield',
777
    '#title' => t('Enter the ID of an existing file'),
778
    '#title_display' => 'invisible',
779
    '#field_prefix' => t('File ID'),
780
    '#size' => $element['#size'],
781
    '#theme_wrappers' => array(),
782
    '#attributes' => array('class' => array('upload')),
783
    '#weight' => -9,
784
  );
785
786
  $element['browse_button'] = array(
787 85ad3d82 Assos Assos
    '#type' => 'link',
788
    '#href' => '',
789 ca0757b9 Assos Assos
    '#title' => t('Browse'),
790
    '#attributes' => array('class' => array('button', 'browse', 'element-hidden')),
791 85ad3d82 Assos Assos
    '#options' => array('fragment' => FALSE, 'external' => TRUE),
792 ca0757b9 Assos Assos
    '#weight' => -8,
793 85ad3d82 Assos Assos
  );
794 ca0757b9 Assos Assos
795
  // Force the progress indicator for the remove button to be either 'none' or
796
  // 'throbber', even if the upload button is using something else.
797
  $ajax_settings['progress']['type'] = 'throbber';
798
  $ajax_settings['progress']['message'] = NULL;
799
  $ajax_settings['effect'] = 'none';
800 85ad3d82 Assos Assos
  $element['edit'] = array(
801
    '#type' => 'link',
802
    '#href' => 'media/' . $fid . '/edit/nojs',
803
    '#title' => t('Edit'),
804
    '#attributes' => array(
805
      'class' => array(
806
        // Required for CTools modal to work.
807 0ccfec7f Assos Assos
        'ctools-use-modal',
808 85ad3d82 Assos Assos
        'ctools-modal-media-file-edit', 'button', 'edit',
809
      ),
810
    ),
811
    '#weight' => 20,
812 ca0757b9 Assos Assos
    '#access' => $element['#file'] ? file_entity_access('update', $element['#file']) : FALSE,
813 85ad3d82 Assos Assos
  );
814 7295e063 Assos Assos
815
  // If we have parent entity form/source langcodes, pass them in query. They
816
  // will be used in
817
  /* @see media_file_edit_modal() */
818
  if (!empty($element['#media_parent_entity_form_langcode'])) {
819
    $element['edit']['#options']['query']['media_parent_entity_form_langcode'] = $element['#media_parent_entity_form_langcode'];
820
    if (!empty($element['#media_parent_entity_source_langcode'])) {
821
      $element['edit']['#options']['query']['media_parent_entity_source_langcode'] = $element['#media_parent_entity_source_langcode'];
822
    }
823
  }
824
825 ca0757b9 Assos Assos
  $element['remove_button'] = array(
826
    '#name' => implode('_', $element['#parents']) . '_remove_button',
827
    '#type' => 'submit',
828
    '#value' => t('Remove'),
829
    '#validate' => array(),
830
    '#submit' => array('media_file_submit'),
831
    '#limit_validation_errors' => array($element['#parents']),
832
    '#ajax' => $ajax_settings,
833
    '#attributes' => array('class' => array('remove')),
834
    '#weight' => 0,
835 85ad3d82 Assos Assos
  );
836
837
  $element['fid'] = array(
838
    '#type' => 'hidden',
839
    '#value' => $fid,
840
    '#attributes' => array('class' => array('fid')),
841
  );
842
843
  // Media browser attach code.
844
  $element['#attached']['js'][] = drupal_get_path('module', 'media') . '/js/media.js';
845
846 0939d55c Assos Assos
  // IDs of form elements are 'unstable' in Drupal because of drupal_html_id
847
  // add a class for our Javascript instead.
848
  $element_js_class = drupal_html_class('js-media-element-' . $element['#id']);
849
  $element['upload']['#attributes']['class'][] = $element_js_class;
850
851 388c412d Assos Assos
  // Cache the media options and pass the cache ID as a JavaScript setting.
852
  $cid = drupal_get_token(drupal_random_bytes(32));
853
  cache_set('media_options:' . $cid, $element['#media_options']['global'], 'cache_form', REQUEST_TIME + 21600);
854
855 ca0757b9 Assos Assos
  $element['browse_button']['#attached']['js'] = array(
856
    array(
857
      'type' => 'setting',
858 388c412d Assos Assos
      'data' => array('media' => array('elements' => array('.' . $element_js_class => array('global' => array('options' => $cid))))),
859 ca0757b9 Assos Assos
    )
860 85ad3d82 Assos Assos
  );
861
862 bf3c8457 Florent Torregrosa
  $element['#attached']['library'][] = array('media', 'media_browser');
863
  $element['#attached']['library'][] = array('media', 'media_browser_settings');
864 85ad3d82 Assos Assos
865 ca0757b9 Assos Assos
  // Prefix and suffix used for Ajax replacement.
866 bf3c8457 Florent Torregrosa
  $element['#prefix'] = '<div id="' . $original_id . '-ajax-wrapper">';
867 ca0757b9 Assos Assos
  $element['#suffix'] = '</div>';
868
869 85ad3d82 Assos Assos
  return $element;
870 ca0757b9 Assos Assos
}
871
872
/**
873
 * Implements hook_form_FORM_ID_alter().
874
 */
875
function media_form_file_entity_edit_alter(&$form, &$form_state) {
876
  // Make adjustments to the file edit form when used in a CTools modal.
877
  if (!empty($form_state['ajax'])) {
878
    // Remove the preview and the delete button.
879
    $form['preview']['#access'] = FALSE;
880
    $form['actions']['delete']['#access'] = FALSE;
881
882
    // Convert the cancel link to a button which triggers a modal close.
883
    $form['actions']['cancel']['#attributes']['class'][] = 'button';
884
    $form['actions']['cancel']['#attributes']['class'][] = 'button-no';
885
    $form['actions']['cancel']['#attributes']['class'][] = 'ctools-close-modal';
886
  }
887
}
888
889
/**
890
 * The #value_callback for a media type element.
891
 */
892
function media_file_value(&$element, $input = FALSE, $form_state = NULL) {
893
  $fid = 0;
894
895
  // Find the current value of this field from the form state.
896
  $form_state_fid = $form_state['values'];
897
  foreach ($element['#parents'] as $parent) {
898
    $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0;
899
  }
900
901
  if ($element['#extended'] && isset($form_state_fid['fid'])) {
902
    $fid = $form_state_fid['fid'];
903
  }
904
  elseif (is_numeric($form_state_fid)) {
905
    $fid = $form_state_fid;
906
  }
907
908
  // Process any input and attach files.
909
  if ($input !== FALSE) {
910
    $return = $input;
911
912
    // Attachments take priority over all other values.
913
    if ($file = media_attach_file($element)) {
914
      $fid = $file->fid;
915
    }
916
    else {
917
      // Check for #filefield_value_callback values.
918
      // Because FAPI does not allow multiple #value_callback values like it
919
      // does for #element_validate and #process, this fills the missing
920
      // functionality to allow File fields to be extended through FAPI.
921
      if (isset($element['#file_value_callbacks'])) {
922
        foreach ($element['#file_value_callbacks'] as $callback) {
923
          $callback($element, $input, $form_state);
924
        }
925
      }
926
      // Load file if the FID has changed to confirm it exists.
927
      if (isset($input['fid']) && $file = file_load($input['fid'])) {
928
        $fid = $file->fid;
929
      }
930
    }
931
  }
932
933
  // If there is no input, set the default value.
934
  else {
935
    if ($element['#extended']) {
936
      $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
937
      $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0);
938
    }
939
    else {
940
      $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0;
941
      $return = array('fid' => 0);
942
    }
943
944
    // Confirm that the file exists when used as a default value.
945
    if ($default_fid && $file = file_load($default_fid)) {
946
      $fid = $file->fid;
947
    }
948
  }
949
950
  $return['fid'] = $fid;
951
952
  return $return;
953 85ad3d82 Assos Assos
}
954
955
/**
956
 * Validate media form elements.
957
 *
958
 * The file type is validated during the upload process, but this is necessary
959
 * necessary in order to respect the #required property.
960
 */
961
function media_element_validate(&$element, &$form_state) {
962 ca0757b9 Assos Assos
  $clicked_button = end($form_state['triggering_element']['#parents']);
963
964
  // Check required property based on the FID.
965
  if ($element['#required'] && empty($element['fid']['#value']) && !in_array($clicked_button, array('attach_button', 'remove_button'))) {
966
    form_error($element['browse_button'], t('!name field is required.', array('!name' => $element['#title'])));
967
  }
968
969
  // Consolidate the array value of this field to a single FID.
970
  if (!$element['#extended']) {
971
    form_set_value($element, $element['fid']['#value'], $form_state);
972
  }
973
}
974
975
/**
976
 * Form submission handler for attach / remove buttons of media elements.
977
 *
978
 * @see media_element_process()
979
 */
980
function media_file_submit($form, &$form_state) {
981
  // Determine whether it was the attach or remove button that was clicked, and
982
  // set $element to the managed_file element that contains that button.
983
  $parents = $form_state['triggering_element']['#array_parents'];
984
  $button_key = array_pop($parents);
985
  $element = drupal_array_get_nested_value($form, $parents);
986
987
  // No action is needed here for the attach button, because all media
988
  // attachments on the form are processed by media_file_value() regardless of
989
  // which button was clicked. Action is needed here for the remove button,
990
  // because we only remove a file in response to its remove button being
991
  // clicked.
992
  if ($button_key == 'remove_button') {
993
    // If it's a temporary file we can safely remove it immediately, otherwise
994
    // it's up to the implementing module to clean up files that are in use.
995
    if ($element['#file'] && $element['#file']->status == 0) {
996
      file_delete($element['#file']);
997 85ad3d82 Assos Assos
    }
998 ca0757b9 Assos Assos
    // Update both $form_state['values'] and $form_state['input'] to reflect
999
    // that the file has been removed, so that the form is rebuilt correctly.
1000
    // $form_state['values'] must be updated in case additional submit handlers
1001
    // run, and for form building functions that run during the rebuild, such as
1002
    // when the media element is part of a field widget.
1003
    // $form_state['input'] must be updated so that media_file_value() has
1004
    // correct information during the rebuild.
1005
    $values_element = $element['#extended'] ? $element['fid'] : $element;
1006
    form_set_value($values_element, NULL, $form_state);
1007
    drupal_array_set_nested_value($form_state['input'], $values_element['#parents'], NULL);
1008 85ad3d82 Assos Assos
  }
1009 ca0757b9 Assos Assos
1010
  // Set the form to rebuild so that $form is correctly updated in response to
1011
  // processing the file removal. Since this function did not change $form_state
1012
  // if the upload button was clicked, a rebuild isn't necessary in that
1013
  // situation and setting $form_state['redirect'] to FALSE would suffice.
1014
  // However, we choose to always rebuild, to keep the form processing workflow
1015
  // consistent between the two buttons.
1016
  $form_state['rebuild'] = TRUE;
1017 85ad3d82 Assos Assos
}
1018
1019
/**
1020 ca0757b9 Assos Assos
 * Attaches any files that have been referenced by a media element.
1021
 *
1022
 * @param $element
1023
 *   The FAPI element whose files are being attached.
1024
 *
1025
 * @return
1026
 *   The file object representing the file that was attached, or FALSE if no
1027
 *   file was attached.
1028 85ad3d82 Assos Assos
 */
1029 ca0757b9 Assos Assos
function media_attach_file($element) {
1030
  $upload_name = implode('_', $element['#parents']);
1031
  if (empty($_POST['media'][$upload_name])) {
1032
    return FALSE;
1033
  }
1034 85ad3d82 Assos Assos
1035 ca0757b9 Assos Assos
  if (!$file = file_load($_POST['media'][$upload_name])) {
1036
    watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
1037
    form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title'])));
1038
    return FALSE;
1039 85ad3d82 Assos Assos
  }
1040
1041 ca0757b9 Assos Assos
  return $file;
1042
}
1043
1044
/**
1045
 * Returns HTML for a managed file element.
1046
 *
1047
 * @param $variables
1048
 *   An associative array containing:
1049
 *   - element: A render element representing the file.
1050
 *
1051
 * @ingroup themeable
1052
 */
1053
function theme_media_element($variables) {
1054
  $element = $variables['element'];
1055
1056
  $attributes = array();
1057
  if (isset($element['#id'])) {
1058
    $attributes['id'] = $element['#id'];
1059
  }
1060
  if (!empty($element['#attributes']['class'])) {
1061
    $attributes['class'] = (array) $element['#attributes']['class'];
1062
  }
1063
  $attributes['class'][] = 'form-media';
1064
1065
  // This wrapper is required to apply JS behaviors and CSS styling.
1066
  $output = '';
1067
  $output .= '<div' . drupal_attributes($attributes) . '>';
1068
  $output .= drupal_render_children($element);
1069
  $output .= '</div>';
1070
  return $output;
1071
}
1072
1073
/**
1074
 * #pre_render callback to hide display of the browse/attach or remove controls.
1075
 *
1076
 * Browse/attach controls are hidden when a file is already attached.
1077
 * Remove controls are hidden when there is no file attached. Controls are
1078
 * hidden here instead of in media_element_process(), because #access for these
1079
 * buttons depends on the media element's #value. See the documentation of
1080
 * form_builder() for more detailed information about the relationship between
1081
 * #process, #value, and #access.
1082
 *
1083
 * Because #access is set here, it affects display only and does not prevent
1084
 * JavaScript or other untrusted code from submitting the form as though access
1085
 * were enabled. The form processing functions for these elements should not
1086
 * assume that the buttons can't be "clicked" just because they are not
1087
 * displayed.
1088
 *
1089
 * @see media_element_process()
1090
 * @see form_builder()
1091
 */
1092
function media_element_pre_render($element) {
1093
  // If we already have a file, we don't want to show the browse and attach
1094
  // controls.
1095
  if (!empty($element['#value']['fid'])) {
1096
    $element['upload']['#access'] = FALSE;
1097
    $element['browse_button']['#access'] = FALSE;
1098
    $element['attach_button']['#access'] = FALSE;
1099
  }
1100
  // If we don't already have a file, there is nothing to remove.
1101
  else {
1102
    $element['remove_button']['#access'] = FALSE;
1103
  }
1104
  return $element;
1105 85ad3d82 Assos Assos
}
1106
1107
/**
1108 bf3c8457 Florent Torregrosa
 * Generates a thumbnail preview of a file.
1109 85ad3d82 Assos Assos
 *
1110 bf3c8457 Florent Torregrosa
 * Provides default fallback images if an image of the file cannot be generated.
1111 85ad3d82 Assos Assos
 *
1112
 * @param object $file
1113
 *   A Drupal file object.
1114 bf3c8457 Florent Torregrosa
 * @param boolean $link
1115
 *   (optional) Boolean indicating whether the thumbnail should be linked to the
1116
 *   file. Defaults to FALSE.
1117
 * @param string $view_mode
1118
 *   (optional) The view mode to use when rendering the thumbnail. Defaults to
1119
 *   'preview'.
1120 85ad3d82 Assos Assos
 *
1121
 * @return array
1122 bf3c8457 Florent Torregrosa
 *   Renderable array suitable for drupal_render() with the necessary classes
1123
 *   and CSS to support a media thumbnail.
1124 85ad3d82 Assos Assos
 */
1125 bf3c8457 Florent Torregrosa
function media_get_thumbnail_preview($file, $link = FALSE, $view_mode = 'preview') {
1126 85ad3d82 Assos Assos
  // If a file has an invalid type, allow file_view_file() to work.
1127
  if (!file_type_is_enabled($file->type)) {
1128
    $file->type = file_get_type($file);
1129
  }
1130 ca0757b9 Assos Assos
1131 bf3c8457 Florent Torregrosa
  $preview = file_view_file($file, $view_mode);
1132 85ad3d82 Assos Assos
  $preview['#show_names'] = TRUE;
1133
  $preview['#add_link'] = $link;
1134
  $preview['#theme_wrappers'][] = 'media_thumbnail';
1135 ca0757b9 Assos Assos
  $preview['#attached']['css'][] = drupal_get_path('module', 'media') . '/css/media.css';
1136 bf3c8457 Florent Torregrosa
1137 85ad3d82 Assos Assos
  return $preview;
1138
}
1139
1140
/**
1141
 * Check that the media is one of the selected types.
1142
 *
1143
 * @param object $file
1144
 *   A Drupal file object.
1145
 * @param array $types
1146
 *   An array of media type names
1147
 *
1148
 * @return array
1149
 *   If the file type is not allowed, it will contain an error message.
1150
 *
1151
 * @see hook_file_validate()
1152
 */
1153 ca0757b9 Assos Assos
function media_file_validate_types($file, $types) {
1154 85ad3d82 Assos Assos
  $errors = array();
1155 18596a08 Assos Assos
1156 02a0babc Assos Assos
  if (!function_exists('file_entity_get_filetype_candidates')) {
1157
    module_load_include('inc', 'file_entity', 'file_entity.pages');
1158
  }
1159 18596a08 Assos Assos
  $file_candidates = array_keys(file_entity_get_filetype_candidates($file));
1160
  if (!array_intersect($file_candidates, $types)) {
1161 85ad3d82 Assos Assos
    $errors[] = t('Only the following types of files are allowed to be uploaded: %types-allowed', array('%types-allowed' => implode(', ', $types)));
1162
  }
1163
1164
  return $errors;
1165
}
1166
1167
/**
1168
 * Implements hook_file_displays_alter().
1169
 */
1170
function media_file_displays_alter(&$displays, $file, $view_mode) {
1171
  if ($view_mode == 'preview' && empty($displays)) {
1172
    // We re in the media browser and this file has no formatters enabled.
1173
    // Instead of letting it go through theme_file_link(), pass it through
1174
    // theme_media_formatter_large_icon() to get our cool file icon instead.
1175
    $displays['file_field_media_large_icon'] = array(
1176
      'weight' => 0,
1177
      'status' => 1,
1178
      'settings' => NULL,
1179
    );
1180
  }
1181
1182 ca0757b9 Assos Assos
  // Alt and title are special.
1183
  // @see file_entity_file_load
1184
  $alt = variable_get('file_entity_alt', '[file:field_file_image_alt_text]');
1185
  $title = variable_get('file_entity_title', '[file:field_file_image_title_text]');
1186
1187
  $replace_options = array(
1188
    'clear' => TRUE,
1189
    'sanitize' => FALSE,
1190
  );
1191
1192
  // Load alt and title text from fields.
1193
  if (!empty($alt)) {
1194 fc3d89c3 Assos Assos
    $file->alt = decode_entities(token_replace($alt, array('file' => $file), $replace_options));
1195 ca0757b9 Assos Assos
  }
1196
  if (!empty($title)) {
1197 fc3d89c3 Assos Assos
    $file->title = decode_entities(token_replace($title, array('file' => $file), $replace_options));
1198 ca0757b9 Assos Assos
  }
1199 8fa03d70 Assos Assos
1200
  // Reduce memory footprint and response size in media browser.
1201
  $file->file_contents = '';
1202 85ad3d82 Assos Assos
}
1203
1204
/**
1205 ca0757b9 Assos Assos
 * For sanity in grammar.
1206
 *
1207
 * @see media_set_browser_params()
1208 85ad3d82 Assos Assos
 */
1209 ca0757b9 Assos Assos
function media_get_browser_params() {
1210
  return media_set_browser_params();
1211
}
1212 85ad3d82 Assos Assos
1213 ca0757b9 Assos Assos
/**
1214
 * Provides a singleton of the params passed to the media browser.
1215
 *
1216
 * This is useful in situations like form alters because callers can pass
1217
 * id="wysiywg_form" or whatever they want, and a form alter could pick this up.
1218
 * We may want to change the hook_media_browser_plugin_view() implementations to
1219
 * use this function instead of being passed params for consistency.
1220
 *
1221
 * It also offers a chance for some meddler to meddle with them.
1222
 *
1223
 * @see media_browser()
1224
 */
1225
function media_set_browser_params() {
1226
  $params = &drupal_static(__FUNCTION__, array());
1227
1228
  if (empty($params)) {
1229
    // Build out browser settings. Permissions- and security-related behaviors
1230
    // should not rely on these parameters, since they come from the HTTP query.
1231 388c412d Assos Assos
    // There are two ways of passing secure data:
1232
    // - Store the options in the 'cache_form' cache bin, using a random key
1233
    //   prefixed with 'media_options:'. Pass the random key in the 'options'
1234
    //   query argument.
1235
    // - Inject the options by altering the browser parameters.
1236
    //   @see hook_media_browser_params_alter()
1237
    $params = drupal_get_query_parameters();
1238
1239 b1ab1c0c Assos Assos
    // Filter out everything except a whitelist of known safe options.
1240
    $safe_options = array(
1241 02a0babc Assos Assos
      'enabledPlugins',
1242 b1ab1c0c Assos Assos
      'fid',
1243
      'id',
1244
      'multiselect',
1245 18596a08 Assos Assos
      'field',
1246 b1ab1c0c Assos Assos
      'options',
1247
      'plugins',
1248
      'render',
1249
      'types',
1250 d3a59f55 Assos Assos
      'render_multi_edit_form',
1251 388c412d Assos Assos
    );
1252 b1ab1c0c Assos Assos
    $params = array_intersect_key($params, array_flip($safe_options));
1253 388c412d Assos Assos
1254 18596a08 Assos Assos
    // If the cache is present, use its values instead of the GET parameters.
1255 388c412d Assos Assos
    if (!empty($params['options']) && is_string($params['options']) && $options = cache_get('media_options:' . $params['options'], 'cache_form')) {
1256 18596a08 Assos Assos
      $params = $options->data;
1257 388c412d Assos Assos
    }
1258 85ad3d82 Assos Assos
1259 ca0757b9 Assos Assos
    // Transform text 'true' and 'false' to actual booleans.
1260
    foreach ($params as $k => $v) {
1261
      if ($v === 'true') {
1262
        $params[$k] = TRUE;
1263
      }
1264
      elseif ($v === 'false') {
1265
        $params[$k] = FALSE;
1266
      }
1267
    }
1268 85ad3d82 Assos Assos
1269 8fa03d70 Assos Assos
    media_array_walk_recursive($params);
1270 ca0757b9 Assos Assos
1271 388c412d Assos Assos
    // Provide some default parameters.
1272
    $params += array(
1273
      'types' => array(),
1274
      'multiselect' => FALSE,
1275
    );
1276
1277 ca0757b9 Assos Assos
    // Allow modules to alter the parameters.
1278
    drupal_alter('media_browser_params', $params);
1279 85ad3d82 Assos Assos
  }
1280
1281 ca0757b9 Assos Assos
  return $params;
1282 85ad3d82 Assos Assos
}
1283
1284
/**
1285
 * Implements hook_ctools_plugin_api().
1286
 *
1287
 * Lets CTools know which plugin APIs are implemented by Media module.
1288
 */
1289
function media_ctools_plugin_api($module, $api) {
1290
  if ($module == 'file_entity' && $api == 'file_default_displays') {
1291
    return array(
1292
      'version' => 1,
1293
    );
1294
  }
1295
}
1296
1297
/**
1298
 * Implements hook_form_FORM_ID_alter().
1299
 *
1300
 * This alter enhances the default admin/content/file page, addding JS and CSS.
1301
 * It also makes modifications to the thumbnail view by replacing the existing
1302
 * checkboxes and table with thumbnails.
1303
 */
1304
function media_form_file_entity_admin_file_alter(&$form, $form_state) {
1305
  if (!empty($form_state['values']['operation'])) {
1306
    // The form is being rebuilt because an operation requiring confirmation
1307
    // We don't want to be messing with it in this case.
1308
    return;
1309
  }
1310
1311
  // Add the "Add file" local action, and notify users if they have files
1312
  // selected and they try to switch between the "Thumbnail" and "List" local
1313
  // tasks.
1314
  $path = drupal_get_path('module', 'media');
1315
  $form['#attributes']['class'][] = 'file-entity-admin-file-form';
1316
  $form['#attached']['js'][] = $path . '/js/media.admin.js';
1317
  $form['#attached']['css'][] = $path . '/css/media.css';
1318
1319
  // By default, this form contains a table select element called "files". For
1320
  // the 'thumbnails' tab, Media generates a thumbnail for each file and
1321
  // replaces the tableselect with a grid of thumbnails.
1322
  if (arg(3) == 'thumbnails') {
1323 ca0757b9 Assos Assos
    if (empty($form['admin']['files']['#options'])) {
1324 85ad3d82 Assos Assos
      // Display empty text if there are no files.
1325
      $form['admin']['files'] = array(
1326 ca0757b9 Assos Assos
        '#markup' => '<p>' . $form['admin']['files']['#empty'] . '</p>',
1327 85ad3d82 Assos Assos
      );
1328
    }
1329
    else {
1330
      $files = file_load_multiple(array_keys($form['admin']['files']['#options']));
1331
1332
      $form['admin']['files'] = array(
1333
        '#tree' => TRUE,
1334
        '#prefix' => '<div class="media-display-thumbnails media-clear clearfix"><ul id="media-browser-library-list" class="media-list-thumbnails">',
1335
        '#suffix' => '</ul></div>',
1336
      );
1337
1338
      foreach ($files as $file) {
1339
        $preview = media_get_thumbnail_preview($file, TRUE);
1340
        $form['admin']['files'][$file->fid] = array(
1341
          '#type' => 'checkbox',
1342
          '#title' => '',
1343
          '#prefix' => '<li>' . drupal_render($preview),
1344
          '#suffix' => '</li>',
1345
        );
1346
      }
1347
    }
1348
  }
1349
}
1350
1351
/**
1352
 * Implements hook_views_api().
1353
 */
1354
function media_views_api() {
1355
  return array(
1356
    'api' => 3,
1357
    'path' => drupal_get_path('module', 'media'),
1358
  );
1359
}
1360
1361
/**
1362
 * Implements hook_views_default_views().
1363
 */
1364
function media_views_default_views() {
1365
  return media_load_all_exports('media', 'views', 'view.inc', 'view');
1366
}
1367
1368
/**
1369
 * Fetches an array of exportables from files.
1370
 *
1371
 * @param string $module
1372
 *   The module invoking this request. (Can be called by other modules.)
1373
 * @param string $directory
1374
 *   The subdirectory in the custom module.
1375
 * @param string $extension
1376
 *   The file extension.
1377
 * @param string $name
1378
 *   The name of the variable found in each file. Defaults to the same as
1379
 *   $extension.
1380
 *
1381
 * @return array
1382
 *   Array of $name objects.
1383
 */
1384
function media_load_all_exports($module, $directory, $extension, $name = NULL) {
1385
  if (!$name) {
1386
    $name = $extension;
1387
  }
1388
1389
  $return = array();
1390
  // Find all the files in the directory with the correct extension.
1391
  $files = file_scan_directory(drupal_get_path('module', $module) . "/$directory", "/.$extension/");
1392
  foreach ($files as $path => $file) {
1393
    require $path;
1394
    if (isset($$name)) {
1395
      $return[$$name->name] = $$name;
1396
    }
1397
  }
1398
1399
  return $return;
1400
}
1401
1402
/**
1403
 * Returns metadata describing Media browser plugins.
1404
 *
1405 bf3c8457 Florent Torregrosa
 * @return
1406
 *   An associative array of plugin information, keyed by plugin.
1407
 *
1408 85ad3d82 Assos Assos
 * @see hook_media_browser_plugin_info()
1409
 * @see hook_media_browser_plugin_info_alter()
1410
 */
1411
function media_get_browser_plugin_info() {
1412
  $info = &drupal_static(__FUNCTION__);
1413
1414
  if (!isset($info)) {
1415 bf3c8457 Florent Torregrosa
    $info = module_invoke_all('media_browser_plugin_info');
1416
    drupal_alter('media_browser_plugin_info', $info);
1417 85ad3d82 Assos Assos
  }
1418
1419
  return $info;
1420
}
1421
1422 ca0757b9 Assos Assos
/**
1423
 * Gets the MIME type mapped to a given extension.
1424
 *
1425
 * @param string $extension
1426
 *   A file extension.
1427
 *
1428
 * @return string
1429
 *   The MIME type associated with the extension or FALSE if the extension does
1430
 *   not have an associated MIME type.
1431
 *
1432
 * @see file_mimetype_mapping()
1433
 */
1434
function media_get_extension_mimetype($extension) {
1435
  include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
1436
  $mimetype_mappings = file_mimetype_mapping();
1437
1438
  if (isset($mimetype_mappings['extensions'][$extension])) {
1439
    $id = $mimetype_mappings['extensions'][$extension];
1440
    return $mimetype_mappings['mimetypes'][$id];
1441
  }
1442
  else {
1443
    return FALSE;
1444
  }
1445
}
1446
1447 85ad3d82 Assos Assos
/**
1448
 * Helper function to get a list of local stream wrappers.
1449
 */
1450
function media_get_local_stream_wrappers() {
1451
  return file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL);
1452
}
1453
1454
/**
1455
 * Helper function to get a list of remote stream wrappers.
1456
 */
1457
function media_get_remote_stream_wrappers() {
1458
  $wrappers = file_get_stream_wrappers();
1459
  $wrappers = array_diff_key($wrappers, file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL));
1460
  $wrappers = array_diff_key($wrappers, file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_HIDDEN));
1461
  return $wrappers;
1462
}