Projet

Général

Profil

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

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

1
<?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
/* ***************************************** */
12
/* INCLUDES                                  */
13
/* ***************************************** */
14

    
15
// Code relating to using media as a field.
16
require_once dirname(__FILE__) . '/includes/media.fields.inc';
17

    
18
 // Functions for working with [[inline tags]] and wysiwyg editors.
19
require_once dirname(__FILE__) . '/includes/media.filter.inc';
20

    
21
/* ***************************************** */
22
/* Hook Implementations                      */
23
/* ***************************************** */
24

    
25
/**
26
 * Implements hook_hook_info().
27
 */
28
function media_hook_info() {
29
  $hooks = array(
30
    'media_parse',
31
    'media_browser_plugin_info',
32
    'media_browser_plugin_info_alter',
33
    'media_browser_plugins_alter',
34
    'media_browser_params_alter',
35
    'media_wysiwyg_allowed_view_modes_alter',
36
    'media_format_form_prepare_alter',
37
    'media_token_to_markup_alter',
38
    'query_media_browser_alter',
39
  );
40

    
41
  return array_fill_keys($hooks, array('group' => 'media'));
42
}
43

    
44
/**
45
 * Implements hook_help().
46
 */
47
function media_help($path, $arg) {
48
  switch ($path) {
49
    case 'admin/help#media':
50
      $output = '';
51
      $output .= '<h3>' . t('About') . '</h3>';
52
      $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>';
53
      $output .= '<h3>' . t('Uses') . '</h3>';
54
      $output .= '<dl>';
55
      $output .= '<dt>' . t('Media Repository') . '</dt>';
56
      $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>';
57
      $output .= '<dt>' . t('Attaching media assets to content types') . '</dt>';
58
      $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>';
59
      $output .= '<dt>' . t('Using media assets in WYSIWYG') . '</dt>';
60
      $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:');
61
      $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>';
62
      $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>';
63
      return $output;
64
  }
65
}
66

    
67
/**
68
 * Implements hook_entity_info_alter().
69
 */
70
function media_entity_info_alter(&$entity_info) {
71
  // For sites that updated from Media 1.x, continue to provide these deprecated
72
  // view modes.
73
  // @see http://drupal.org/node/1051090
74
  if (variable_get('media__show_deprecated_view_modes', FALSE)) {
75
    $entity_info['file']['view modes'] += array(
76
      'media_link' => array(
77
        'label' => t('Link'),
78
        'custom settings' => TRUE,
79
      ),
80
      'media_original' => array(
81
        'label' => t('Original'),
82
        'custom settings' => TRUE,
83
      ),
84
    );
85
  }
86

    
87
  if (module_exists('entity_translation')) {
88
    $entity_info['file']['translation']['entity_translation']['class'] = 'MediaEntityTranslationHandler';
89
    $entity_info['file']['translation']['entity_translation']['path schemes']['media'] = array('edit path' => 'media/%file/edit/%ctools_js');
90
  }
91
}
92

    
93
/**
94
 * Access callback for files. This function is deprecated.
95
 *
96
 * @todo Completely remove this function in favor of file_entity_access after
97
 * a few releases, to ensure rest of contib catches up.
98
 */
99
function media_access($op, $account = NULL) {
100
  return user_access('administer files', $account);
101
}
102

    
103
/**
104
 * Implements hook_menu().
105
 */
