1 |
85ad3d82
|
Assos Assos
|
<?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 |
|
|
} |