|
|
|[ \n\r\t\(])((http://|https://)([a-zA-Z0-9@:%_+*~#?&=.,/;-]*[a-zA-Z0-9@:%_+*~#&=/;-]))([.,?!]*?)(?=(||
|[ \n\r\t\)]))`i", $callback, $text);
return $text;
}
/**
* If one of our allowed providers knows what to do with the url,
* then let it embed the video.
*
* @param int $filter
* The filter id.
* @param array $match
* The matched text from our regex.
*
* @return string
* The replacement text for the url.
*/
function _media_url_parse_full_links($match) {
// Get just the URL.
$match[2] = check_url(decode_entities($match[2]));
try {
$file = media_parse_to_file($match[2]);
}
catch (Exception $e) {
// Ignore errors; pass the original text for other filters to deal with.
return $match[0];
}
if ($file->fid) {
// Return the embedded media.
// Now load the desired media to display.
$medias = entity_load('media', array($file->fid));
if (!empty($medias)) {
$media = array_pop($medias);
// Generate a preview of the file
// @TODO: Allow user to change the formatter in the filter settings.
$preview = field_view_field('media', $media, 'file', 'media_large');
$preview['#show_names'] = TRUE;
return drupal_render($preview);
}
}
// Nothing was parsed; return the original text.
return $match[0];
}
function _media_url_curry($func, $arity) {
return create_function('', "
\$args = func_get_args();
if(count(\$args) >= $arity)
return call_user_func_array('$func', \$args);
\$args = var_export(\$args, 1);
return create_function('','
\$a = func_get_args();
\$z = ' . \$args . ';
\$a = array_merge(\$z,\$a);
return call_user_func_array(\'$func\', \$a);
');
");
}
/**
* Replace callback to convert tag into markup
* @param string $match
* Takes a match of tag code
* @param boolean $wysiwyg
* Set to TRUE if called from within the WYSIWYG text area editor.
* @return
* Return the replaced markup
*/
function media_token_to_markup($match, $wysiwyg = FALSE) {
$match = str_replace("[[","",$match);
$match = str_replace("]]","",$match);
$tag = $match[0];
try {
if (!is_string($tag)) {
throw new Exception('Unable to find matching tag');
}
$media = drupal_json_decode($tag);
if (!isset($media['fid'])) {
throw new Exception('No file Id');
}
if (!isset($media['view_mode'])) {
// Should we log or throw an exception here instead?
// Do we need to validate the view mode for fields API?
$media['view_mode'] = media_variable_get('wysiwyg_default_view_mode');
}
$media_obj = media_load($media['fid']);
if (!$media_obj) {
throw new Exception('Could not load media object');
}
// Track the fid of this media object in the {media_filter_usage} table.
media_filter_track_usage($media['fid']);
$settings = is_array($media['attributes']) ? $media['attributes'] : array();
$attribute_whitelist = media_variable_get('wysiwyg_allowed_attributes');
$settings = array_intersect_key($settings, array_flip($attribute_whitelist));
// @TODO: What case does this provide for? Can we add this logic in JS when we embed it?
// This doesn't look great to me. Also won't work if the style has anything
// between width and height (or if they are in reverse order).
if (isset($settings['style'])) {
if (preg_match('@width: (.+?)px; height: (.+?)px;@i', $settings['style'], $matches)) {
$settings['width'] = $matches[1];
$settings['height'] = $matches[2];
}
}
if ($wysiwyg) {
$settings['wysiwyg'] = $wysiwyg;
}
}
catch (Exception $e) {
watchdog('media', 'Unable to render media from %tag. Error: %error', array('%tag' => $tag, '%error' => $e->getMessage()));
return '';
}
$file_field = media_get_file_without_label($media_obj, $media['view_mode'], $settings);
return drupal_render($file_field);
}
/**
* Builds a map of media tags in the element being rendered to their rendered HTML.
*
* The map is stored in JS, so we can transform them when the editor is being displayed.
*
* @param array $element
*/
function media_pre_render_text_format($element) {
// filter_process_format() copies properties to the expanded 'value' child
// element.
if (!isset($element['format'])) {
return $element;
}
$field = &$element['value'];
$settings = array(
'field' => $field['#id'],
);
$tagmap = _media_generate_tagMap($field['#value']);
if (isset($tagmap)) {
drupal_add_js(array('tagmap' => array_unique($tagmap)), 'setting');
}
return $element;
}
/**
* Generates an array of [inline tags] => to be used in filter
* replacement and to add the mapping to JS.
* @param
* The String containing text and html markup of textarea
* @return
* An associative array with tag code as key and html markup as the value.
*
* @see
* media_process_form
* media_token_to_markup
*/
function _media_generate_tagMap($text) {
// Making $tagmap static as this function is called many times and
// adds duplicate markup for each tag code in Drupal.settings JS,
// so in media_process_form it adds something like tagCode:,
// and when we replace in attach see two duplicate images
// for one tagCode. Making static would make function remember value
// between function calls. Since media_process_form is multiple times
// with same form, this function is also called multiple times.
static $tagmap = array();
preg_match_all("/\[\[.*?]]/s", $text, $matches, PREG_SET_ORDER);
foreach($matches as $match) {
// We see if tagContent is already in $tagMap, if not we add it
// to $tagmap. If we return an empty array, we break embeddings of the same
// media multiple times.
if(empty($tagmap[$match[0]])) {
// @TODO: Total HACK, but better than nothing.
// We should find a better way of cleaning this up.
if ($markup_for_media = media_token_to_markup($match, TRUE)) {
$tagmap[$match[0]] = $markup_for_media;
} else {
$tagmap[$match[0]] = '';
}
}
}
return $tagmap;
}
/**
* Form callback used when embedding media.
*
* Allows the user to pick a format for their media file.
* Can also have additional params depending on the media type.
*/
function media_format_form($form, $form_state, $media) {
// This will vary depending on the media type.
$form = array();
$form['#media'] = $media;
$instance_info = field_info_instance('media', 'file', $media->type);
$entity_info = entity_get_info('media');
$view_modes = $entity_info['view modes'];
$options = array();
drupal_alter('media_wysiwyg_allowed_view_modes', $media_type, $view_modes);
foreach ($view_modes as $key => $title) {
$format = field_get_display($instance_info, $key, $media);
//If the format is set to hidden, don't offer it
if ($format['type'] == 'hidden') {
continue;
}
//@TODO: Display more verbose information about which formatter and what it does.
$options[$key] = $title['label'];
$file_field = media_get_file_without_label($media, $key, array('wysiwyg' => TRUE));
// Make a pretty name out of this.
$formats[$key] = drupal_render($file_field);
}
if (!count($formats)) {
throw new Exception('Unable to continue, no available formats for displaying media.');
return;
}
$default_view_mode = media_variable_get('wysiwyg_default_view_mode');
if (!isset($formats[$default_view_mode])) {
$default_view_mode = key($formats);
}
// Add JS and settings array of formats.
$settings = array();
$settings['media'] = array('formatFormFormats' => $formats);
drupal_add_js($settings, 'setting');
drupal_add_library('media', 'media_base');
drupal_add_library('system', 'form');
$path = drupal_get_path('module', 'media');
$form['#attached']['js'][] = $path . '/javascript/media-format-form.js';
$form['#attached']['css'][] = $path . '/css/media-format-form.css';
$form['heading'] = array(
'#type' => 'markup',
'#prefix' => '',
'#suffix' => '
',
'#markup' => t('Embedding %filename', array('%filename' => $media->filename)),
);
$preview = media_get_thumbnail_preview($media);
$form['preview'] = array(
'#type' => 'markup',
'#title' => basename($media->uri),
'#markup' => drupal_render($preview),
);
// These will get passed on to WYSIWYG
$form['options'] = array(
'#type' => 'fieldset',
'#title' => t('options'),
);
$form['options']['format'] = array(
'#type' => 'select',
'#title' => t('Current format is'),
'#options' => $options,
'#default_value' => $default_view_mode
);
// Similar to a form_alter, but we want this to run first so that media.types.inc
// can add the fields specific to a given type (like alt tags on media).
// If implemented as an alter, this might not happen, making other alters not
// be able to work on those fields.
// @TODO: We need to pass in existing values for those attributes.
drupal_alter('media_format_form_prepare', $form, $form_state, $media);
if (!element_children($form['options'])) {
$form['options']['#attributes'] = array('style' => 'display:none');
}
return $form;
}
/**
* Wrapper around field_view_field, returns the file field w/o a label
*
* @param Object $media
* @param string $view_mode
* @param array $settings
* Any attribute overrides to pass to the style formatter.
* @return array
* drupal_renderable array
*/
function media_get_file_without_label($media, $view_mode, $settings = array()) {
$instance = field_info_instance('media', 'file', $media->type);
$format = field_get_display($instance, $view_mode, $media);
$format['label'] = 'hidden';
$format['settings'] = array_merge($format['settings'], $settings);
$media->override = $settings;
return field_view_field('media', $media, 'file', $format);
}
/**
* Clears caches that may be affected by the media filter.
*
* The media filter calls media_load(). This means that if a media object
* is updated, the check_markup() and field caches could return stale content.
* There are several possible approaches to deal with this:
* - Disable filter caching in media_filter_info(), this was found to cause a
* 30% performance hit from profiling four node teasers, due to both the
* media filter itself, and other filters that can't be cached.
* - Clear the filter and field caches whenever any media node is updated, this
* would ensure cache coherency but would reduce the effectiveness of those
* caches on high traffic sites with lots of media content updates.
* - The approach taken here: Record the fid of all media objects that are
* referenced by the media filter. Only clear the filter and field caches
* when one of these is updated, as opposed to all media objects.
* - @todo: consider an EntityFieldQuery to limit cache clearing to only those
* entities that use a text format with the media filter, possibly checking
* the contents of those fields to further limit this to fields referencing
* the media object being updated. This would need to be implemented
* carefully to avoid scalability issues with large result sets, and may
* not be worth the effort.
*
* @param $fid
* Optional media fid being updated. If not given, the cache will be cleared
* as long as any file is referenced.
*/
function media_filter_invalidate_caches($fid = FALSE) {
// If fid is passed, confirm that it has previously been referenced by the
// media filter. If not, clear the cache if the {media_filter_usage} has any
// valid records.
if (($fid && db_query('SELECT fid FROM {media_filter_usage} WHERE fid = :fid', array(':fid' => $fid))->fetchField()) || (!$fid && media_filter_usage_has_records())) {
// @todo: support entity cache, either via a hook, or using module_exists().
cache_clear_all('*', 'cache_filter', TRUE);
cache_clear_all('*', 'cache_field', TRUE);
}
}
/**
* Determines if the {media_filter_usage} table has any entries.
*/
function media_filter_usage_has_records() {
return (bool) db_query_range('SELECT 1 FROM {media_filter_usage} WHERE fid > :fid', 0, 1, array(':fid' => 0))->fetchField();
}
/**
* Tracks usage of media fids by the media filter.
*
* @param $fid
* The media fid.
*/
function media_filter_track_usage($fid) {
// This function only tracks when fids are found by the media filter.
// It would be impractical to check when formatted text is edited to remove
// references to fids, however by keeping a timestamp, we can implement
// rudimentary garbage collection in hook_flush_caches().
// However we only need to track that an fid has ever been referenced,
// not every time, so avoid updating this table more than once per month,
// per fid.
$timestamp = db_query('SELECT timestamp FROM {media_filter_usage} WHERE fid = :fid', array(':fid' => $fid))->fetchField();
if (!$timestamp || $timestamp <= REQUEST_TIME - 86400 * 30) {
db_merge('media_filter_usage')->key(array('fid' => $fid))->fields(array('fid' => $fid, 'timestamp' => REQUEST_TIME))->execute();
}
}