106
function media_menu() {
107
  // For managing different types of media and the fields associated with them.
108
  $items['admin/config/media/browser'] = array(
109
    'title' => 'Media browser settings',
110
    'description' => 'Configure the behavior and display of the media browser.',
111
    'page callback' => 'drupal_get_form',
112
    'page arguments' => array('media_admin_config_browser'),
113
    'access arguments' => array('administer media browser'),
114
    'file' => 'includes/media.admin.inc',
115
  );
116

    
117
  // Administrative screens for managing media.
118
  $items['admin/content/file/thumbnails'] = array(
119
    'title' => 'Thumbnails',
120
    'description' => 'Manage files used on your site.',
121
    'page callback' => 'drupal_get_form',
122
    'page arguments' => array('file_entity_admin_file'),
123
    'access arguments' => array('administer files'),
124
    'type' => MENU_LOCAL_TASK,
125
    'file' => 'file_entity.admin.inc',
126
    'file path' => drupal_get_path('module', 'file_entity'),
127
    'weight' => 10,
128
  );
129

    
130
  // Used to import files from a local filesystem into Drupal.
131
  $items['admin/content/file/import'] = array(
132
    'title' => 'Import files',
133
    'description' => 'Import files into your media library.',
134
    'page callback' => 'drupal_get_form',
135
    'page arguments' => array('media_import'),
136
    'access arguments' => array('import media'),
137
    'type' => MENU_LOCAL_ACTION,
138
    'file' => 'includes/media.admin.inc',
139
    'weight' => 10,
140
  );
141
  $items['admin/content/file/thumbnails/import'] = $items['admin/content/file/import'];
142

    
143
  $items['media/browser'] = array(
144
    'title' => 'Media browser',
145
    'description' => 'Media Browser for picking media and uploading new media',
146
    'page callback' => 'media_browser',
147
    'access callback' => 'file_entity_access',
148
    'access arguments' => array('create'),
149
    'type' => MENU_CALLBACK,
150
    'file' => 'includes/media.browser.inc',
151
    'theme callback' => 'media_dialog_get_theme_name',
152
  );
153

    
154
  // A testbed to try out the media browser with different launch commands.
155
  $items['media/browser/testbed'] = array(
156
    'title' => 'Media Browser test',
157
    'description' => 'Make it easier to test media browser',
158
    'page callback' => 'drupal_get_form',
159
    'page arguments' => array('media_browser_testbed'),
160
    'access arguments' => array('administer files'),
161
    'type' => MENU_CALLBACK,
162
    'file' => 'includes/media.browser.inc',
163
  );
164

    
165
  $items['media/%file/format-form'] = array(
166
    'title' => 'Style selector',
167
    'description' => 'Choose a format for a piece of media',
168
    'page callback' => 'drupal_get_form',
169
    'page arguments' => array('media_format_form', 1),
170
    'access callback' => 'file_entity_access',
171
    'access arguments' => array('view', 1),
172
    'file' => 'includes/media.filter.inc',
173
    'theme callback' => 'media_dialog_get_theme_name',
174
    'type' => MENU_CALLBACK,
175
  );
176

    
177
  if (module_exists('multiform')) {
178
    // @todo Investigate passing file IDs in query string rather than a menu
179
    // argument and then deprecate media_multi_load().
180
    $items['admin/content/file/edit-multiple/%media_multi'] = array(
181
      'title' => 'Edit multiple files',
182
      'page callback' => 'media_file_page_edit_multiple',
183
      'page arguments' => array(4),
184
      'access callback' =>  '_media_file_entity_access_recursive',
185
      'access arguments' => array(4, 'update'),
186
      'file' => 'includes/media.pages.inc',
187
    );
188
  }
189

    
190
  // We could re-use the file/%file/edit path for the modal callback, but
191
  // it is just easier to use our own namespace here.
192
  $items['media/%file/edit/%ctools_js'] = array(
193
    'title' => 'Edit',
194
    'page callback' => 'drupal_get_form',
195
    'page arguments' => array('media_file_edit_modal', 1, 3),
196
    'access callback' => 'file_entity_access',
197
    'access arguments' => array('update', 1),
198
    'file' => 'includes/media.pages.inc',
199
    'type' => MENU_CALLBACK,
200
  );
201

    
202
  // Upgrade interface for old file types.
203
  $items['admin/structure/file-types/upgrade'] = array(
204
    'title' => 'Upgrade types',
205
    'page callback' => 'drupal_get_form',
206
    'page arguments' => array('media_upgrade_file_types'),
207
    'access arguments' => array('administer file types'),
208
    'file' => 'includes/media.pages.inc',
209
    'type' => MENU_CALLBACK,
210
  );
211
  $items['admin/structure/file-types/upgrade/confirm'] = array(
212
    'title' => 'Upgrade types',
213
    'page callback' => 'drupal_get_form',
214
    'page arguments' => array('media_upgrade_file_types_confirm'),
215
    'access arguments' => array('administer file types'),
216
    'file' => 'includes/media.pages.inc',
217
    'type' => MENU_CALLBACK,
218
  );
219

    
220
  return $items;
221
}
222

    
223
/**
224
 * Implements hook_menu_local_tasks_alter().
225
 */
226
function media_menu_local_tasks_alter(&$data, $router_item, $root_path) {
227
  // Add action link to 'file/add' on 'admin/content/file/thumbnails' page.
228
  if ($root_path == 'admin/content/file/thumbnails') {
229
    $item = menu_get_item('file/add');
230
    if (!empty($item['access'])) {
231
      $data['actions']['output'][] = array(
232
        '#theme' => 'menu_local_action',
233
        '#link' => $item,
234
        '#weight' => $item['weight'],
235
      );
236
    }
237
  }
238
}
239

    
240
/**
241
 * Implements hook_admin_paths().
242
 */
243
function media_admin_paths() {
244
  $paths['media/*/edit/*'] = TRUE;
245

    
246
  // If the media browser theme is set to the admin theme, ensure it gets set
247
  // as an admin path as well.
248
  $dialog_theme = variable_get('media__dialog_theme', '');
249
  if (empty($dialog_theme) || $dialog_theme == variable_get('admin_theme')) {
250
    $paths['media/browser'] = TRUE;
251
    $paths['media/browser/*'] = TRUE;
252
  }
253

    
254
  return $paths;
255
}
256

    
257
/**
258
 * Implements hook_permission().
259
 */
260
function media_permission() {
261
  return array(
262
    'administer media browser' => array(
263
      'title' => t('Administer media browser'),
264
      'description' => t('Access media browser settings.'),
265
    ),
266
    'import media' => array(
267
      'title' => t('Import media files from the local filesystem'),
268
      'description' => t('Simple file importer'),
269
    ),
270
  );
271
}
272

    
273
/**
274
 * Implements hook_theme().
275
 */
276
function media_theme() {
277
  return array(
278
    // The default media file list form element.
279
    'media_file_list' => array(
280
      'variables' => array('element' => NULL),
281
    ),
282

    
283
    // A preview of the uploaded file.
284
    'media_thumbnail' => array(
285
      'render element' => 'element',
286
      'file' => 'includes/media.theme.inc',
287
    ),
288

    
289
    // Dialog page.
290
    'media_dialog_page' => array(
291
      'render element' => 'page',
292
      'template' => 'templates/media-dialog-page',
293
      'file' => 'includes/media.theme.inc',
294
    ),
295

    
296
    // Media form API element type.
297
    'media_element' => array(
298
      'render element' => 'element',
299
      'file' => 'includes/media.theme.inc',
300
    ),
301

    
302
    // Display a file as a large icon.
303
    'media_formatter_large_icon' => array(
304
      'variables' => array('file' => NULL, 'attributes' => array(), 'style_name' => 'media_thumbnail'),
305
      'file' => 'includes/media.theme.inc',
306
    ),
307
  );
308
}
309

    
310
/**
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
  // Check if there are file types that need migration and display a message
350
  // to user if so.
351
  $menu = menu_get_item();
352
  if (variable_get('media__display_types_migration_mess', TRUE) && $menu['path'] == 'admin/structure/file-types') {
353
    if ($migratable_types = _media_get_migratable_file_types()) {
354
      drupal_set_message(t('There are disabled/deleted file types that can be migrated to their new alternatives. Visit <a href="!url">migration page</a> to get more information.', array('!url' => url('admin/structure/file-types/upgrade'))));
355
    }
356
  }
357
}
358

    
359
/**
360
 * Implements hook_element_info_alter().
361
 */
