Projet

Général

Profil

Paste
Télécharger (28,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / media / includes / media.filter.inc @ 74f6bef0

1
<?php
2

    
3
/**
4
 * @file
5
 * Functions related to the WYSIWYG editor and the media input filter.
6
 *
7
 * @TODO: Rename this file?
8
 */
9

    
10
define('MEDIA_TOKEN_REGEX', '/\[\[.*?\]\]/s');
11
define('MEDIA_TOKEN_REGEX_ALT', '/%7B.*?%7D/s');
12

    
13
/**
14
 * Implements hook_wysiwyg_include_directory().
15
 */
16
function media_wysiwyg_include_directory($type) {
17
  switch ($type) {
18
    case 'plugins':
19
      return 'wysiwyg_plugins';
20

    
21
    break;
22
  }
23
}
24

    
25
/**
26
 * Implements hook_field_attach_insert().
27
 *
28
 * Track file usage for media files included in formatted text. Note that this
29
 * is heavy-handed, and should be replaced when Drupal's filter system is
30
 * context-aware.
31
 */
32
function media_field_attach_insert($entity_type, $entity) {
33
  _media_filter_add_file_usage_from_fields($entity_type, $entity);
34
}
35

    
36
/**
37
 * Implements hook_field_attach_update().
38
 *
39
 * @see media_field_attach_insert().
40
 */
41
function media_field_attach_update($entity_type, $entity) {
42
  _media_filter_add_file_usage_from_fields($entity_type, $entity);
43
}
44

    
45
/**
46
 * Add file usage from file references in an entity's text fields.
47
 */
48
function _media_filter_add_file_usage_from_fields($entity_type, $entity) {
49
  // Track the total usage for files from all fields combined.
50
  $entity_files = media_entity_field_count_files($entity_type, $entity);
51

    
52
  list($entity_id, $entity_vid, $entity_bundle) = entity_extract_ids($entity_type, $entity);
53

    
54
  // When an entity has revisions and then is saved again NOT as new version the
55
  // previous revision of the entity has be loaded to get the last known good
56
  // count of files. The saved data is compared against the last version
57
  // so that a correct file count can be created for that (the current) version
58
  // id. This code may assume some things about entities that are only true for
59
  // node objects. This should be reviewed.
60
  // @TODO this conditional can probably be condensed
61
  if (empty($entity->revision) && empty($entity->old_vid) && empty($entity->is_new) && ! empty($entity->original)) {
62
    $old_files = media_entity_field_count_files($entity_type, $entity->original);
63
    foreach ($old_files as $fid => $old_file_count) {
64
      // Were there more files on the node just prior to saving?
65
      if (empty($entity_files[$fid])) {
66
        $entity_files[$fid] = 0;
67
      }
68
      if ($old_file_count > $entity_files[$fid]) {
69
        $deprecate = $old_file_count - $entity_files[$fid];
70
        // Now deprecate this usage
71
        $file = file_load($fid);
72
        if ($file) {
73
          file_usage_delete($file, 'media', $entity_type, $entity_id, $deprecate);
74
        }
75
        // Usage is deleted, nothing more to do with this file
76
        unset($entity_files[$fid]);
77
      }
78
      // There are the same number of files, nothing to do
79
      elseif ($entity_files[$fid] ==  $old_file_count) {
80
        unset($entity_files[$fid]);
81
      }
82
      // There are more files now, adjust the difference for the greater number.
83
      // file_usage incrementing will happen below.
84
      else {
85
        // We just need to adjust what the file count will account for the new
86
        // images that have been added since the increment process below will
87
        // just add these additional ones in
88
        $entity_files[$fid] = $entity_files[$fid] - $old_file_count;
89
      }
90
    }
91
  }
92

    
93
  // Each entity revision counts for file usage. If versions are not enabled
94
  // the file_usage table will have no entries for this because of the delete
95
  // query above.
96
  foreach ($entity_files as $fid => $entity_count) {
97
    if ($file = file_load($fid)) {
98
      file_usage_add($file, 'media', $entity_type, $entity_id, $entity_count);
99
    }
100
  }
101

    
102
}
103

    
104
/**
105
 * Parse file references from an entity's text fields and return them as an array.
106
 */
107
function media_filter_parse_from_fields($entity_type, $entity) {
108
  $file_references = array();
109

    
110
  foreach (_media_filter_fields_with_text_filtering($entity_type, $entity) as $field_name) {
111
    if ($field_items = field_get_items($entity_type, $entity, $field_name)) {
112
      foreach ($field_items as $field_item) {
113
        preg_match_all(MEDIA_TOKEN_REGEX, $field_item['value'], $matches);
114
        foreach ($matches[0] as $tag) {
115
          $tag = str_replace(array('[[', ']]'), '', $tag);
116
          $tag_info = drupal_json_decode($tag);
117
          if (isset($tag_info['fid']) && $tag_info['type'] == 'media') {
118
            $file_references[] = $tag_info;
119
          }
120
        }
121

    
122
        preg_match_all(MEDIA_TOKEN_REGEX_ALT, $field_item['value'], $matches_alt);
123
        foreach ($matches_alt[0] as $tag) {
124
          $tag = urldecode($tag);
125
          $tag_info = drupal_json_decode($tag);
126
          if (isset($tag_info['fid']) && $tag_info['type'] == 'media') {
127
            $file_references[] = $tag_info;
128
          }
129
        }
130
      }
131
    }
132
  }
133

    
134
  return $file_references;
135
}
136

    
137
/**
138
 * Returns an array containing the names of all fields that perform text filtering.
139
 */
140
function _media_filter_fields_with_text_filtering($entity_type, $entity) {
141
  list($entity_id, $revision_id, $bundle) = entity_extract_ids($entity_type, $entity);
142
  $fields = field_info_instances($entity_type, $bundle);
143

    
144
  // Get all of the fields on this entity that allow text filtering.
145
  $fields_with_text_filtering = array();
146
  foreach ($fields as $field_name => $field) {
147
    if (!empty($field['settings']['text_processing'])) {
148
      $fields_with_text_filtering[] = $field_name;
149
    }
150
  }
151

    
152
  return $fields_with_text_filtering;
153
}
154

    
155
/**
156
 * Utility function to get the file count in this entity
157
 *
158
 * @param type $entity
159
 * @param type $entity_type
160
 * @return int
161
 */
162
function media_entity_field_count_files($entity_type, $entity) {
163
  $entity_files = array();
164
  foreach (media_filter_parse_from_fields($entity_type, $entity) as $file_reference) {
165
    if (empty($entity_files[$file_reference['fid']])) {
166
      $entity_files[$file_reference['fid']] = 1;
167
    }
168
    else {
169
      $entity_files[$file_reference['fid']]++;
170
    }
171
  }
172
  return $entity_files;
173
}
174

    
175
/**
176
 * Implements hook_entity_delete().
177
 */
178
function media_entity_delete($entity, $type) {
179
  list($entity_id) = entity_extract_ids($type, $entity);
180

    
181
  db_delete('file_usage')
182
    ->condition('module', 'media')
183
    ->condition('type', $type)
184
    ->condition('id', $entity_id)
185
    ->execute();
186
}
187

    
188
/**
189
 * Implements hook_field_attach_delete_revision().
190
 *
191
 * @param type $entity_type
192
 * @param type $entity
193
 */
194
function media_field_attach_delete_revision($entity_type, $entity) {
195
  list($entity_id) = entity_extract_ids($entity_type, $entity);
196
  $files = media_entity_field_count_files($entity_type, $entity);
197
  foreach ($files as $fid => $count) {
198
    if ($file = file_load($fid)) {
199
      file_usage_delete($file, 'media', $entity_type , $entity_id, $count);
200
    }
201
  }
202
}
203

    
204
/**
205
 * Filter callback for media markup filter.
206
 *
207
 * @TODO check for security probably pass text through filter_xss
208
 */
209
function media_filter($text) {
210
  $text = preg_replace_callback(MEDIA_TOKEN_REGEX, 'media_token_to_markup', $text);
211
  return $text;
212
}
213

    
214
/**
215
 * Parses the contents of a CSS declaration block.
216
 *
217
 * @param string $declarations
218
 *   One or more CSS declarations delimited by a semicolon. The same as a CSS
219
 *   declaration block (see http://www.w3.org/TR/CSS21/syndata.html#rule-sets),
220
 *   but without the opening and closing curly braces. Also the same as the
221
 *   value of an inline HTML style attribute.
222
 *
223
 * @return array
224
 *   A keyed array. The keys are CSS property names, and the values are CSS
225
 *   property values.
226
 */
227
function media_parse_css_declarations($declarations) {
228
  $properties = array();
229
  foreach (array_map('trim', explode(";", $declarations)) as $declaration) {
230
    if ($declaration != '') {
231
      list($name, $value) = array_map('trim', explode(':', $declaration, 2));
232
      $properties[strtolower($name)] = $value;
233
    }
234
  }
235
  return $properties;
236
}
237

    
238
/**
239
 * Replace callback to convert a media file tag into HTML markup.
240
 *
241
 * @param string $match
242
 *   Takes a match of tag code
243
 * @param bool $wysiwyg
244
 *   Set to TRUE if called from within the WYSIWYG text area editor.
245
 *
246
 * @return string
247
 *   The HTML markup representation of the tag, or an empty string on failure.
248
 *
249
 * @see media_get_file_without_label()
250
 * @see hook_media_token_to_markup_alter()
251
 */
252
function media_token_to_markup($match, $wysiwyg = FALSE) {
253
  $settings = array();
254
  $match = str_replace("[[", "", $match);
255
  $match = str_replace("]]", "", $match);
256
  $tag = $match[0];
257

    
258
  try {
259
    if (!is_string($tag)) {
260
      throw new Exception('Unable to find matching tag');
261
    }
262

    
263
    $tag_info = drupal_json_decode($tag);
264

    
265
    if (!isset($tag_info['fid'])) {
266
      throw new Exception('No file Id');
267
    }
268

    
269
    // Ensure the 'link_text' key is always defined.
270
    if (!isset($tag_info['link_text'])) {
271
      $tag_info['link_text'] = NULL;
272
    }
273

    
274
    // Ensure a valid view mode is being requested.
275
    if (!isset($tag_info['view_mode'])) {
276
      $tag_info['view_mode'] = variable_get('media__wysiwyg_default_view_mode', 'full');
277
    }
278
    elseif ($tag_info['view_mode'] != 'default') {
279
      $file_entity_info = entity_get_info('file');
280
      if (!in_array($tag_info['view_mode'], array_keys($file_entity_info['view modes']))) {
281
        // Media 1.x defined some old view modes that have been superseded by
282
        // more semantically named ones in File Entity. The media_update_7203()
283
        // function updates field settings that reference the old view modes,
284
        // but it's impractical to update all text content, so adjust
285
        // accordingly here.
286
        static $view_mode_updates = array(
287
          'media_preview' => 'preview',
288
          'media_small' => 'teaser',
289
          'media_large' => 'full',
290
        );
291
        if (isset($view_mode_updates[$tag_info['view_mode']])) {
292
          $tag_info['view_mode'] = $view_mode_updates[$tag_info['view_mode']];
293
        }
294
        else {
295
          throw new Exception('Invalid view mode');
296
        }
297
      }
298
    }
299

    
300
    $file = file_load($tag_info['fid']);
301
    if (!$file) {
302
      throw new Exception('Could not load media object');
303
    }
304
    $tag_info['file'] = $file;
305

    
306
    // The class attributes is a string, but drupal requires it to be
307
    // an array, so we fix it here.
308
    if (!empty($tag_info['attributes']['class'])) {
309
      $tag_info['attributes']['class'] = explode(" ", $tag_info['attributes']['class']);
310
    }
311

    
312
    // Grab the potentially overrided fields from the file.
313
    $fields = media_filter_field_parser($tag_info);
314

    
315
    $attributes = is_array($tag_info['attributes']) ? $tag_info['attributes'] : array();
316
    $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align'));
317
    $settings['attributes'] = array_intersect_key($attributes, array_flip($attribute_whitelist));
318
    $settings['fields'] = $fields;
319

    
320
    if (!empty($tag_info['attributes']) && is_array($tag_info['attributes'])) {
321
      $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align'));
322
      $settings['attributes'] = array_intersect_key($tag_info['attributes'], array_flip($attribute_whitelist));
323
      $settings['fields'] = $fields;
324

    
325
      // Many media formatters will want to apply width and height independently
326
      // of the style attribute or the corresponding HTML attributes, so pull
327
      // these two out into top-level settings. Different WYSIWYG editors have
328
      // different behavior with respect to whether they store user-specified
329
      // dimensions in the HTML attributes or the style attribute - check both.
330
      // Per http://www.w3.org/TR/html5/the-map-element.html#attr-dim-width, the
331
      // HTML attributes are merely hints: CSS takes precedence.
332
      if (isset($settings['attributes']['style'])) {
333
        $css_properties = media_parse_css_declarations($settings['attributes']['style']);
334
        foreach (array('width', 'height') as $dimension) {
335
          if (isset($css_properties[$dimension]) && substr($css_properties[$dimension], -2) == 'px') {
336
            $settings[$dimension] = substr($css_properties[$dimension], 0, -2);
337
          }
338
          elseif (isset($settings['attributes'][$dimension])) {
339
            $settings[$dimension] = $settings['attributes'][$dimension];
340
          }
341
        }
342
      }
343
    }
344
  }
345
  catch (Exception $e) {
346
    watchdog('media', 'Unable to render media from %tag. Error: %error', array('%tag' => $tag, '%error' => $e->getMessage()));
347
    return '';
348
  }
349

    
350
  // If the tag has link text stored with it, override the filename with it for
351
  // the rest of this function, so that if the file is themed as a link, the
352
  // desired text will be used (see, for example, theme_file_link()).
353
  // @todo: Try to find a less hacky way to do this.
354
  if (isset($tag_info['link_text'])) {
355
    // The link text will have characters such as "&" encoded for HTML, but the
356
    // filename itself needs the raw value when it is used to build the link,
357
    // in order to avoid double encoding.
358
    $file->filename = decode_entities($tag_info['link_text']);
359
  }
360

    
361
  if ($wysiwyg) {
362
    $settings['wysiwyg'] = $wysiwyg;
363
    // If sending markup to a WYSIWYG, we need to pass the file infomation so
364
    // that a inline macro can be generated when the WYSIWYG is detached.
365
    // The WYSIWYG plugin is expecting this information in the format of a
366
    // urlencoded JSON string stored in the data-file_info attribute of the
367
    // element.
368
    $element = media_get_file_without_label($file, $tag_info['view_mode'], $settings);
369
    $data = drupal_json_encode(array(
370
      'type' => 'media',
371
      'fid' => $file->fid,
372
      'view_mode' => $tag_info['view_mode'],
373
      'link_text' => $tag_info['link_text'],
374
    ));
375
    $element['#attributes']['data-file_info'] = urlencode($data);
376
    $element['#attributes']['class'][] = 'media-element';
377
  }
378
  else {
379
    // Display the field elements.
380
    $element = array();
381
    $element['content']['file'] = media_get_file_without_label($file, $tag_info['view_mode'], $settings);
382
    // Overwrite or set the file #alt attribute if it has been set in this
383
    // instance.
384
    if (!empty($element['content']['file']['#attributes']['alt'])) {
385
      $element['content']['file']['#alt'] = $element['content']['file']['#attributes']['alt'];
386
    }
387
    // Overwrite or set the file #title attribute if it has been set in this
388
    // instance.
389
    if (!empty($element['content']['file']['#attributes']['title'])) {
390
      $element['content']['file']['#title'] = $element['content']['file']['#attributes']['title'];
391
    }
392
    field_attach_prepare_view('file', array($file->fid => $file), $tag_info['view_mode']);
393
    entity_prepare_view('file', array($file->fid => $file));
394
    $element['content'] += field_attach_view('file', $file, $tag_info['view_mode']);
395
    if (count(element_children($element['content'])) > 1) {
396
      // Add surrounding divs to group them together.
397
      // We dont want divs when there are no additional fields to allow files
398
      // to display inline with text, without breaking p tags.
399
      $element['content']['#type'] = 'container';
400
      $element['content']['#attributes']['class'] = array(
401
        'media',
402
        'media-element-container',
403
        'media-' . $element['content']['file']['#view_mode']
404
      );
405
    }
406
  }
407
  drupal_alter('media_token_to_markup', $element, $tag_info, $settings);
408
  return drupal_render($element);
409
}
410

    
411
/**
412
 * Parse the field array from the collapsed AJAX string.
413
 */
414
function media_filter_field_parser($tag_info) {
415
  $fields = array();
416
  if (isset($tag_info['fields'])) {
417
    foreach($tag_info['fields'] as $field_name => $field_value) {
418
      if (strpos($field_name, 'field_') === 0) {
419
        $parsed_field = explode('[', str_replace(']', '', $field_name));
420
        if(isset($parsed_field[2])) {
421
          if(isset($parsed_field[3])) {
422
            $fields[$parsed_field[0]][$parsed_field[1]][$parsed_field[2]][$parsed_field[3]] = $field_value;
423
          } else {
424
            $fields[$parsed_field[0]][$parsed_field[1]][$parsed_field[2]] = $field_value;
425
          }
426
        } else {
427
          $fields[$parsed_field[0]][$parsed_field[1]] = $field_value;
428
        }
429
      }
430
    }
431
  }
432
  return $fields;
433
}
434
/**
435
 * Builds a map of media tags in the element.
436
 *
437
 * Builds a map of the media tags in an element that are being rendered to their
438
 * rendered HTML. The map is stored in JS, so we can transform them when the
439
 * editor is being displayed.
440
 */
441
function media_pre_render_text_format($element) {
442
  // filter_process_format() copies properties to the expanded 'value' child
443
  // element.
444
  if (!isset($element['format'])) {
445
    return $element;
446
  }
447

    
448
  $field = &$element['value'];
449
  $settings = array(
450
    'field' => $field['#id'],
451
  );
452

    
453
  $tagmap = _media_generate_tagMap($field['#value']);
454

    
455
  if (isset($tagmap)) {
456
    drupal_add_js(array('tagmap' => $tagmap), 'setting');
457
  }
458
  return $element;
459
}
460

    
461
/**
462
 * Creates map of inline media tags.
463
 *
464
 * Generates an array of [inline tags] => <html> to be used in filter
465
 * replacement and to add the mapping to JS.
466
 *
467
 * @param string $text
468
 *   The String containing text and html markup of textarea
469
 *
470
 * @return array
471
 *   An associative array with tag code as key and html markup as the value.
472
 *
473
 * @see media_process_form()
474
 * @see media_token_to_markup()
475
 */
476
function _media_generate_tagMap($text) {
477
  // Making $tagmap static as this function is called many times and
478
  // adds duplicate markup for each tag code in Drupal.settings JS,
479
  // so in media_process_form it adds something like tagCode:<markup>,
480
  // <markup> and when we replace in attach see two duplicate images
481
  // for one tagCode. Making static would make function remember value
482
  // between function calls. Since media_process_form is multiple times
483
  // with same form, this function is also called multiple times.
484
  static $tagmap = array();
485
  preg_match_all("/\[\[.*?\]\]/s", $text, $matches, PREG_SET_ORDER);
486
  foreach ($matches as $match) {
487
    // We see if tagContent is already in $tagMap, if not we add it
488
    // to $tagmap.  If we return an empty array, we break embeddings of the same
489
    // media multiple times.
490
    if (empty($tagmap[$match[0]])) {
491
      // @TODO: Total HACK, but better than nothing.
492
      // We should find a better way of cleaning this up.
493
      if ($markup_for_media = media_token_to_markup($match, TRUE)) {
494
        $tagmap[$match[0]] = $markup_for_media;
495
      }
496
      else {
497
        $missing = file_create_url(drupal_get_path('module', 'media') . '/images/icons/default/image-x-generic.png');
498
        $tagmap[$match[0]] = '<div><img src="' . $missing . '" width="100px" height="100px"/></div>';
499
      }
500
    }
501
  }
502
  return $tagmap;
503
}
504

    
505
/**
506
 * Return a list of view modes allowed for a file embedded in the WYSIWYG.
507
 *
508
 * @param object $file
509
 *   A file entity.
510
 *
511
 * @return array
512
 *   An array of view modes that can be used on the file when embedded in the
513
 *   WYSIWYG.
514
 */
515
function media_get_wysiwyg_allowed_view_modes($file) {
516
  $enabled_view_modes = &drupal_static(__FUNCTION__, array());
517

    
518
  // @todo Add more caching for this.
519
  if (!isset($enabled_view_modes[$file->type])) {
520
    $enabled_view_modes[$file->type] = array();
521

    
522
    // Add the default view mode by default.
523
    $enabled_view_modes[$file->type]['default'] = array('label' => t('Default'), 'custom settings' => TRUE);
524

    
525
    $entity_info = entity_get_info('file');
526
    $view_mode_settings = field_view_mode_settings('file', $file->type);
527
    foreach ($entity_info['view modes'] as $view_mode => $view_mode_info) {
528
      // Do not show view modes that don't have their own settings and will
529
      // only fall back to the default view mode.
530
      if (empty($view_mode_settings[$view_mode]['custom_settings'])) {
531
        continue;
532
      }
533

    
534
      // Don't present the user with an option to choose a view mode in which
535
      // the file is hidden.
536
      $extra_fields = field_extra_fields_get_display('file', $file->type, $view_mode);
537
      if (empty($extra_fields['file']['visible'])) {
538
        continue;
539
      }
540

    
541
      // Add the view mode to the list of enabled view modes.
542
      $enabled_view_modes[$file->type][$view_mode] = $view_mode_info;
543
    }
544
  }
545

    
546
  $view_modes = $enabled_view_modes[$file->type];
547
  drupal_alter('media_wysiwyg_allowed_view_modes', $view_modes, $file);
548
  return $view_modes;
549
}
550

    
551
/**
552
 * Form callback used when embedding media.
553
 *
554
 * Allows the user to pick a format for their media file.
555
 * Can also have additional params depending on the media type.
556
 */
557
function media_format_form($form, $form_state, $file) {
558
  $form = array();
559
  $form['#media'] = $file;
560

    
561
  // Allow for overrides to the fields.
562
  $query_fields = isset($_GET['fields']) ? drupal_json_decode($_GET['fields']) : array();
563
  $fields = media_filter_field_parser(array('fields' => $query_fields), $file);
564

    
565
  $view_modes = media_get_wysiwyg_allowed_view_modes($file);
566
  $formats = $options = array();
567
  foreach ($view_modes as $view_mode => $view_mode_info) {
568
    // @TODO: Display more verbose information about which formatter and what it
569
    // does.
570
    $options[$view_mode] = $view_mode_info['label'];
571
    $element = media_get_file_without_label($file, $view_mode, array('wysiwyg' => TRUE));
572

    
573
    // Make a pretty name out of this.
574
    $formats[$view_mode] = drupal_render($element);
575
  }
576

    
577
  // Add the previews back into the form array so they can be altered.
578
  $form['#formats'] = &$formats;
579

    
580
  if (!count($formats)) {
581
    throw new Exception('Unable to continue, no available formats for displaying media.');
582
    return;
583
  }
584

    
585
  // Allow for overrides to the display format.
586
  $default_view_mode = is_array($query_fields) && isset($query_fields['format']) ? $query_fields['format'] : variable_get('media__wysiwyg_default_view_mode', 'full');
587
  if (!isset($formats[$default_view_mode])) {
588
    $default_view_mode = key($formats);
589
  }
590

    
591
  // Add the previews by reference so that they can easily be altered by
592
  // changing $form['#formats'].
593
  $settings['media']['formatFormFormats'] = &$formats;
594
  $form['#attached']['js'][] = array('data' => $settings, 'type' => 'setting');
595

    
596
  // Add the required libraries, JavaScript and CSS for the form.
597
  $form['#attached']['library'][] = array('media', 'media_base');
598
  $form['#attached']['library'][] = array('system', 'form');
599
  $form['#attached']['js'][] = drupal_get_path('module', 'media') . '/js/media.format_form.js';
600

    
601
  $form['title'] = array(
602
    '#markup' => t('Embedding %filename', array('%filename' => $file->filename)),
603
  );
604

    
605
  $preview = media_get_thumbnail_preview($file);
606

    
607
  $form['preview'] = array(
608
    '#type' => 'markup',
609
    '#title' => check_plain(basename($file->uri)),
610
    '#markup' => drupal_render($preview),
611
  );
612

    
613
  // These will get passed on to WYSIWYG.
614
  $form['options'] = array(
615
    '#type' => 'fieldset',
616
    '#title' => t('options'),
617
  );
618

    
619
  $form['options']['format'] = array(
620
    '#type' => 'select',
621
    '#title' => t('Display as'),
622
    '#options' => $options,
623
    '#default_value' => $default_view_mode,
624
    '#description' => t('Choose the type of display you would like for this
625
      file. Please be aware that files may display differently than they do when
626
      they are inserted into an editor.')
627
  );
628

    
629
  // Add fields from the file, so that we can override them if neccesary.
630
  $form['options']['fields'] = array();
631
  foreach ($fields as $field_name => $field_value) {
632
    $file->{$field_name} = $field_value;
633
  }
634
  field_attach_form('file', $file, $form['options']['fields'], $form_state);
635
  $instance = field_info_instances('file', $file->type);
636
  foreach ($instance as $field_name => $field_value) {
637
    if (isset($instance[$field_name]['settings']) && isset($instance[$field_name]['settings']['wysiwyg_override']) && !$instance[$field_name]['settings']['wysiwyg_override']) {
638
      unset($form['options']['fields'][$field_name]);
639
    }
640
  }
641

    
642
  // Similar to a form_alter, but we want this to run first so that
643
  // media.types.inc can add the fields specific to a given type (like alt tags
644
  // on media). If implemented as an alter, this might not happen, making other
645
  // alters not be able to work on those fields.
646
  // @todo: We need to pass in existing values for those attributes.
647
  drupal_alter('media_format_form_prepare', $form, $form_state, $file);
648

    
649
  if (!element_children($form['options'])) {
650
    $form['options']['#attributes'] = array('style' => 'display:none');
651
  }
652

    
653
  return $form;
654
}
655

    
656
/**
657
 * Returns a drupal_render() array for just the file portion of a file entity.
658
 *
659
 * Optional custom settings can override how the file is displayed.
660
 */
661
function media_get_file_without_label($file, $view_mode, $settings = array()) {
662
  $file->override = $settings;
663

    
664
  $element = file_view_file($file, $view_mode);
665

    
666
  // The formatter invoked by file_view_file() can use $file->override to
667
  // customize the returned render array to match the requested settings. To
668
  // support simple formatters that don't do this, set the element attributes to
669
  // what was requested, but not if the formatter applied its own logic for
670
  // element attributes.
671
  if (!isset($element['#attributes']) && isset($settings['attributes'])) {
672
    $element['#attributes'] = $settings['attributes'];
673

    
674
    // While this function may be called for any file type, images are a common
675
    // use-case. theme_image() and theme_image_style() require the 'alt'
676
    // attribute to be passed separately from the 'attributes' array (see
677
    // http://drupal.org/node/999338). Until that's fixed, implement this
678
    // special-case logic. Image formatters using other theme functions are
679
    // responsible for their own 'alt' attribute handling. See
680
    // theme_media_formatter_large_icon() for an example.
681
    if (isset($settings['attributes']['alt']) && !isset($element['#alt']) && isset($element['#theme']) && in_array($element['#theme'], array('image', 'image_style'))) {
682
      $element['#alt'] = $settings['attributes']['alt'];
683
    }
684
  }
685

    
686
  return $element;
687
}
688

    
689
/**
690
 * Implements hook_entity_dependencies().
691
 */
692
function media_entity_dependencies($entity, $entity_type) {
693
  // Go through all the entity's text fields and add a dependency on any files
694
  // that are referenced there.
695
  $dependencies = array();
696
  foreach (media_filter_parse_from_fields($entity_type, $entity) as $file_reference) {
697
    $dependencies[] = array('type' => 'file', 'id' => $file_reference['fid']);
698
  }
699
  return $dependencies;
700
}
701

    
702
/**
703
 * Implements hook_entity_uuid_load().
704
 */
705
function media_entity_uuid_load(&$entities, $entity_type) {
706
  // Go through all the entity's text fields and replace file IDs in media
707
  // tokens with the corresponding UUID.
708
  foreach ($entities as $entity) {
709
    media_filter_replace_tokens_in_all_text_fields($entity_type, $entity, 'media_token_fid_to_uuid');
710
  }
711
}
712

    
713
/**
714
 * Implements hook_entity_uuid_presave().
715
 */
716
function media_entity_uuid_presave(&$entity, $entity_type) {
717
  // Go through all the entity's text fields and replace UUIDs in media tokens
718
  // with the corresponding file ID.
719
  media_filter_replace_tokens_in_all_text_fields($entity_type, $entity, 'media_token_uuid_to_fid');
720
}
721

    
722
/**
723
 * Replaces media tokens in an entity's text fields, using the specified callback function.
724
 */
725
function media_filter_replace_tokens_in_all_text_fields($entity_type, $entity, $callback) {
726
  $text_field_names = _media_filter_fields_with_text_filtering($entity_type, $entity);
727
  foreach ($text_field_names as $field_name) {
728
    if (!empty($entity->{$field_name})) {
729
      $field = field_info_field($field_name);
730
      $all_languages = field_available_languages($entity_type, $field);
731
      $field_languages = array_intersect($all_languages, array_keys($entity->{$field_name}));
732
      foreach ($field_languages as $language) {
733
        if (!empty($entity->{$field_name}[$language])) {
734
          foreach ($entity->{$field_name}[$language] as &$item) {
735
            $item['value'] = preg_replace_callback(MEDIA_TOKEN_REGEX, $callback, $item['value']);
736
          }
737
        }
738
      }
739
    }
740
  }
741
}
742

    
743
/**
744
 * Callback to replace file IDs with UUIDs in a media token.
745
 */
746
function media_token_fid_to_uuid($matches) {
747
  return _media_token_uuid_replace($matches, 'entity_get_uuid_by_id');
748
}
749

    
750
/**
751
 * Callback to replace UUIDs with file IDs in a media token.
752
 */
753
function media_token_uuid_to_fid($matches) {
754
  return _media_token_uuid_replace($matches, 'entity_get_id_by_uuid');
755
}
756

    
757
/**
758
 * Helper function to replace UUIDs with file IDs or vice versa.
759
 *
760
 * @param array $matches
761
 *   An array of matches for media tokens, from a preg_replace_callback()
762
 *   callback function.
763
 * @param string $entity_uuid_function
764
 *   Either 'entity_get_uuid_by_id' (to replace file IDs with UUIDs in the
765
 *   token) or 'entity_get_id_by_uuid' (to replace UUIDs with file IDs).
766
 *
767
 * @return string
768
 *   A string representing the JSON-encoded token, with the appropriate
769
 *   replacement between file IDs and UUIDs.
770
 */
771
function _media_token_uuid_replace($matches, $entity_uuid_function) {
772
  $tag = $matches[0];
773
  $tag = str_replace(array('[[', ']]'), '', $tag);
774
  $tag_info = drupal_json_decode($tag);
775
  if (isset($tag_info['fid'])) {
776
    if ($new_ids = $entity_uuid_function('file', array($tag_info['fid']))) {
777
      $new_id = reset($new_ids);
778
      $tag_info['fid'] = $new_id;
779
    }
780
  }
781
  return '[[' . drupal_json_encode($tag_info) . ']]';
782
}