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
|
}
|