362
function media_element_info_alter(&$types) {
363
  $types['text_format']['#pre_render'][] = 'media_pre_render_text_format';
364
}
365

    
366
/**
367
 * Implements hook_file_operations().
368
 */
369
function media_file_operations() {
370
  $operations = array();
371

    
372
  // If the multiform module is not installed, do not show this option.
373
  if (module_exists('multiform')) {
374
    $operations['edit_multiple'] = array(
375
      'label' => t('Edit selected files'),
376
      'callback' => 'media_file_operation_edit_multiple',
377
    );
378
  }
379

    
380
  return $operations;
381
}
382

    
383
/**
384
 * Return a URL for editing an files.
385
 *
386
 * Works with an array of fids or a single fid.
387
 *
388
 * @param mixed $fids
389
 *   An array of file IDs or a single file ID.
390
 */
391
function media_file_edit_url($fids) {
392
  if (!is_array($fids)) {
393
    $fids = array($fids);
394
  }
395

    
396
  if (count($fids) > 1) {
397
    return 'admin/content/file/edit-multiple/' . implode(' ', $fids);
398
  }
399
  else {
400
    return 'file/' . reset($fids) . '/edit';
401
  }
402
}
403

    
404
/**
405
 * Callback for the edit operation.
406
 *
407
 * Redirects the user to the edit multiple files page.
408
 *
409
 * @param array $fids
410
 *   An array of file IDs.
411
 *
412
 * @see media_file_page_edit_multiple()
413
 */
414
function media_file_operation_edit_multiple($fids) {
415
  // The thumbnail browser returns TRUE/FALSE for each item, so use array keys.
416
  $fids = array_keys(array_filter($fids));
417
  drupal_goto(media_file_edit_url($fids), array('query' => drupal_get_destination()));
418
}
419

    
420
/**
421
 * Implements hook_forms().
422
 */
423
function media_forms($form_id, $args) {
424
  $forms = array();
425
  // To support the multiedit form, each form has to have a unique ID.
426
  // So we name all the forms media_edit_N where the first requested form is
427
  // media_edit_0, 2nd is media_edit_1, etc.
428
  module_load_include('inc', 'file_entity', 'file_entity.pages');
429
  if ($form_id != 'media_edit' && (strpos($form_id, 'media_edit') === 0)) {
430
    $forms[$form_id] = array(
431
      'callback' => 'file_entity_edit',
432
    );
433
  }
434
  return $forms;
435
}
436

    
437
/**
438
 * Implements hook_form_FIELD_UI_FIELD_SETTINGS_FORM_alter().
439
 *
440
 * @todo: Respect field settings in 7.x-2.x and handle them in the media widget
441
 * UI.
442
 */
443
function media_form_field_ui_field_settings_form_alter(&$form, &$form_state) {
444
  // On file fields that use the media widget we need remove specific fields.
445
  if ($form['field']['type']['#value'] == 'file') {
446
    $fields = field_info_instances($form['#entity_type'], $form['#bundle']);
447
    if ($fields[$form['field']['field_name']['#value']]['widget']['type'] == 'media_generic') {
448
      $form['field']['settings']['display_field']['#access'] = FALSE;
449
      $form['field']['settings']['display_default']['#access'] = FALSE;
450
    }
451
  }
452
}
453

    
454
/**
455
 * Implements hook_form_FIELD_UI_FIELD_EDIT_FORM_alter().
456
 *
457
 * @todo: Respect field settings in 7.x-2.x and handle them in the media widget
458
 * UI.
459
 */
460
function media_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
461
  // On file fields that use the media widget we need remove specific fields.
462
  if ($form['#field']['type'] == 'file' && $form['instance']['widget']['type']['#value'] == 'media_generic') {
463
    $form['field']['settings']['display_field']['#access'] = FALSE;
464
    $form['field']['settings']['display_default']['#access'] = FALSE;
465
    $form['instance']['settings']['description_field']['#access'] = FALSE;
466
    $form['instance']['settings']['file_extensions']['#title'] = t('Allowed file extensions for uploaded files');
467
    $form['instance']['settings']['file_extensions']['#maxlength'] = 255;
468
  }
469

    
470
  // On image fields using the media widget we remove the alt/title fields.
471
  if ($form['#field']['type'] == 'image' && $form['instance']['widget']['type']['#value'] == 'media_generic') {
472
    $form['instance']['settings']['alt_field']['#access'] = FALSE;
473
    $form['instance']['settings']['title_field']['#access'] = FALSE;
474
    $form['instance']['settings']['file_extensions']['#title'] = t('Allowed file extensions for uploaded files');
475
    // Do not increase maxlength of file extensions for image fields, since
476
    // presumably they will not need a long list of extensions.
477
  }
478
  if ($form['#instance']['entity_type'] == 'file') {
479
    $form['instance']['settings']['wysiwyg_override'] = array(
480
      '#type' => 'checkbox',
481
      '#title' => t('Override in WYSIWYG'),
482
      '#description' => t('If checked, then this field may be overridden in the WYSIWYG editor.'),
483
      '#default_value' => isset($form['#instance']['settings']['wysiwyg_override']) ? $form['#instance']['settings']['wysiwyg_override'] : TRUE,
484
    );
485
  }
486
}
487

    
488
/**
489
 * Implements hook_form_FORM_ID_alter().
490
 */
491
function media_form_file_entity_edit_alter(&$form, &$form_state) {
492
  // Make adjustments to the file edit form when used in a CTools modal.
493
  if (!empty($form_state['ajax'])) {
494
    // Remove the preview and the delete button.
495
    $form['preview']['#access'] = FALSE;
496
    $form['actions']['delete']['#access'] = FALSE;
497

    
498
    // Convert the cancel link to a button which triggers a modal close.
499
    $form['actions']['cancel']['#attributes']['class'][] = 'button';
500
    $form['actions']['cancel']['#attributes']['class'][] = 'button-no';
501
    $form['actions']['cancel']['#attributes']['class'][] = 'ctools-close-modal';
502
  }
503
}
504

    
505
/**
506
 * Implements hook_form_alter().
507
 */
