diff --git a/includes/media.filter.inc b/includes/media.filter.inc index 1936232..816e1db 100644 --- a/includes/media.filter.inc +++ b/includes/media.filter.inc @@ -313,12 +313,11 @@ function media_token_to_markup($match, $wysiwyg = FALSE) { $fields = media_filter_field_parser($tag_info); $attributes = is_array($tag_info['attributes']) ? $tag_info['attributes'] : array(); - $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')); + $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', _media_wysiwyg_allowed_attributes_default()); $settings['attributes'] = array_intersect_key($attributes, array_flip($attribute_whitelist)); $settings['fields'] = $fields; if (!empty($tag_info['attributes']) && is_array($tag_info['attributes'])) { - $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')); $settings['attributes'] = array_intersect_key($tag_info['attributes'], array_flip($attribute_whitelist)); $settings['fields'] = $fields; @@ -668,18 +667,46 @@ function media_get_file_without_label($file, $view_mode, $settings = array()) { // support simple formatters that don't do this, set the element attributes to // what was requested, but not if the formatter applied its own logic for // element attributes. - if (!isset($element['#attributes']) && isset($settings['attributes'])) { - $element['#attributes'] = $settings['attributes']; + if (isset($settings['attributes'])) { + if (empty($element['#attributes'])) { + $element['#attributes'] = $settings['attributes']; + } // While this function may be called for any file type, images are a common - // use-case. theme_image() and theme_image_style() require the 'alt' - // attribute to be passed separately from the 'attributes' array (see - // http://drupal.org/node/999338). Until that's fixed, implement this - // special-case logic. Image formatters using other theme functions are - // responsible for their own 'alt' attribute handling. See - // theme_media_formatter_large_icon() for an example. - if (isset($settings['attributes']['alt']) && !isset($element['#alt']) && isset($element['#theme']) && in_array($element['#theme'], array('image', 'image_style'))) { - $element['#alt'] = $settings['attributes']['alt']; + // use-case, and image theme functions have their own structures for + // render arrays. + if (isset($element['#theme'])) { + switch ($element['#theme']) { + case 'image': + case 'image_style': + // theme_image() and theme_image_style() require the 'alt' attributes to + // be passed separately from the 'attributes' array. (see + // http://drupal.org/node/999338). Until that's fixed, implement this + // special-case logic. Image formatters using other theme functions are + // responsible for their own 'alt' attribute handling. See + // theme_media_formatter_large_icon() for an example. + if (empty($element['#alt']) && isset($settings['attributes']['alt'])) { + $element['#alt'] = $settings['attributes']['alt']; + } + break; + + case 'image_formatter': + // theme_image_formatter() requires the attributes to be + // set on the item rather than the element itself. + if (empty($element['#item']['attributes'])) { + $element['#item']['attributes'] = $settings['attributes']; + } + + // theme_image_formatter() also requires alt, title, height, and + // width attributes to be set on the item rather than within its + // attributes array. + foreach (array('alt', 'title', 'width', 'height') as $attr) { + if (isset($settings['attributes'][$attr])) { + $element['#item'][$attr] = $settings['attributes'][$attr]; + } + } + break; + } } } diff --git a/js/media.filter.js b/js/media.filter.js index c1b39c9..c0e3a89 100644 --- a/js/media.filter.js +++ b/js/media.filter.js @@ -14,13 +14,12 @@ * @param content */ replaceTokenWithPlaceholder: function(content) { - var tagmap = Drupal.media.filter.ensure_tagmap(), + Drupal.media.filter.ensure_tagmap() + var tagmap = Drupal.settings.tagmap, matches = content.match(/\[\[.*?\]\]/g), - media_definition, - id = 0; + media_definition; if (matches) { - var i = 1; for (var macro in tagmap) { var index = matches.indexOf(macro); if (index !== -1) { @@ -38,10 +37,9 @@ var element = Drupal.media.filter.create_element(tagmap[macro], media_definition); var markup = Drupal.media.filter.outerHTML(element); - content = content.replace(macro, Drupal.media.filter.getWrapperStart(i) + markup + Drupal.media.filter.getWrapperEnd(i)); + content = content.replace(macro, markup); } } - i++; } } return content; @@ -52,29 +50,23 @@ * @param content */ replacePlaceholderWithToken: function(content) { - var tagmap = Drupal.media.filter.ensure_tagmap(); - var i = 1; - for (var macro in tagmap) { - var startTag = Drupal.media.filter.getWrapperStart(i), endTag = Drupal.media.filter.getWrapperEnd(i); - var startPos = content.indexOf(startTag), endPos = content.indexOf(endTag); - if (startPos !== -1 && endPos !== -1) { - // If the placeholder wrappers are empty, remove the macro too. - if (endPos - startPos - startTag.length === 0) { - macro = ''; - } - content = content.substr(0, startPos) + macro + content.substr(endPos + (new String(endTag)).length); - } - i++; - } - return content; - }, - - getWrapperStart: function(i) { - return ''; - }, + Drupal.media.filter.ensure_tagmap(); + // Convert all xhtml markup to html for reliable matching/replacing. + content = content.replace(/[\s]\/\>/g, '>'); + + // Re-build the macros in case any element has changed in the editor. + $('.media-element', content).each(function(i, element) { + var markup = Drupal.media.filter.outerHTML($(element)); + macro = Drupal.media.filter.create_macro($(element)); + + // Store the macro => html for more efficient rendering in + // replaceTokenWithPlaceholder(). + Drupal.settings.tagmap[macro] = markup; + // Replace the media element with its macro. + content = content.replace(markup, macro); + }); - getWrapperEnd: function(i) { - return ''; + return content; }, /** @@ -199,7 +191,7 @@ * @param element (jQuery object) */ outerHTML: function (element) { - return $('
').append(element.eq(0).clone()).html(); + return element[0].outerHTML || $('
').append(element.eq(0).clone()).html(); }, /** @@ -227,7 +219,7 @@ // Return the wrapped html code to insert in an editor and use it with // replacePlaceholderWithToken() - return Drupal.media.filter.getWrapperStart(i) + markup + Drupal.media.filter.getWrapperEnd(i); + return markup; }, /** diff --git a/media.info b/media.info index 3398d7e..ebc6c54 100644 --- a/media.info +++ b/media.info @@ -6,6 +6,7 @@ core = 7.x dependencies[] = file_entity dependencies[] = image dependencies[] = views +dependencies[] = token files[] = includes/MediaReadOnlyStreamWrapper.inc files[] = includes/MediaBrowserPluginInterface.inc diff --git a/media.module b/media.module index 7435518..61e7450 100644 --- a/media.module +++ b/media.module @@ -1068,6 +1068,23 @@ function media_file_displays_alter(&$displays, $file, $view_mode) { $file->{$field_name} = $value;} } } + // Alt and title are special. + // @see file_entity_file_load + $alt = variable_get('file_entity_alt', '[file:field_file_image_alt_text]'); + $title = variable_get('file_entity_title', '[file:field_file_image_title_text]'); + + $replace_options = array( + 'clear' => TRUE, + 'sanitize' => FALSE, + ); + + // Load alt and title text from fields. + if (!empty($alt)) { + $file->alt = token_replace($alt, array('file' => $file), $replace_options); + } + if (!empty($title)) { + $file->title = token_replace($title, array('file' => $file), $replace_options); + } } /** @@ -1300,3 +1317,27 @@ function _media_get_migratable_file_types() { return array_diff($types, $enabled_types); } + +/** + * Returns the default set of allowed attributes for use with WYSIWYG. + * + * @return Array of whitelisted attributes. + */ +function _media_wysiwyg_allowed_attributes_default() { + return array( + 'alt', + 'title', + 'height', + 'width', + 'hspace', + 'vspace', + 'border', + 'align', + 'style', + 'class', + 'id', + 'usemap', + 'data-picture-group', + 'data-picture-align', + ); +} diff --git a/tests/media.file.usage.test b/tests/media.file.usage.test index db5c614..39b2ea6 100644 --- a/tests/media.file.usage.test +++ b/tests/media.file.usage.test @@ -22,7 +22,7 @@ class MediaFileUsageTest extends MediaTestHelper { * Enable media and file entity modules for testing. */ public function setUp() { - parent::setUp(array('media', 'file_entity')); + parent::setUp(); // Create and log in a user. $account = $this->drupalCreateUser(array('administer nodes', 'create article content')); @@ -30,72 +30,6 @@ class MediaFileUsageTest extends MediaTestHelper { } /** - * Generates markup to be inserted for a file. - * - * This is a PHP version of InsertMedia.insert() from js/wysiwyg-media.js. - * - * @param int $fid - * Drupal file id - * @param int $count - * Quantity of markup to insert - * - * @return string - * Filter markup. - */ - private function generateFileMarkup($fid, $count = 1) { - $file_usage_markup = ''; - - // Build the data that is used in a media tag. - $data = array( - 'fid' => $fid, - 'type' => 'media', - 'view_mode' => 'preview', - 'attributes' => array( - 'height' => 100, - 'width' => 100, - 'classes' => 'media-element file_preview', - ) - ); - - // Create the file usage markup. - for ($i = 1; $i <= $count; $i++) { - $file_usage_markup .= '

[[' . drupal_json_encode($data) . ']]

'; - } - - return $file_usage_markup; - } - - - /** - * Utility function to create a test node. - * - * @param int $fid - * Create the node with media markup in the body field - * - * @return int - * Returns the node id - */ - private function createNode($fid = FALSE) { - $markup = ''; - if (! empty($fid)) { - $markup = $this->generateFileMarkup($fid); - } - - // Create an article node with file markup in the body field. - $edit = array( - 'title' => $this->randomName(8), - 'body[und][0][value]' => $markup, - ); - // Save the article node. First argument is the URL, then the value array - // and the third is the label the button that should be "clicked". - $this->drupalPost('node/add/article', $edit, t('Save')); - - // Get the article node that was saved by the unique title. - $node = $this->drupalGetNodeByTitle($edit['title']); - return $node->nid; - } - - /** * Tests the tracking of file usages for files submitted via the WYSIWYG editor. */ public function testFileUsageIncrementing() { @@ -133,7 +67,7 @@ class MediaFileUsageTest extends MediaTestHelper { // Create a new revision that has two instances of the file. File usage will // be 4. $node = node_load($nid); - $node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 2); + $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 2); $node->revision = TRUE; node_save($node); @@ -163,7 +97,7 @@ class MediaFileUsageTest extends MediaTestHelper { // Create a new revision that has the file on it. File usage will be 5. $node = node_load($nid); - $node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 1); + $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 1); $node->revision = TRUE; node_save($node); @@ -194,7 +128,7 @@ class MediaFileUsageTest extends MediaTestHelper { // Create a new revision with the file on it twice. File usage will be 4. $node = node_load($nid); - $node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 2); + $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 2); $node->revision = TRUE; node_save($node); @@ -206,7 +140,7 @@ class MediaFileUsageTest extends MediaTestHelper { // Re-save current revision with file on it once instead of twice. File // usage will be 3. $node = node_load($nid); - $node->body[LANGUAGE_NONE][0]['value'] = $this->generateFileMarkup($fid, 1); + $node->body[LANGUAGE_NONE][0]['value'] = $this->generateJsonTokenMarkup($fid, 1); $saved_vid = $node->vid; node_save($node); diff --git a/tests/media.test b/tests/media.test index aaf6dcd..3a7d92a 100644 --- a/tests/media.test +++ b/tests/media.test @@ -14,6 +14,171 @@ class MediaTestHelper extends DrupalWebTestCase { * Enable media and file entity modules for testing. */ public function setUp() { - parent::setUp(array('media', 'file_entity')); + parent::setUp(array('token', 'media', 'file_entity')); } + + /** + * Generates markup to be inserted for a file. + * + * This is a PHP version of InsertMedia.insert() from js/wysiwyg-media.js. + * + * @param int $fid + * Drupal file id + * @param int $count + * Quantity of markup to insert + * @param array $attributes + * Extra attributes to insert. + * @param array $fields + * Extra field values to insert. + * + * @return string + * Filter markup. + */ + protected function generateJsonTokenMarkup($fid, $count = 1, array $attributes = array(), array $fields = array()) { + $markup = ''; + // Merge default atttributes. + $attributes += array( + 'height' => 100, + 'width' => 100, + 'classes' => 'media-element file_preview', + ); + + // Build the data that is used in a media tag. + $data = array( + 'fid' => $fid, + 'type' => 'media', + 'view_mode' => 'preview', + 'attributes' => $attributes, + 'fields' => $fields, + ); + + // Create the file usage markup. + for ($i = 1; $i <= $count; $i++) { + $markup .= '

[[' . drupal_json_encode($data) . ']]

'; + } + + return $markup; + } + + /** + * Utility function to create a test node. + * + * @param int $fid + * Create the node with media markup in the body field + * @param array $attributes + * Extra attributes to insert to the file. + * @param array $fields + * Extra field values to insert. + * + * @return int + * Returns the node id + */ + protected function createNode($fid = FALSE, array $attributes = array(), array $fields = array()) { + $markup = ''; + if (! empty($fid)) { + $markup = $this->generateJsonTokenMarkup($fid, 1, $attributes, $fields); + } + + // Create an article node with file markup in the body field. + $edit = array( + 'title' => $this->randomName(8), + 'body[und][0][value]' => $markup, + ); + // Save the article node. First argument is the URL, then the value array + // and the third is the label the button that should be "clicked". + $this->drupalPost('node/add/article', $edit, t('Save')); + + // Get the article node that was saved by the unique title. + $node = $this->drupalGetNodeByTitle($edit['title']); + return $node->nid; + } + +} + +class MediaWysiwygOverridesTest extends MediaTestHelper { + + /** + * Provide test information. + */ + public static function getInfo() { + return array( + 'name' => t('Media wysiwyg overrides'), + 'description' => t('Tests that overriden attributes display correct.'), + 'group' => t('Media'), + ); + } + + public function setUp() { + parent::setUp(); + + // Create and log in a user. + $account = $this->drupalCreateUser(array('administer nodes', 'create article content', 'administer filters', 'use text format filtered_html')); + $this->drupalLogin($account); + + // Enable the media filter for full html. + $edit = array( + 'filters[media_filter][status]' => TRUE, + 'filters[filter_html][status]' => FALSE, + ); + $this->drupalPost('admin/config/content/formats/filtered_html', $edit, t('Save configuration')); + } + + /** + * Test image media overrides. + */ + public function testAttributeOverrides() { + $files = $this->drupalGetTestFiles('image'); + $file = file_save($files[0]); + + // Create a node to test with. + $nid = $this->createNode($file->fid); + + $this->drupalGet('node/' . $nid); + $this->assertRaw('height="100" width="100"', t('Image displays with default attributes.')); + + // Create a node with overriden attributes. + $attributes = array( + 'style' => 'float: left; width: 50px;', + ); + $nid = $this->createNode($file->fid, $attributes); + $this->drupalGet('node/' . $nid); + $this->assertRaw('style="float: left; width: 50px;"', t('Image displays with inline attributes.')); + + // Create a node with overriden alt/title. + $attributes = array( + 'alt' => $this->randomName(), + 'title' => $this->randomName(), + ); + $nid = $this->createNode($file->fid, $attributes); + $this->drupalGet('node/' . $nid); + $this->assertRaw(drupal_attributes($attributes), t('Image displays with alt/title set as attributes.')); + + // Create a node with overriden alt/title fields. + $fields = $attributes = array(); + $attributes['alt'] = $fields['field_file_image_alt_text[und][0][value]'] = $this->randomName(); + $attributes['title'] = $fields['field_file_image_title_text[und][0][value]'] = $this->randomName(); + + $this->drupalGet('file/' . $file->fid . '/edit'); + $this->drupalGet('file/' . $file->fid); + $this->drupalGet('admin/config/media/file-settings'); + $nid = $this->createNode($file->fid, array(), $fields); + $this->drupalGet('node/' . $nid); + // Ensure that the alt/title from attributes display rather the field ones. + $this->assertRaw(drupal_attributes($attributes), t('Image displays with alt/title set as fields.')); + + // Create a node with overriden alt/title fields as well as attributes. + $attributes = array( + 'alt' => $this->randomName(), + 'title' => $this->randomName(), + ); + $fields = array( + 'field_file_image_alt_text[und][0][value]' => $this->randomName(), + 'field_file_image_title_text[und][0][value]' => $this->randomName(), + ); + $nid = $this->createNode($file->fid, $attributes, $fields); + $this->drupalGet('node/' . $nid); + // Ensure that the alt/title from attributes display rather the field ones. + $this->assertRaw(drupal_attributes($attributes), t('Image displays with alt/title set as attributes overriding field values.')); + } + } diff --git a/wysiwyg_plugins/media.inc b/wysiwyg_plugins/media.inc index d90b79f..611ee60 100644 --- a/wysiwyg_plugins/media.inc +++ b/wysiwyg_plugins/media.inc @@ -68,7 +68,7 @@ function media_include_browser_js() { } } // Add wysiwyg-specific settings. - $settings = array('wysiwyg_allowed_attributes' => variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align'))); + $settings = array('wysiwyg_allowed_attributes' => variable_get('media__wysiwyg_allowed_attributes', _media_wysiwyg_allowed_attributes_default())); drupal_add_js(array('media' => $settings), 'setting'); }