508
function media_form_alter(&$form, &$form_state, $form_id) {
509
  // If we're in the media browser, set the #media_browser key to true
510
  // so that if an ajax request gets sent to a different path, the form
511
  // still uses the media_browser_form_submit callback.
512
  if (current_path() == 'media/browser' && $form_id != 'views_exposed_form') {
513
    $form_state['#media_browser'] = TRUE;
514
  }
515

    
516
  // If the #media_browser key isset and is true we are using the browser
517
  // popup, so add the media_browser submit handler.
518
  if (!empty($form_state['#media_browser'])) {
519
    $form['#submit'][] = 'media_browser_form_submit';
520
  }
521
}
522

    
523
/**
524
 * Submit handler; direction form submissions in the media browser.
525
 */
526
function media_browser_form_submit($form, &$form_state) {
527
  $url = NULL;
528
  $parameters = array();
529

    
530
  // Single upload.
531
  if (!empty($form_state['file'])) {
532
    $file = $form_state['file'];
533
    $url = 'media/browser';
534
    $parameters = array('query' => array('render' => 'media-popup', 'fid' => $file->fid));
535
  }
536

    
537
  // Multi upload.
538
  if (!empty($form_state['files'])) {
539
    $files = $form_state['files'];
540
    $url = 'media/browser';
541
    $parameters = array('query' => array('render' => 'media-popup', 'fid' => array_keys($files)));
542
  }
543

    
544
  // If $url is set, we had some sort of upload, so redirect the form.
545
  if (!empty($url)) {
546
    $form_state['redirect'] = array($url, $parameters);
547
  }
548
}
549

    
550
/**
551
 * Implements hook_form_FORM_ID_alter().
552
 */
553
function media_form_wysiwyg_profile_form_alter(&$form, &$form_state) {
554
  // Add warnings if the media filter is disabled for the WYSIWYG's text format.
555
  $form['buttons']['drupal']['media']['#element_validate'][] = 'media_wysiwyg_button_element_validate';
556
  $form['buttons']['drupal']['media']['#after_build'][] = 'media_wysiwyg_button_element_validate';
557
  form_load_include($form_state, 'inc', 'media', 'wysiwyg_plugins/media');
558
}
559

    
560
/**
561
 * Access callback for the media-multi form.
562
 *
563
 * @param $files
564
 *   An array of files being editing on the multiform.
565
 * @param $op
566
 *   A string containing the operation requested, such as 'update'.
567
 * @return
568
 *   TRUE if the current user has access to edit all of the files, otherwise FALSE.
569
 */
570
function _media_file_entity_access_recursive($files, $op) {
571
  // Check that the current user can access each file.
572
  if (!empty($files)) {
573
    foreach ($files as $file) {
574
      if (!file_entity_access($op, $file)) {
575
        return FALSE;
576
      }
577
    }
578
    return TRUE;
579
  }
580
  return FALSE;
581
}
582

    
583
/* ***************************************** */
584
/* API FUNCTIONS                             */
585
/* ***************************************** */
586

    
587
/**
588
 * Load callback for %media_multi placeholder in menu paths.
589
 *
590
 * @param string $fids
591
 *   Separated by space (e.g., "3 6 12 99"). This often appears as "+" within
592
 *   URLs (e.g., "3+6+12+99"), but Drupal automatically decodes paths when
593
 *   intializing $_GET['q'].
594
 *
595
 * @return array
596
 *   An array of corresponding file entities.
597
 */
598
function media_multi_load($fids) {
599
  return file_load_multiple(explode(' ', $fids));
600
}
601

    
602
/* ***************************************** */
603
/* Callbacks                                 */
604
/* ***************************************** */
605

    
606
/**
607
 * Process callback for the media_browser element.
608
 */
609
function media_file_list_element_process($element, $edit, $form_state, $form) {
610
  $element['list'] = array(
611
    '#type'     => 'select',
612
    '#options'  => $element['#options'],
613
    '#size'     => variable_get('media__file_list_size', 10),
614
  );
615
  return $element;
616
}
617

    
618
/**
619
 * Implements hook_library().
620
 */
621
function media_library() {
622
  $path = drupal_get_path('module', 'media');
623
  $info = system_get_info('module', 'media');
624

    
625
  $common = array(
626
    'website' => 'http://drupal.org/project/media',
627
    'version' => !empty($info['version']) ? $info['version'] : '7.x-2.x',
628
  );
629

    
630
  // Contains libraries common to other media modules.
631
  $libraries['media_base'] = array(
632
    'title' => 'Media base',
633
    'js' => array(
634
      $path . '/js/media.core.js' => array('group' => JS_LIBRARY, 'weight' => -5),
635
      $path . '/js/util/json2.js' => array('group' => JS_LIBRARY),
636
      $path . '/js/util/ba-debug.min.js' => array('group' => JS_LIBRARY),
637
    ),
638
    'css' => array(
639
      $path . '/css/media.css',
640
    ),
641
  );
642

    
643
  // Includes resources needed to launch the media browser.  Should be included
644
  // on pages where the media browser needs to be launched from.
645
  $libraries['media_browser'] = array(
646
    'title' => 'Media Browser popup libraries',
647
    'js' => array(
648
      $path . '/js/media.popups.js' => array('group' => JS_DEFAULT),
649
    ),
650
    'dependencies' => array(
651
      array('system', 'ui.resizable'),
652
      array('system', 'ui.draggable'),
653
      array('system', 'ui.dialog'),
654
      array('media', 'media_base'),
655
    ),
656
  );
657

    
658
  // Resources needed in the media browser itself.
659
  $libraries['media_browser_page'] = array(
660
    'title' => 'Media browser',
661
    'js' => array(
662
      $path . '/js/media.browser.js'  => array('group' => JS_DEFAULT),
663
    ),
664
    'dependencies' => array(
665
      array('system', 'ui.tabs'),
666
      array('system', 'ui.draggable'),
667
      array('system', 'ui.dialog'),
668
      array('media', 'media_base'),
669
    ),
670
  );
671

    
672
  foreach ($libraries as &$library) {
673
    $library += $common;
674
  }
675
  return $libraries;
676
}
677

    
678
/**
679
 * Theme callback used to identify when we are in a popup dialog.
680
 *
681
 * Generally the default theme will look terrible in the media browser. This
682
 * will default to the administration theme, unless set otherwise.
683
 */
684
function media_dialog_get_theme_name() {
685
  return variable_get('media__dialog_theme', variable_get('admin_theme'));
686
}
687

    
688
/**
689
 * This will parse a url or embedded code into a unique URI.
690
 *
691
 * The function will call all modules implementing hook_media_parse($url),
692
 * which should return either a string containing a parsed URI or NULL.
693
 *
694
 * @NOTE The implementing modules may throw an error, which will not be caught
695
 * here; it's up to the calling function to catch any thrown errors.
696
 *
697
 * @NOTE In emfield, we originally also accepted an array of regex patterns
698
 * to match against. However, that module used a registration for providers,
699
 * and simply stored the match in the database keyed to the provider object.
700
 * However, other than the stream wrappers, there is currently no formal
701
 * registration for media handling. Additionally, few, if any, stream wrappers
702
 * will choose to store a straight match from the parsed URL directly into
703
 * the URI. Thus, we leave both the matching and the final URI result to the
704
 * implementing module in this implementation.
705
 *
706
 * An alternative might be to do the regex pattern matching here, and pass a
707
 * successful match back to the implementing module. However, that would
708
 * require either an overloaded function or a new hook, which seems like more
709
 * overhead than it's worth at this point.
710
 *
711
 * @TODO Once hook_module_implements_alter() is in core (see the issue at
712
 * http://drupal.org/node/692950) we may want to implement media_media_parse()
713
 * to ensure we were passed a valid URL, rather than an unsupported or
714
 * malformed embed code that wasn't caught earlier. It will needed to be
715
 * weighted so it's called after all other streams have a go, as the fallback,
716
 * and will need to throw an error.
717
 *
718
 * @param string $url
719
 *   The original URL or embed code to parse.
720
 *
721
 * @return string
722
 *   The unique URI for the file, based on its stream wrapper, or NULL.
723
 *
724
 * @see media_parse_to_file()
725
 * @see media_add_from_url_validate()
726
 */
727
function media_parse_to_uri($url) {
728
  // Trim any whitespace before parsing.
729
  $url = trim($url);
730
  foreach (module_implements('media_parse') as $module) {
731
    $success = module_invoke($module, 'media_parse', $url);
732
    if (isset($success)) {
733
      return $success;
734
    }
735
  }
736
}
737

    
738
/**
739
 * Parse a URL or embed code and return a file object.
740
 *
741
 * If a remote stream doesn't claim the parsed URL in media_parse_to_uri(),
742
 * then we'll copy the file locally.
743
 *
744
 * @NOTE The implementing modules may throw an error, which will not be caught
745
 * here; it's up to the calling function to catch any thrown errors.
746
 *
747
 * @see media_parse_to_uri()
748
 * @see media_add_from_url_submit()
749
 */
750
function media_parse_to_file($url) {
751
  try {
752
    $uri = media_parse_to_uri($url);
753
  }
754
  catch (Exception $e) {
755
    // Pass the error along.
756
    throw $e;
757
    return;
758
  }
759

    
760
  if (isset($uri)) {
761
    // Attempt to load an existing file from the unique URI.
762
    $select = db_select('file_managed', 'f')
763
    ->extend('PagerDefault')
764
    ->fields('f', array('fid'))
765
    ->condition('uri', $uri);
766

    
767
    $fid = $select->execute()->fetchCol();
768
    if (!empty($fid)) {
769
      $file = file_load(array_pop($fid));
770
      return $file;
771
    }
772
  }
773

    
774
  if (isset($uri)) {
775
    // The URL was successfully parsed to a URI, but does not yet have an
776
    // associated file: save it!
777
    $file = file_uri_to_object($uri);
778
    file_save($file);
779
  }
780
  else {
781
    // The URL wasn't parsed. We'll try to save a remote file.
782
    // Copy to temporary first.
783
    $source_uri = file_stream_wrapper_uri_normalize('temporary://' . basename($url));
784
    if (!@copy(@$url, $source_uri)) {
785
      throw new Exception('Unable to add file ' . $url);
786
      return;
787
    }
788
    $source_file = file_uri_to_object($source_uri);
789
    $scheme = variable_get('file_default_scheme', 'public') . '://';
790
    $uri = file_stream_wrapper_uri_normalize($scheme . $source_file->filename);
791
    // Now to its new home.
792
    $file = file_move($source_file, $uri, FILE_EXISTS_RENAME);
793
  }
794

    
795
  return $file;
796
}
797

    
798
/**
799
 * Utility function to recursively run check_plain on an array.
800
 *
801
 * @todo There is probably something in core I am not aware of that does this.
802
 */
803
function media_recursive_check_plain(&$value, $key) {
804
  $value = check_plain($value);
805
}
806

    
807
/**
808
 * Implements hook_element_info().
809
 */
810
function media_element_info() {
811
  $types = array();
812
  $types['media'] = array(
813
    '#input' => TRUE,
814
    '#process' => array('media_element_process'),
815
    // '#value_callback' => 'media_element_value',
816
    '#element_validate' => array('media_element_validate'),
817
    '#theme_wrappers' => array('container'),
818
    '#progress_indicator' => 'throbber',
819
    '#extended' => FALSE,
820
    '#required' => FALSE,
821
    '#media_options' => array(
822
      'global' => array(
823
        // Example: array('image', 'audio');
824
        'types' => array(),
825
        // Example: array('http', 'ftp', 'flickr');
826
        'schemes' => array(),
827
      ),
828
    ),
829
    '#attributes' => array(
830
      'class' => array('media-widget', 'form-item'),
831
    ),
832
    '#attached' => array(
833
      'library' => array(
834
        array('media', 'media_browser'),
835
      ),
836
    ),
837
  );
838
  return $types;
839
}
840

    
841
/**
842
 * Process callback for the media form element.
843
 */
844
function media_element_process(&$element, &$form_state, $form) {
845
  $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0;
846
  $file = $fid ? file_load($fid) : FALSE;
847

    
848
  // Add the CTools modal JavaScript for the edit button if necessary.
849
  ctools_include('modal');
850
  ctools_include('ajax');
851
  ctools_modal_add_js();
852

    
853
  // Set some default element properties.
854
  $element['#file'] = $file;
855

    
856
  $element['title'] = array(
857
    '#type' => 'item',
858
    '#title' => $element['#title'],
859
    '#description' => $element['#description'],
860
    '#required' => $element['#required'],
861
    '#weight' => -100,
862
  );
863
  if (isset($element['#title_display'])) {
864
    $element['title']['#title_display'] = $element['#title_display'];
865
  }
866

    
867
  // @todo This should load from the JS in case of a failed form submission.
868
  $element['preview'] = array(
869
    '#prefix' => '<div class="preview launcher">',
870
    '#suffix' => '</div>',
871
    '#weight' => 0,
872
    'content' => $file ? media_get_thumbnail_preview($file) : array(),
873
  );
874

    
875
  // @todo: Perhaps this language logic should be handled by JS since the state
876
  // changes on client side after choosing an item.
877
  $element['select'] = array(
878
    '#type' => 'link',
879
    '#href' => '',
880
    '#title' => t('Select'),
881
    '#attributes' => array('class' => array('button', 'launcher')),
882
    '#options' => array('fragment' => FALSE, 'external' => TRUE),
883
    '#weight' => 10,
884
  );
885
  // @todo Figure out how to update the preview image after the Edit modal is
886
  // closed.
887
  $element['edit'] = array(
888
    '#type' => 'link',
889
    '#href' => 'media/' . $fid . '/edit/nojs',
890
    '#title' => t('Edit'),
891
    '#attributes' => array(
892
      'class' => array(
893
        // Required for CTools modal to work.
894
        'ctools-use-modal', 'use-ajax',
895
        'ctools-modal-media-file-edit', 'button', 'edit',
896
      ),
897
    ),
898
    '#weight' => 20,
899
    '#access' => $file ? file_entity_access('update', $file) : TRUE, // only do perm check for existing files
900
  );
901
  $element['remove'] = array(
902
    '#type' => 'link',
903
    '#href' => '',
904
    '#title' => t('Remove'),
905
    '#attributes' => array('class' => array('button', 'remove')),
906
    '#options' => array('fragment' => FALSE, 'external' => TRUE),
907
    '#weight' => 30,
908
  );
909

    
910
  $element['fid'] = array(
911
    '#type' => 'hidden',
912
    '#value' => $fid,
913
    '#attributes' => array('class' => array('fid')),
914
    '#weight' => 100,
915
  );
916

    
917
  // Media browser attach code.
918
  $element['#attached']['js'][] = drupal_get_path('module', 'media') . '/js/media.js';
919

    
920
  $setting = array();
921
  $setting['media']['elements'][$element['#id']] = $element['#media_options'];
922

    
923
  $element['#attached']['js'][] = array(
924
    'type' => 'setting',
925
    'data' => $setting,
926
  );
927

    
928
  // @todo: Might need to think about this. All settings would likely apply to
929
  // all media in a multi-value, but what about passing the existing fid?
930
  module_load_include('inc', 'media', 'includes/media.browser');
931
  media_attach_browser_js($element);
932

    
933
  return $element;
934
  // @todo: make this work for file and image fields.
935
}
936

    
937
/**
938
 * Validate media form elements.
939
 *
940
 * The file type is validated during the upload process, but this is necessary
941
 * necessary in order to respect the #required property.
942
 */
943
function media_element_validate(&$element, &$form_state) {
944
  if ($element['#required']) {
945
    $has_value = FALSE;
946
    $widget_parents = $element['#array_parents'];
947
    array_pop($widget_parents);
948
    $items = drupal_array_get_nested_value($form_state['values'], $widget_parents);
949
    foreach ($items as $value) {
950
      if (is_array($value) && !empty($value['fid'])) {
951
        $has_value = TRUE;
952
      }
953
    }
954
    if (!$has_value) {
955
      form_error($element, t('%element_title is required.', array('%element_title' => $element['#title'])));
956
    }
957
  }
958
}
959

    
960
/**
961
 * Implements hook_filter_info().
962
 */
963
function media_filter_info() {
964
  $filters['media_filter'] = array(
965
    'title' => t('Convert Media tags to markup'),
966
    'description' => t('This filter will convert [[{type:media... ]] tags into markup.'),
967
    'process callback' => 'media_filter',
968
    'weight' => 2,
969
    // @TODO not implemented
970
    'tips callback' => 'media_filter_tips',
971
  );
972

    
973
  // If the WYSIWYG module is enabled, add additional help.
974
  if (module_exists('wysiwyg')) {
975
    $filters['media_filter']['description'] .= ' ' . t('This must be enabled for the Media WYSIWYG integration to work with this input format.');
976
  }
977

    
978
  return $filters;
979
}
980

    
981
/**
982
 * Media thumbnail render function.
983
 *
984
 * Returns a renderable array with the necessary classes to support a media
985
 * thumbnail. Also provides default fallback images if no image is available.
986
 *
987
 * @param object $file
988
 *   A Drupal file object.
989
 *
990
 * @return array
991
 *   Renderable array.
992
 */
993
function media_get_thumbnail_preview($file, $link = NULL) {
994
  // If a file has an invalid type, allow file_view_file() to work.
995
  if (!file_type_is_enabled($file->type)) {
996
    $file->type = file_get_type($file);
997
  }
998
  // @todo should CSS be attached to the form element here?
999
  // $element['#attached']['css'][] = drupal_get_path('module', 'media') . '/css/media.css';
1000
  drupal_add_css(drupal_get_path('module', 'media') . '/css/media.css');
1001
  $preview = file_view_file($file, 'preview');
1002
  $preview['#show_names'] = TRUE;
1003
  $preview['#add_link'] = $link;
1004
  $preview['#theme_wrappers'][] = 'media_thumbnail';
1005
  return $preview;
1006
}
1007

    
1008
/**
1009
 * Check that the media is one of the selected types.
1010
 *
1011
 * @param object $file
1012
 *   A Drupal file object.
1013
 * @param array $types
1014
 *   An array of media type names
1015
 *
1016
 * @return array
1017
 *   If the file type is not allowed, it will contain an error message.
1018
 *
1019
 * @see hook_file_validate()
1020
 */
1021
function media_file_validate_types(stdClass $file, $types) {
1022
  $errors = array();
1023
  if (!in_array(file_get_type($file), $types)) {
1024
    $errors[] = t('Only the following types of files are allowed to be uploaded: %types-allowed', array('%types-allowed' => implode(', ', $types)));
1025
  }
1026

    
1027
  return $errors;
1028
}
1029

    
1030
/**
1031
 * Implements hook_file_displays_alter().
1032
 */
1033
function media_file_displays_alter(&$displays, $file, $view_mode) {
1034
  if ($view_mode == 'preview' && empty($displays)) {
1035
    // We re in the media browser and this file has no formatters enabled.
1036
    // Instead of letting it go through theme_file_link(), pass it through
1037
    // theme_media_formatter_large_icon() to get our cool file icon instead.
1038
    $displays['file_field_media_large_icon'] = array(
1039
      'weight' => 0,
1040
      'status' => 1,
1041
      'settings' => NULL,
1042
    );
1043
  }
1044

    
1045
  // Override the fields of the file when requested by the WYSIWYG.
1046
  if (isset($file->override) && isset($file->override['fields'])) {
1047
    $instance = field_info_instances('file', $file->type);
1048
    foreach ($file->override['fields'] as $field_name => $value) {
1049
      if (!isset($instance[$field_name]['settings']) || !isset($instance[$field_name]['settings']['wysiwyg_override']) || $instance[$field_name]['settings']['wysiwyg_override']) {
1050
        $file->{$field_name} = $value;}
1051
    }
1052
  }
1053
}
1054

    
1055
/**
1056
 * Implements hook_file_default_displays_alter().
1057
 */
1058
function media_file_default_displays_alter(&$file_displays) {
1059
  // Image previews should be displayed using the media image style.
1060
  if (isset($file_displays['image__preview__file_field_image'])) {
1061
    $file_displays['image__preview__file_field_image']->settings['image_style'] = 'media_thumbnail';
1062
  }
1063

    
1064
  // Video previews should be displayed using a large filetype icon.
1065
  if (isset($file_displays['video__preview__file_field_file_default'])) {
1066
    $file_displays['video__preview__file_field_file_default']->status = FALSE;
1067
  }
1068

    
1069
  $file_display = new stdClass();
1070
  $file_display->api_version = 1;
1071
  $file_display->name = 'video__preview__file_field_media_large_icon';
1072
  $file_display->weight = 50;
1073
  $file_display->status = TRUE;
1074
  $file_display->settings = '';
1075
  $file_displays['video__preview__file_field_media_large_icon'] = $file_display;
1076

    
1077
  // Audio previews should be displayed using a large filetype icon.
1078
  if (isset($file_displays['audio__preview__file_field_file_default'])) {
1079
    $file_displays['audio__preview__file_field_file_default']->status = FALSE;
1080
  }
1081

    
1082
  $file_display = new stdClass();
1083
  $file_display->api_version = 1;
1084
  $file_display->name = 'audio__preview__file_field_media_large_icon';
1085
  $file_display->weight = 50;
1086
  $file_display->status = TRUE;
1087
  $file_display->settings = '';
1088
  $file_displays['audio__preview__file_field_media_large_icon'] = $file_display;
1089

    
1090
  // Document previews should be displayed using a large filetype icon.
1091
  if (isset($file_displays['document__preview__file_field_file_default'])) {
1092
    $file_displays['document__preview__file_field_file_default']->status = FALSE;
1093
  }
1094

    
1095
  $file_display = new stdClass();
1096
  $file_display->api_version = 1;
1097
  $file_display->name = 'document__preview__file_field_media_large_icon';
1098
  $file_display->weight = 50;
1099
  $file_display->status = TRUE;
1100
  $file_display->settings = '';
1101
  $file_displays['document__preview__file_field_media_large_icon'] = $file_display;
1102
}
1103

    
1104
/**
1105
 * Implements hook_ctools_plugin_api().
1106
 *
1107
 * Lets CTools know which plugin APIs are implemented by Media module.
1108
 */
1109
function media_ctools_plugin_api($module, $api) {
1110
  if ($module == 'file_entity' && $api == 'file_default_displays') {
1111
    return array(
1112
      'version' => 1,
1113
    );
1114
  }
1115
}
1116

    
1117
/**
1118
 * Implements hook_form_FORM_ID_alter().
1119
 *
1120
 * This alter enhances the default admin/content/file page, addding JS and CSS.
1121
 * It also makes modifications to the thumbnail view by replacing the existing
1122
 * checkboxes and table with thumbnails.
1123
 */
1124
function media_form_file_entity_admin_file_alter(&$form, $form_state) {
1125
  if (!empty($form_state['values']['operation'])) {
1126
    // The form is being rebuilt because an operation requiring confirmation
1127
    // We don't want to be messing with it in this case.
1128
    return;
1129
  }
1130

    
1131
  // Add the "Add file" local action, and notify users if they have files
1132
  // selected and they try to switch between the "Thumbnail" and "List" local
1133
  // tasks.
1134
  $path = drupal_get_path('module', 'media');
1135
  require_once $path . '/includes/media.browser.inc';
1136
  $form['#attributes']['class'][] = 'file-entity-admin-file-form';
1137
  $form['#attached']['js'][] = $path . '/js/media.admin.js';
1138
  $form['#attached']['css'][] = $path . '/css/media.css';
1139
  media_attach_browser_js($form);
1140

    
1141
  // By default, this form contains a table select element called "files". For
1142
  // the 'thumbnails' tab, Media generates a thumbnail for each file and
1143
  // replaces the tableselect with a grid of thumbnails.
1144
  if (arg(3) == 'thumbnails') {
1145
    if (empty($form['admin']['files'])) {
1146
      // Display empty text if there are no files.
1147
      $form['admin']['files'] = array(
1148
        '#markup' => '<p>' . $form['files']['#empty'] . '</p>',
1149
      );
1150
    }
1151
    else {
1152
      $files = file_load_multiple(array_keys($form['admin']['files']['#options']));
1153

    
1154
      $form['admin']['files'] = array(
1155
        '#tree' => TRUE,
1156
        '#prefix' => '<div class="media-display-thumbnails media-clear clearfix"><ul id="media-browser-library-list" class="media-list-thumbnails">',
1157
        '#suffix' => '</ul></div>',
1158
      );
1159

    
1160
      foreach ($files as $file) {
1161
        $preview = media_get_thumbnail_preview($file, TRUE);
1162
        $form['admin']['files'][$file->fid] = array(
1163
          '#type' => 'checkbox',
1164
          '#title' => '',
1165
          '#prefix' => '<li>' . drupal_render($preview),
1166
          '#suffix' => '</li>',
1167
        );
1168
      }
1169
    }
1170
  }
1171
}
1172

    
1173
/**
1174
 * Implements hook_views_api().
1175
 */
1176
function media_views_api() {
1177
  return array(
1178
    'api' => 3,
1179
    'path' => drupal_get_path('module', 'media'),
1180
  );
1181
}
1182

    
1183
/**
1184
 * Implements hook_views_default_views().
1185
 */
1186
function media_views_default_views() {
1187
  return media_load_all_exports('media', 'views', 'view.inc', 'view');
1188
}
1189

    
1190
/**
1191
 * Fetches an array of exportables from files.
1192
 *
1193
 * @param string $module
1194
 *   The module invoking this request. (Can be called by other modules.)
1195
 * @param string $directory
1196
 *   The subdirectory in the custom module.
1197
 * @param string $extension
1198
 *   The file extension.
1199
 * @param string $name
1200
 *   The name of the variable found in each file. Defaults to the same as
1201
 *   $extension.
1202
 *
1203
 * @return array
1204
 *   Array of $name objects.
1205
 */
1206
function media_load_all_exports($module, $directory, $extension, $name = NULL) {
1207
  if (!$name) {
1208
    $name = $extension;
1209
  }
1210

    
1211
  $return = array();
1212
  // Find all the files in the directory with the correct extension.
1213
  $files = file_scan_directory(drupal_get_path('module', $module) . "/$directory", "/.$extension/");
1214
  foreach ($files as $path => $file) {
1215
    require $path;
1216
    if (isset($$name)) {
1217
      $return[$$name->name] = $$name;
1218
    }
1219
  }
1220

    
1221
  return $return;
1222
}
1223

    
1224
/**
1225
 * Returns metadata describing Media browser plugins.
1226
 *
1227
 * @see hook_media_browser_plugin_info()
1228
 * @see hook_media_browser_plugin_info_alter()
1229
 */
1230
function media_get_browser_plugin_info() {
1231
  $info = &drupal_static(__FUNCTION__);
1232

    
1233
  if (!isset($info)) {
1234
    $cid = 'media:browser:plugin:info:' . $GLOBALS['language']->language;
1235
    if ($cache = cache_get($cid)) {
1236
      $info = $cache->data;
1237
    }
1238
    else {
1239
      $info = module_invoke_all('media_browser_plugin_info');
1240
      drupal_alter('media_browser_plugin_info', $info);
1241
      cache_set($cid, $info);
1242
    }
1243
  }
1244

    
1245
  return $info;
1246
}
1247

    
1248
/**
1249
 * Helper function to get a list of local stream wrappers.
1250
 */
1251
function media_get_local_stream_wrappers() {
1252
  return file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL);
1253
}
1254

    
1255
/**
1256
 * Helper function to get a list of remote stream wrappers.
1257
 */
1258
function media_get_remote_stream_wrappers() {
1259
  $wrappers = file_get_stream_wrappers();
1260
  $wrappers = array_diff_key($wrappers, file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_NORMAL));
1261
  $wrappers = array_diff_key($wrappers, file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL_HIDDEN));
1262
  return $wrappers;
1263
}
1264

    
1265

    
1266
/**
1267
 * Checks if there are any files that belong to disabled or deleted file
1268
 * types.
1269
 *
1270
 * @return Array of file types (machine names) that are candidates for
1271
 *   migration.
1272
 */
1273
function _media_get_migratable_file_types() {
1274
  $query = db_select('file_managed', 'f')
1275
    ->fields('f', array('type'))
1276
    ->distinct();
1277
  $types = $query->execute()->fetchCol();
1278
  $enabled_types = array();
1279
  foreach (file_type_get_enabled_types() as $type) {
1280
    $enabled_types[] = $type->type;
1281
  }
1282

    
1283
  return array_diff($types, $enabled_types);
1284
}